@lincoln504/pi-research 0.1.13 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +55 -138
- package/dist/cli.mjs +23375 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +130 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +211 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +122 -0
- package/docs/THIRD-PARTY-NOTICES.md +66 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1195 -0
- package/src/config.ts +1032 -155
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +258 -11
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -212
- package/src/infrastructure/searxng-lifecycle.ts +0 -644
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stderr/Stdout Capture
|
|
3
|
+
*
|
|
4
|
+
* Captures stderr/stdout during task execution to redirect native logs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Use createRequire to get a mutable reference to 'fs'.
|
|
8
|
+
// ESM namespace imports (import * as fs from 'node:fs') create bindings that are
|
|
9
|
+
// writable at Node.js runtime but flagged as immutable by esbuild's static analysis.
|
|
10
|
+
// Since this module needs to monkey-patch fs.writeSync to capture native addon output
|
|
11
|
+
// on FD 1/2, we use createRequire — the official Node.js CJS↔ESM interop mechanism
|
|
12
|
+
// — to obtain a fully mutable module object.
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import type * as FsType from 'node:fs';
|
|
15
|
+
import type { Stats } from 'node:fs';
|
|
16
|
+
import { TextDecoder } from 'node:util';
|
|
17
|
+
import type { LogContext } from './log-utils.ts';
|
|
18
|
+
import { getLogContext, formatArg, safeJsonStringify, redactSecrets } from './log-utils.ts';
|
|
19
|
+
|
|
20
|
+
const fs: typeof FsType = createRequire(import.meta.url)('node:fs');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Session-scoped capture flags to support concurrent research runs.
|
|
24
|
+
* Maps session IDs to capture state, with a global flag as fallback.
|
|
25
|
+
*/
|
|
26
|
+
const sessionCaptureStates = new Map<string, boolean>();
|
|
27
|
+
// Active UNKEYED captures (captureStdio called without a sessionId — e.g. the outer
|
|
28
|
+
// research run's runCapturingStderr). Tracked as a depth counter separate from the keyed
|
|
29
|
+
// map so that ending a keyed capture (which recomputes the global flag) cannot clear the
|
|
30
|
+
// flag while an unkeyed capture is still active — otherwise a later capture would treat
|
|
31
|
+
// stdout/stderr as un-patched, save the still-patched writers as "originals", and on a
|
|
32
|
+
// LIFO-violating restore leave them permanently diverted.
|
|
33
|
+
let unkeyedCaptureDepth = 0;
|
|
34
|
+
let isAnyLoggerCapturingOutput = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Recompute the global "is anything capturing" flag from BOTH the keyed sessions and
|
|
38
|
+
* the unkeyed-capture depth, so neither scheme can clobber the other's state.
|
|
39
|
+
*/
|
|
40
|
+
function recomputeGlobalCapturing(): void {
|
|
41
|
+
isAnyLoggerCapturingOutput =
|
|
42
|
+
unkeyedCaptureDepth > 0 || Array.from(sessionCaptureStates.values()).some(v => v);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a session is currently capturing output
|
|
47
|
+
*/
|
|
48
|
+
export function isSessionCapturing(sessionId?: string): boolean {
|
|
49
|
+
if (sessionId) {
|
|
50
|
+
return sessionCaptureStates.get(sessionId) ?? false;
|
|
51
|
+
}
|
|
52
|
+
return isAnyLoggerCapturingOutput;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mark a session as capturing or not capturing
|
|
57
|
+
*/
|
|
58
|
+
export function setSessionCapturing(sessionId: string, capturing: boolean): void {
|
|
59
|
+
sessionCaptureStates.set(sessionId, capturing);
|
|
60
|
+
if (capturing) {
|
|
61
|
+
isAnyLoggerCapturingOutput = true;
|
|
62
|
+
} else {
|
|
63
|
+
// Recompute from all sessions AND active unkeyed captures.
|
|
64
|
+
recomputeGlobalCapturing();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Native log patterns that should be captured
|
|
70
|
+
*/
|
|
71
|
+
const NATIVE_LOG_PATTERNS = [
|
|
72
|
+
'Warning:',
|
|
73
|
+
'Error:',
|
|
74
|
+
'Dawn',
|
|
75
|
+
'ONNX',
|
|
76
|
+
'ort',
|
|
77
|
+
'maxDynamicStorageBuffersPerPipelineLayout',
|
|
78
|
+
'maxComputeWorkgroupStorageSize',
|
|
79
|
+
'allocation limit',
|
|
80
|
+
'artificially',
|
|
81
|
+
'reduced from',
|
|
82
|
+
'dynamic offset allocation limit',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Complex TUI ANSI escape sequences
|
|
87
|
+
*/
|
|
88
|
+
const COMPLEX_TUI_PATTERNS = [
|
|
89
|
+
'\x1b[H', // Home
|
|
90
|
+
'\x1b[2J', // Clear screen
|
|
91
|
+
'\x1b[J', // Clear to end
|
|
92
|
+
'\x1b[K', // Clear line
|
|
93
|
+
'\x1b[?25', // Cursor visibility
|
|
94
|
+
'\x1b[?1000', // Mouse tracking
|
|
95
|
+
'\x1b[?1002',
|
|
96
|
+
'\x1b[?1003',
|
|
97
|
+
'\x1b[?1006',
|
|
98
|
+
'\x1b[?2004', // Bracketed paste
|
|
99
|
+
'\x1b[<u', // Kitty protocol
|
|
100
|
+
'\x1b[>4;0m', // xterm modifyOtherKeys
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Box-drawing characters - definitive TUI marker
|
|
105
|
+
*/
|
|
106
|
+
const BOX_DRAWING_CHARS = /[─│┌┐└┘├┤┬┴┼═║╔╗╚╝╠╣╦╩╬╴╵╶╷╭╮╯╰╱╲╳]/;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if a message contains a complex TUI escape sequence
|
|
110
|
+
*/
|
|
111
|
+
function isComplexTui(message: string): boolean {
|
|
112
|
+
if (!message.includes('\x1b[')) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const pattern of COMPLEX_TUI_PATTERNS) {
|
|
117
|
+
if (message.includes(pattern)) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check for cursor position patterns (ANSI ESC sequences — control chars intentional)
|
|
123
|
+
// eslint-disable-next-line no-control-regex
|
|
124
|
+
if (/\u001b\[\d+;\d+H/.test(message) || /\u001b\[\d+[ABCD]/.test(message)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if a message is a native log (should be captured)
|
|
133
|
+
*/
|
|
134
|
+
function isNativeLog(message: string): boolean {
|
|
135
|
+
return NATIVE_LOG_PATTERNS.some(pattern => message.includes(pattern));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a message is box drawing (TUI)
|
|
140
|
+
*/
|
|
141
|
+
function isBoxDrawing(message: string): boolean {
|
|
142
|
+
return BOX_DRAWING_CHARS.test(message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if stderr should be redirected
|
|
147
|
+
*/
|
|
148
|
+
function shouldRedirectStderr(stat: Stats): boolean {
|
|
149
|
+
// Only redirect when FD 2 is a tty or regular file, not a pipe/socket (MCP mode)
|
|
150
|
+
return !stat.isFIFO() && !stat.isSocket();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create a console patch function
|
|
155
|
+
*/
|
|
156
|
+
function createConsolePatch(level: string, logFile: string, hasSufficientDiskSpace: () => boolean) {
|
|
157
|
+
return (...args: unknown[]) => {
|
|
158
|
+
// Check disk space before writing
|
|
159
|
+
if (!hasSufficientDiskSpace()) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const timestamp = new Date().toISOString();
|
|
164
|
+
const entry: LogContext = {
|
|
165
|
+
timestamp,
|
|
166
|
+
level: `CONSOLE_${level.toUpperCase()}`,
|
|
167
|
+
...getLogContext(),
|
|
168
|
+
// Redact secrets + bound size, matching Logger.emit(); captured console
|
|
169
|
+
// output can echo auth headers / tokens from dependencies.
|
|
170
|
+
message: redactSecrets(args.map(formatArg).join(' ')),
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
fs.appendFileSync(logFile, `${safeJsonStringify(entry)}\n`);
|
|
174
|
+
} catch { /* ignore */ }
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Run a task while capturing its stderr and stdout
|
|
180
|
+
*/
|
|
181
|
+
export async function captureStdio<T>(
|
|
182
|
+
logFile: string,
|
|
183
|
+
hasSufficientDiskSpace: () => boolean,
|
|
184
|
+
task: () => Promise<T>,
|
|
185
|
+
sessionId?: string
|
|
186
|
+
): Promise<T> {
|
|
187
|
+
// Check if this specific session or any session is already capturing
|
|
188
|
+
const sessionCapturing = sessionId ? isSessionCapturing(sessionId) : false;
|
|
189
|
+
if (!logFile || sessionCapturing || (!sessionId && isAnyLoggerCapturingOutput)) {
|
|
190
|
+
return await task();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (sessionId) {
|
|
194
|
+
setSessionCapturing(sessionId, true);
|
|
195
|
+
} else {
|
|
196
|
+
unkeyedCaptureDepth++;
|
|
197
|
+
isAnyLoggerCapturingOutput = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const decoder = new TextDecoder();
|
|
201
|
+
|
|
202
|
+
// Save originals
|
|
203
|
+
const originalStderrWrite = process.stderr.write;
|
|
204
|
+
const originalStdoutWrite = process.stdout.write;
|
|
205
|
+
const originalFsWriteSync = fs.writeSync;
|
|
206
|
+
const originalConsole = {
|
|
207
|
+
log: console.log,
|
|
208
|
+
info: console.info,
|
|
209
|
+
warn: console.warn,
|
|
210
|
+
error: console.error,
|
|
211
|
+
debug: console.debug,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Redirect FD 2 at the OS level (Linux + macOS only)
|
|
215
|
+
let savedFd2: number = -1;
|
|
216
|
+
let fd2Redirected = false;
|
|
217
|
+
if (process.platform !== 'win32') {
|
|
218
|
+
try {
|
|
219
|
+
const stat = fs.fstatSync(2);
|
|
220
|
+
if (shouldRedirectStderr(stat)) {
|
|
221
|
+
savedFd2 = fs.openSync('/dev/fd/2', 'a');
|
|
222
|
+
fs.closeSync(2);
|
|
223
|
+
const newFd = fs.openSync(logFile, 'a');
|
|
224
|
+
if (newFd === 2) {
|
|
225
|
+
fd2Redirected = true;
|
|
226
|
+
} else {
|
|
227
|
+
// Didn't get FD 2 — undo
|
|
228
|
+
if (newFd >= 0) fs.closeSync(newFd);
|
|
229
|
+
try {
|
|
230
|
+
const r = fs.openSync(`/dev/fd/${savedFd2}`, 'a');
|
|
231
|
+
if (r !== 2 && r >= 0) fs.closeSync(r);
|
|
232
|
+
} catch { /* best-effort restore */ }
|
|
233
|
+
try { fs.closeSync(savedFd2); } catch { /* ignore */ }
|
|
234
|
+
savedFd2 = -1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch { /* fstat failed or /dev/fd unavailable */ }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Patch console methods
|
|
241
|
+
console.log = createConsolePatch('log', logFile, hasSufficientDiskSpace);
|
|
242
|
+
console.info = createConsolePatch('info', logFile, hasSufficientDiskSpace);
|
|
243
|
+
console.warn = createConsolePatch('warn', logFile, hasSufficientDiskSpace);
|
|
244
|
+
console.error = createConsolePatch('error', logFile, hasSufficientDiskSpace);
|
|
245
|
+
console.debug = createConsolePatch('debug', logFile, hasSufficientDiskSpace);
|
|
246
|
+
|
|
247
|
+
// Patch stderr.write
|
|
248
|
+
(process.stderr.write as any) = (chunk: string | Uint8Array, encodingOrCb?: any, callback?: any) => {
|
|
249
|
+
const cb = typeof encodingOrCb === 'function' ? encodingOrCb : callback;
|
|
250
|
+
const message = typeof chunk === 'string' ? chunk : decoder.decode(chunk);
|
|
251
|
+
|
|
252
|
+
// Check disk space before writing
|
|
253
|
+
if (!hasSufficientDiskSpace()) {
|
|
254
|
+
if (typeof cb === 'function') cb();
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const timestamp = new Date().toISOString();
|
|
259
|
+
const entry: LogContext = {
|
|
260
|
+
timestamp,
|
|
261
|
+
level: 'STDERR',
|
|
262
|
+
...getLogContext(),
|
|
263
|
+
message: redactSecrets(message.trim()),
|
|
264
|
+
};
|
|
265
|
+
try {
|
|
266
|
+
fs.appendFileSync(logFile, `${safeJsonStringify(entry)}\n`);
|
|
267
|
+
} catch { /* ignore */ }
|
|
268
|
+
|
|
269
|
+
if (typeof cb === 'function') cb();
|
|
270
|
+
return true;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Patch stdout.write
|
|
274
|
+
(process.stdout.write as any) = (chunk: string | Uint8Array, encodingOrCb?: any, callback?: any) => {
|
|
275
|
+
const message = typeof chunk === 'string' ? chunk : decoder.decode(chunk);
|
|
276
|
+
|
|
277
|
+
// Native log patterns or plain text that we want to divert from TUI
|
|
278
|
+
const isNative = isNativeLog(message);
|
|
279
|
+
const complexTui = isComplexTui(message);
|
|
280
|
+
const boxDrawing = isBoxDrawing(message);
|
|
281
|
+
const hasAnsi = message.includes('\x1b[');
|
|
282
|
+
|
|
283
|
+
// If it's a known native log, divert it regardless of colors/TUI markers
|
|
284
|
+
if (isNative && !boxDrawing) {
|
|
285
|
+
const timestamp = new Date().toISOString();
|
|
286
|
+
const entry: LogContext = {
|
|
287
|
+
timestamp,
|
|
288
|
+
level: 'STDOUT_NATIVE',
|
|
289
|
+
...getLogContext(),
|
|
290
|
+
message: redactSecrets(message.trim()),
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
fs.appendFileSync(logFile, `${safeJsonStringify(entry)}\n`);
|
|
294
|
+
} catch { /* ignore */ }
|
|
295
|
+
|
|
296
|
+
const cb = typeof encodingOrCb === 'function' ? encodingOrCb : callback;
|
|
297
|
+
if (typeof cb === 'function') cb();
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If it's complex TUI or box-drawing, PASS THROUGH
|
|
302
|
+
if (complexTui || boxDrawing) {
|
|
303
|
+
return originalStdoutWrite.call(process.stdout, chunk, encodingOrCb, callback);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// If it's plain text (no ANSI) and non-empty, divert and log
|
|
307
|
+
if (!hasAnsi && message.trim().length > 0) {
|
|
308
|
+
if (!hasSufficientDiskSpace()) {
|
|
309
|
+
const cb = typeof encodingOrCb === 'function' ? encodingOrCb : callback;
|
|
310
|
+
if (typeof cb === 'function') cb();
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const timestamp = new Date().toISOString();
|
|
315
|
+
const entry: LogContext = {
|
|
316
|
+
timestamp,
|
|
317
|
+
level: 'STDOUT_PLAIN',
|
|
318
|
+
...getLogContext(),
|
|
319
|
+
message: redactSecrets(message.trim()),
|
|
320
|
+
};
|
|
321
|
+
try {
|
|
322
|
+
fs.appendFileSync(logFile, `${safeJsonStringify(entry)}\n`);
|
|
323
|
+
} catch { /* ignore */ }
|
|
324
|
+
|
|
325
|
+
const cb = typeof encodingOrCb === 'function' ? encodingOrCb : callback;
|
|
326
|
+
if (typeof cb === 'function') cb();
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Everything else goes to stdout
|
|
331
|
+
return originalStdoutWrite.call(process.stdout, chunk, encodingOrCb, callback);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Patch fs.writeSync for FD 1 and 2
|
|
335
|
+
try {
|
|
336
|
+
const descriptor = Object.getOwnPropertyDescriptor(fs, 'writeSync');
|
|
337
|
+
if (!descriptor || (descriptor.writable || descriptor.set)) {
|
|
338
|
+
fs.writeSync = (fd: number, chunk: any, ...args: any[]) => {
|
|
339
|
+
if (fd === 1 || fd === 2) {
|
|
340
|
+
const message = typeof chunk === 'string' ? chunk : decoder.decode(chunk);
|
|
341
|
+
|
|
342
|
+
const isNative = isNativeLog(message);
|
|
343
|
+
const boxDrawing = isBoxDrawing(message);
|
|
344
|
+
const complexTui = isComplexTui(message);
|
|
345
|
+
const hasAnsi = message.includes('\x1b[');
|
|
346
|
+
|
|
347
|
+
const shouldDivert = fd === 2 ||
|
|
348
|
+
(isNative && !boxDrawing && !complexTui) ||
|
|
349
|
+
(!hasAnsi && !boxDrawing && message.trim().length > 0);
|
|
350
|
+
|
|
351
|
+
if (shouldDivert) {
|
|
352
|
+
if (!hasSufficientDiskSpace()) {
|
|
353
|
+
return (typeof chunk === 'string' ? Buffer.from(chunk).length : (chunk as any).length);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const timestamp = new Date().toISOString();
|
|
357
|
+
const entry: LogContext = {
|
|
358
|
+
timestamp,
|
|
359
|
+
level: fd === 1 ? 'FS_WRITE_SYNC_STDOUT' : 'FS_WRITE_SYNC_STDERR',
|
|
360
|
+
...getLogContext(),
|
|
361
|
+
message: redactSecrets(message.trim()),
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
fs.appendFileSync(logFile, `${safeJsonStringify(entry)}\n`);
|
|
365
|
+
} catch { /* ignore */ }
|
|
366
|
+
return (typeof chunk === 'string' ? Buffer.from(chunk).length : (chunk as any).length);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return (originalFsWriteSync as any).apply(fs, [fd, chunk, ...args]);
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
} catch (_e) { /* ignore */ }
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
return await task();
|
|
376
|
+
} finally {
|
|
377
|
+
// Restore everything
|
|
378
|
+
process.stderr.write = originalStderrWrite;
|
|
379
|
+
process.stdout.write = originalStdoutWrite;
|
|
380
|
+
try {
|
|
381
|
+
fs.writeSync = originalFsWriteSync;
|
|
382
|
+
} catch { /* ignore */ }
|
|
383
|
+
console.log = originalConsole.log;
|
|
384
|
+
console.info = originalConsole.info;
|
|
385
|
+
console.warn = originalConsole.warn;
|
|
386
|
+
console.error = originalConsole.error;
|
|
387
|
+
console.debug = originalConsole.debug;
|
|
388
|
+
if (sessionId) {
|
|
389
|
+
setSessionCapturing(sessionId, false);
|
|
390
|
+
} else {
|
|
391
|
+
unkeyedCaptureDepth = Math.max(0, unkeyedCaptureDepth - 1);
|
|
392
|
+
recomputeGlobalCapturing();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Restore FD 2
|
|
396
|
+
if (fd2Redirected && savedFd2 >= 0) {
|
|
397
|
+
try {
|
|
398
|
+
fs.closeSync(2);
|
|
399
|
+
const r = fs.openSync(`/dev/fd/${savedFd2}`, 'a');
|
|
400
|
+
if (r !== 2 && r >= 0) {
|
|
401
|
+
// Didn't recover FD 2 — leave restored FD open
|
|
402
|
+
}
|
|
403
|
+
try { fs.closeSync(savedFd2); } catch { /* ignore */ }
|
|
404
|
+
} catch { /* ignore restore errors */ }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|