Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
106 lines
2.5 KiB
JavaScript
106 lines
2.5 KiB
JavaScript
'use strict'
|
|
|
|
const { InvalidArgumentError } = require('../core/errors')
|
|
|
|
module.exports = class WrapHandler {
|
|
#handler
|
|
|
|
constructor (handler) {
|
|
this.#handler = handler
|
|
}
|
|
|
|
static wrap (handler) {
|
|
// TODO (fix): More checks...
|
|
return handler.onRequestStart ? handler : new WrapHandler(handler)
|
|
}
|
|
|
|
// Unwrap Interface
|
|
|
|
onConnect (abort, context) {
|
|
return this.#handler.onConnect?.(abort, context)
|
|
}
|
|
|
|
onResponseStarted () {
|
|
return this.#handler.onResponseStarted?.()
|
|
}
|
|
|
|
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
return this.#handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
|
|
}
|
|
|
|
onUpgrade (statusCode, rawHeaders, socket) {
|
|
return this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
}
|
|
|
|
onData (data) {
|
|
return this.#handler.onData?.(data)
|
|
}
|
|
|
|
onComplete (trailers) {
|
|
return this.#handler.onComplete?.(trailers)
|
|
}
|
|
|
|
onError (err) {
|
|
if (!this.#handler.onError) {
|
|
throw err
|
|
}
|
|
|
|
return this.#handler.onError?.(err)
|
|
}
|
|
|
|
// Wrap Interface
|
|
|
|
onRequestStart (controller, context) {
|
|
this.#handler.onConnect?.((reason) => controller.abort(reason), context)
|
|
}
|
|
|
|
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
const rawHeaders = []
|
|
for (const [key, val] of Object.entries(headers)) {
|
|
rawHeaders.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
}
|
|
|
|
this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
|
|
}
|
|
|
|
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
const rawHeaders = []
|
|
for (const [key, val] of Object.entries(headers)) {
|
|
rawHeaders.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
}
|
|
|
|
if (this.#handler.onHeaders?.(statusCode, rawHeaders, () => controller.resume(), statusMessage) === false) {
|
|
controller.pause()
|
|
}
|
|
}
|
|
|
|
onResponseData (controller, data) {
|
|
if (this.#handler.onData?.(data) === false) {
|
|
controller.pause()
|
|
}
|
|
}
|
|
|
|
onResponseEnd (controller, trailers) {
|
|
const rawTrailers = []
|
|
for (const [key, val] of Object.entries(trailers)) {
|
|
rawTrailers.push(Buffer.from(key, 'latin1'), toRawHeaderValue(val))
|
|
}
|
|
|
|
this.#handler.onComplete?.(rawTrailers)
|
|
}
|
|
|
|
onResponseError (controller, err) {
|
|
if (!this.#handler.onError) {
|
|
throw new InvalidArgumentError('invalid onError method')
|
|
}
|
|
|
|
this.#handler.onError?.(err)
|
|
}
|
|
}
|
|
|
|
function toRawHeaderValue (value) {
|
|
return Array.isArray(value)
|
|
? value.map((item) => Buffer.from(item, 'latin1'))
|
|
: Buffer.from(value, 'latin1')
|
|
}
|