@dreb/coding-agent 2.63.0 → 2.64.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/README.md +2 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/output-guard.d.ts +9 -1
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +75 -20
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/index.d.ts +1 -0
- package/dist/modes/rpc/index.d.ts.map +1 -1
- package/dist/modes/rpc/index.js +1 -0
- package/dist/modes/rpc/index.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +21 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-event-projection.d.ts +7 -0
- package/dist/modes/rpc/rpc-event-projection.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-event-projection.js +161 -0
- package/dist/modes/rpc/rpc-event-projection.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +16 -7
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +16 -0
- package/docs/dashboard.md +16 -2
- package/docs/rpc.md +28 -2
- package/docs/settings.md +5 -0
- package/package.json +1 -1
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
export declare function takeOverStdout(): void;
|
|
2
2
|
export declare function restoreStdout(): void;
|
|
3
3
|
export declare function isStdoutTakenOver(): boolean;
|
|
4
|
-
/** Maximum aggregate bytes allowed for ordinary queued writes before
|
|
4
|
+
/** Maximum aggregate bytes allowed for ordinary queued writes before the no-drain window starts. */
|
|
5
5
|
export declare const MAX_QUEUED_STDOUT_BYTES: number;
|
|
6
|
+
/** Abort if the queue stays above the cap with no drain progress for this long. */
|
|
7
|
+
export declare const MAX_NO_DRAIN_GRACE_MS = 30000;
|
|
6
8
|
export declare function writeRawStdout(text: string): void;
|
|
7
9
|
export declare function flushRawStdout(): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Test-only: clear all queue state (backlog, byte count, backpressure flag,
|
|
12
|
+
* drain waiters, and the no-drain abort timer) so tests start from a clean
|
|
13
|
+
* process-global slate.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resetOutputGuardForTests(): void;
|
|
8
16
|
//# sourceMappingURL=output-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-guard.d.ts","sourceRoot":"","sources":["../../src/core/output-guard.ts"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,IAAI,IAAI,CAyBrC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAOpC;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;
|
|
1
|
+
{"version":3,"file":"output-guard.d.ts","sourceRoot":"","sources":["../../src/core/output-guard.ts"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,IAAI,IAAI,CAyBrC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAOpC;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAkBD,oGAAoG;AACpG,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AAExD,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAiH5C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAUjD;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBpD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAM/C","sourcesContent":["interface StdoutTakeoverState {\n\trawStdoutWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\trawStderrWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\toriginalStdoutWrite: typeof process.stdout.write;\n}\n\nlet stdoutTakeoverState: StdoutTakeoverState | undefined;\n\nexport function takeOverStdout(): void {\n\tif (stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tconst rawStdoutWrite = process.stdout.write.bind(process.stdout) as StdoutTakeoverState[\"rawStdoutWrite\"];\n\tconst rawStderrWrite = process.stderr.write.bind(process.stderr) as StdoutTakeoverState[\"rawStderrWrite\"];\n\tconst originalStdoutWrite = process.stdout.write;\n\n\tprocess.stdout.write = ((\n\t\tchunk: string | Uint8Array,\n\t\tencodingOrCallback?: BufferEncoding | ((error?: Error | null) => void),\n\t\tcallback?: (error?: Error | null) => void,\n\t): boolean => {\n\t\tif (typeof encodingOrCallback === \"function\") {\n\t\t\treturn rawStderrWrite(String(chunk), encodingOrCallback);\n\t\t}\n\t\treturn rawStderrWrite(String(chunk), callback);\n\t}) as typeof process.stdout.write;\n\n\tstdoutTakeoverState = {\n\t\trawStdoutWrite,\n\t\trawStderrWrite,\n\t\toriginalStdoutWrite,\n\t};\n}\n\nexport function restoreStdout(): void {\n\tif (!stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tprocess.stdout.write = stdoutTakeoverState.originalStdoutWrite;\n\tstdoutTakeoverState = undefined;\n}\n\nexport function isStdoutTakenOver(): boolean {\n\treturn stdoutTakeoverState !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Backpressure-aware bounded write queue\n//\n// stream.write() returns false when the stream's internal buffer is full\n// (backpressure). Ignoring that signal during high-rate event streaming lets\n// output queue up unboundedly inside the process, which is what produced the\n// multi-thousand-event end-of-response bursts in issue 448. While the stream\n// is backpressured we queue subsequent writes and flush them in order on\n// \"drain\". The queue is byte-capped: legitimate multi-MiB bursts (for example\n// dashboard turns with several inline images, issue 495) can push the backlog\n// past the cap while the consumer is merely slow, so the cap is a grace\n// trigger, not an immediate kill — we only abort when the consumer makes no\n// drain progress for the grace window, which means the output channel is dead\n// or hopelessly behind. That keeps us from growing memory without bound.\n// ---------------------------------------------------------------------------\n\n/** Maximum aggregate bytes allowed for ordinary queued writes before the no-drain window starts. */\nexport const MAX_QUEUED_STDOUT_BYTES = 16 * 1024 * 1024; // 16 MiB\n\n/** Abort if the queue stays above the cap with no drain progress for this long. */\nexport const MAX_NO_DRAIN_GRACE_MS = 30_000;\n\nconst FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS = 1_000;\n\nconst stdoutQueue: string[] = [];\nlet stdoutQueuedBytes = 0;\nlet stdoutBackpressured = false;\nlet stdoutDrainListening = false;\nlet stdoutDrainWaiters: Array<() => void> = [];\nlet noDrainAbortTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction writeToStdout(text: string): boolean {\n\tif (stdoutTakeoverState) {\n\t\treturn stdoutTakeoverState.rawStdoutWrite(text);\n\t}\n\treturn process.stdout.write(text);\n}\n\nfunction requestDrainFlush(): void {\n\tif (stdoutDrainListening) return;\n\tstdoutDrainListening = true;\n\tprocess.stdout.once(\"drain\", () => {\n\t\tstdoutDrainListening = false;\n\t\t// Any drain is forward progress by the consumer: reset the no-drain\n\t\t// abort window. If the backlog is still above the cap after this flush,\n\t\t// the next queued write starts a fresh window.\n\t\tdisarmNoDrainAbort();\n\t\tflushStdoutQueue();\n\t});\n}\n\n/** Start (or keep) the no-drain abort window for an over-cap backlog. */\nfunction armNoDrainAbort(): void {\n\tif (noDrainAbortTimer) return;\n\tconst timer = setTimeout(() => {\n\t\tnoDrainAbortTimer = undefined;\n\t\tabortForStalledConsumer();\n\t}, MAX_NO_DRAIN_GRACE_MS);\n\ttimer.unref();\n\tnoDrainAbortTimer = timer;\n}\n\nfunction disarmNoDrainAbort(): void {\n\tif (!noDrainAbortTimer) return;\n\tclearTimeout(noDrainAbortTimer);\n\tnoDrainAbortTimer = undefined;\n}\n\n/**\n * The consumer exceeded the queue cap and then made no drain progress for the\n * full grace window — treat the output channel as dead and abort loudly\n * instead of holding payloads in memory forever.\n */\nfunction abortForStalledConsumer(): void {\n\tif (stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES) {\n\t\t// The backlog drained back under the cap before the window expired.\n\t\treturn;\n\t}\n\tconst diagnostic =\n\t\t`Fatal: stdout write queue exceeded ${MAX_QUEUED_STDOUT_BYTES} bytes with no drain progress ` +\n\t\t`for ${MAX_NO_DRAIN_GRACE_MS} ms. The consumer of this process's stdout is not reading; ` +\n\t\t\"refusing unbounded memory growth. Aborting.\\n\";\n\tlet exiting = false;\n\tconst exit = (): void => {\n\t\tif (exiting) return;\n\t\texiting = true;\n\t\tclearTimeout(forceExit);\n\t\tprocess.exit(1);\n\t};\n\tconst forceExit = setTimeout(exit, FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS);\n\tforceExit.unref();\n\tprocess.stderr.write(diagnostic, exit);\n}\n\nfunction flushStdoutQueue(): void {\n\tstdoutBackpressured = false;\n\twhile (stdoutQueue.length > 0) {\n\t\tconst next = stdoutQueue.shift() as string;\n\t\tstdoutQueuedBytes -= Buffer.byteLength(next);\n\t\t// A false return means the stream accepted the chunk but its buffer is\n\t\t// full again — stop writing and wait for the next drain.\n\t\tif (!writeToStdout(next)) {\n\t\t\tstdoutBackpressured = true;\n\t\t\trequestDrainFlush();\n\t\t\treturn;\n\t\t}\n\t}\n\tif (stdoutDrainWaiters.length > 0) {\n\t\tconst waiters = stdoutDrainWaiters;\n\t\tstdoutDrainWaiters = [];\n\t\tfor (const resolve of waiters) resolve();\n\t}\n}\n\nfunction enqueueStdout(text: string): void {\n\tconst bytes = Buffer.byteLength(text);\n\t// Bound accumulated ordinary backlog, but allow one legitimate protocol frame\n\t// larger than the cap (for example, a complete dashboard snapshot). Once that\n\t// oversized frame is queued, any subsequent write still counts against the cap.\n\tconst exceedsAggregateCap = stdoutQueuedBytes + bytes > MAX_QUEUED_STDOUT_BYTES;\n\tconst isSingleOversizedFrame = bytes > MAX_QUEUED_STDOUT_BYTES && stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES;\n\tif (exceedsAggregateCap && !isSingleOversizedFrame) {\n\t\t// Over the cap: keep queuing (this process has no other output channel)\n\t\t// and start the no-drain abort window. A slow-but-alive consumer — for\n\t\t// example a dashboard synchronously decoding multi-MiB image lines —\n\t\t// makes drain progress before the window expires and is not killed\n\t\t// mid-turn (issue 495); a dead one is.\n\t\tarmNoDrainAbort();\n\t}\n\tstdoutQueue.push(text);\n\tstdoutQueuedBytes += bytes;\n}\n\nexport function writeRawStdout(text: string): void {\n\t// Queue behind any backpressured/queued writes to preserve ordering.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tenqueueStdout(text);\n\t\treturn;\n\t}\n\tif (!writeToStdout(text)) {\n\t\tstdoutBackpressured = true;\n\t\trequestDrainFlush();\n\t}\n}\n\nexport async function flushRawStdout(): Promise<void> {\n\t// Wait for any queued output to drain so flushes observe true end-of-stream.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tstdoutDrainWaiters.push(resolve);\n\t\t});\n\t}\n\n\tif (stdoutTakeoverState) {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tstdoutTakeoverState?.rawStdoutWrite(\"\", (err) => {\n\t\t\t\tif (err) reject(err);\n\t\t\t\telse resolve();\n\t\t\t});\n\t\t});\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tprocess.stdout.write(\"\", (err) => {\n\t\t\tif (err) reject(err);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n\n/**\n * Test-only: clear all queue state (backlog, byte count, backpressure flag,\n * drain waiters, and the no-drain abort timer) so tests start from a clean\n * process-global slate.\n */\nexport function resetOutputGuardForTests(): void {\n\tstdoutQueue.length = 0;\n\tstdoutQueuedBytes = 0;\n\tstdoutBackpressured = false;\n\tstdoutDrainWaiters.length = 0;\n\tdisarmNoDrainAbort();\n}\n"]}
|
|
@@ -36,18 +36,24 @@ export function isStdoutTakenOver() {
|
|
|
36
36
|
// output queue up unboundedly inside the process, which is what produced the
|
|
37
37
|
// multi-thousand-event end-of-response bursts in issue 448. While the stream
|
|
38
38
|
// is backpressured we queue subsequent writes and flush them in order on
|
|
39
|
-
// "drain". The queue is byte-capped:
|
|
40
|
-
//
|
|
41
|
-
//
|
|
39
|
+
// "drain". The queue is byte-capped: legitimate multi-MiB bursts (for example
|
|
40
|
+
// dashboard turns with several inline images, issue 495) can push the backlog
|
|
41
|
+
// past the cap while the consumer is merely slow, so the cap is a grace
|
|
42
|
+
// trigger, not an immediate kill — we only abort when the consumer makes no
|
|
43
|
+
// drain progress for the grace window, which means the output channel is dead
|
|
44
|
+
// or hopelessly behind. That keeps us from growing memory without bound.
|
|
42
45
|
// ---------------------------------------------------------------------------
|
|
43
|
-
/** Maximum aggregate bytes allowed for ordinary queued writes before
|
|
46
|
+
/** Maximum aggregate bytes allowed for ordinary queued writes before the no-drain window starts. */
|
|
44
47
|
export const MAX_QUEUED_STDOUT_BYTES = 16 * 1024 * 1024; // 16 MiB
|
|
48
|
+
/** Abort if the queue stays above the cap with no drain progress for this long. */
|
|
49
|
+
export const MAX_NO_DRAIN_GRACE_MS = 30_000;
|
|
45
50
|
const FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS = 1_000;
|
|
46
51
|
const stdoutQueue = [];
|
|
47
52
|
let stdoutQueuedBytes = 0;
|
|
48
53
|
let stdoutBackpressured = false;
|
|
49
54
|
let stdoutDrainListening = false;
|
|
50
55
|
let stdoutDrainWaiters = [];
|
|
56
|
+
let noDrainAbortTimer;
|
|
51
57
|
function writeToStdout(text) {
|
|
52
58
|
if (stdoutTakeoverState) {
|
|
53
59
|
return stdoutTakeoverState.rawStdoutWrite(text);
|
|
@@ -60,9 +66,55 @@ function requestDrainFlush() {
|
|
|
60
66
|
stdoutDrainListening = true;
|
|
61
67
|
process.stdout.once("drain", () => {
|
|
62
68
|
stdoutDrainListening = false;
|
|
69
|
+
// Any drain is forward progress by the consumer: reset the no-drain
|
|
70
|
+
// abort window. If the backlog is still above the cap after this flush,
|
|
71
|
+
// the next queued write starts a fresh window.
|
|
72
|
+
disarmNoDrainAbort();
|
|
63
73
|
flushStdoutQueue();
|
|
64
74
|
});
|
|
65
75
|
}
|
|
76
|
+
/** Start (or keep) the no-drain abort window for an over-cap backlog. */
|
|
77
|
+
function armNoDrainAbort() {
|
|
78
|
+
if (noDrainAbortTimer)
|
|
79
|
+
return;
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
noDrainAbortTimer = undefined;
|
|
82
|
+
abortForStalledConsumer();
|
|
83
|
+
}, MAX_NO_DRAIN_GRACE_MS);
|
|
84
|
+
timer.unref();
|
|
85
|
+
noDrainAbortTimer = timer;
|
|
86
|
+
}
|
|
87
|
+
function disarmNoDrainAbort() {
|
|
88
|
+
if (!noDrainAbortTimer)
|
|
89
|
+
return;
|
|
90
|
+
clearTimeout(noDrainAbortTimer);
|
|
91
|
+
noDrainAbortTimer = undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The consumer exceeded the queue cap and then made no drain progress for the
|
|
95
|
+
* full grace window — treat the output channel as dead and abort loudly
|
|
96
|
+
* instead of holding payloads in memory forever.
|
|
97
|
+
*/
|
|
98
|
+
function abortForStalledConsumer() {
|
|
99
|
+
if (stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES) {
|
|
100
|
+
// The backlog drained back under the cap before the window expired.
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const diagnostic = `Fatal: stdout write queue exceeded ${MAX_QUEUED_STDOUT_BYTES} bytes with no drain progress ` +
|
|
104
|
+
`for ${MAX_NO_DRAIN_GRACE_MS} ms. The consumer of this process's stdout is not reading; ` +
|
|
105
|
+
"refusing unbounded memory growth. Aborting.\n";
|
|
106
|
+
let exiting = false;
|
|
107
|
+
const exit = () => {
|
|
108
|
+
if (exiting)
|
|
109
|
+
return;
|
|
110
|
+
exiting = true;
|
|
111
|
+
clearTimeout(forceExit);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
};
|
|
114
|
+
const forceExit = setTimeout(exit, FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS);
|
|
115
|
+
forceExit.unref();
|
|
116
|
+
process.stderr.write(diagnostic, exit);
|
|
117
|
+
}
|
|
66
118
|
function flushStdoutQueue() {
|
|
67
119
|
stdoutBackpressured = false;
|
|
68
120
|
while (stdoutQueue.length > 0) {
|
|
@@ -87,25 +139,16 @@ function enqueueStdout(text) {
|
|
|
87
139
|
const bytes = Buffer.byteLength(text);
|
|
88
140
|
// Bound accumulated ordinary backlog, but allow one legitimate protocol frame
|
|
89
141
|
// larger than the cap (for example, a complete dashboard snapshot). Once that
|
|
90
|
-
// oversized frame is queued, any subsequent write still
|
|
142
|
+
// oversized frame is queued, any subsequent write still counts against the cap.
|
|
91
143
|
const exceedsAggregateCap = stdoutQueuedBytes + bytes > MAX_QUEUED_STDOUT_BYTES;
|
|
92
144
|
const isSingleOversizedFrame = bytes > MAX_QUEUED_STDOUT_BYTES && stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES;
|
|
93
145
|
if (exceedsAggregateCap && !isSingleOversizedFrame) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return;
|
|
101
|
-
exiting = true;
|
|
102
|
-
clearTimeout(forceExit);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
};
|
|
105
|
-
const forceExit = setTimeout(exit, FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS);
|
|
106
|
-
forceExit.unref();
|
|
107
|
-
process.stderr.write(diagnostic, exit);
|
|
108
|
-
return;
|
|
146
|
+
// Over the cap: keep queuing (this process has no other output channel)
|
|
147
|
+
// and start the no-drain abort window. A slow-but-alive consumer — for
|
|
148
|
+
// example a dashboard synchronously decoding multi-MiB image lines —
|
|
149
|
+
// makes drain progress before the window expires and is not killed
|
|
150
|
+
// mid-turn (issue 495); a dead one is.
|
|
151
|
+
armNoDrainAbort();
|
|
109
152
|
}
|
|
110
153
|
stdoutQueue.push(text);
|
|
111
154
|
stdoutQueuedBytes += bytes;
|
|
@@ -148,4 +191,16 @@ export async function flushRawStdout() {
|
|
|
148
191
|
});
|
|
149
192
|
});
|
|
150
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Test-only: clear all queue state (backlog, byte count, backpressure flag,
|
|
196
|
+
* drain waiters, and the no-drain abort timer) so tests start from a clean
|
|
197
|
+
* process-global slate.
|
|
198
|
+
*/
|
|
199
|
+
export function resetOutputGuardForTests() {
|
|
200
|
+
stdoutQueue.length = 0;
|
|
201
|
+
stdoutQueuedBytes = 0;
|
|
202
|
+
stdoutBackpressured = false;
|
|
203
|
+
stdoutDrainWaiters.length = 0;
|
|
204
|
+
disarmNoDrainAbort();
|
|
205
|
+
}
|
|
151
206
|
//# sourceMappingURL=output-guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-guard.js","sourceRoot":"","sources":["../../src/core/output-guard.ts"],"names":[],"mappings":"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","sourcesContent":["interface StdoutTakeoverState {\n\trawStdoutWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\trawStderrWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\toriginalStdoutWrite: typeof process.stdout.write;\n}\n\nlet stdoutTakeoverState: StdoutTakeoverState | undefined;\n\nexport function takeOverStdout(): void {\n\tif (stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tconst rawStdoutWrite = process.stdout.write.bind(process.stdout) as StdoutTakeoverState[\"rawStdoutWrite\"];\n\tconst rawStderrWrite = process.stderr.write.bind(process.stderr) as StdoutTakeoverState[\"rawStderrWrite\"];\n\tconst originalStdoutWrite = process.stdout.write;\n\n\tprocess.stdout.write = ((\n\t\tchunk: string | Uint8Array,\n\t\tencodingOrCallback?: BufferEncoding | ((error?: Error | null) => void),\n\t\tcallback?: (error?: Error | null) => void,\n\t): boolean => {\n\t\tif (typeof encodingOrCallback === \"function\") {\n\t\t\treturn rawStderrWrite(String(chunk), encodingOrCallback);\n\t\t}\n\t\treturn rawStderrWrite(String(chunk), callback);\n\t}) as typeof process.stdout.write;\n\n\tstdoutTakeoverState = {\n\t\trawStdoutWrite,\n\t\trawStderrWrite,\n\t\toriginalStdoutWrite,\n\t};\n}\n\nexport function restoreStdout(): void {\n\tif (!stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tprocess.stdout.write = stdoutTakeoverState.originalStdoutWrite;\n\tstdoutTakeoverState = undefined;\n}\n\nexport function isStdoutTakenOver(): boolean {\n\treturn stdoutTakeoverState !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Backpressure-aware bounded write queue\n//\n// stream.write() returns false when the stream's internal buffer is full\n// (backpressure). Ignoring that signal during high-rate event streaming lets\n// output queue up unboundedly inside the process, which is what produced the\n// multi-thousand-event end-of-response bursts in issue 448. While the stream\n// is backpressured we queue subsequent writes and flush them in order on\n// \"drain\". The queue is byte-capped: a consumer that stalls beyond the cap\n// means this process's primary output channel is dead or hopelessly behind,\n// so we fail loudly instead of growing memory without bound.\n// ---------------------------------------------------------------------------\n\n/** Maximum aggregate bytes allowed for ordinary queued writes before aborting. */\nexport const MAX_QUEUED_STDOUT_BYTES = 16 * 1024 * 1024; // 16 MiB\n\nconst FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS = 1_000;\n\nconst stdoutQueue: string[] = [];\nlet stdoutQueuedBytes = 0;\nlet stdoutBackpressured = false;\nlet stdoutDrainListening = false;\nlet stdoutDrainWaiters: Array<() => void> = [];\n\nfunction writeToStdout(text: string): boolean {\n\tif (stdoutTakeoverState) {\n\t\treturn stdoutTakeoverState.rawStdoutWrite(text);\n\t}\n\treturn process.stdout.write(text);\n}\n\nfunction requestDrainFlush(): void {\n\tif (stdoutDrainListening) return;\n\tstdoutDrainListening = true;\n\tprocess.stdout.once(\"drain\", () => {\n\t\tstdoutDrainListening = false;\n\t\tflushStdoutQueue();\n\t});\n}\n\nfunction flushStdoutQueue(): void {\n\tstdoutBackpressured = false;\n\twhile (stdoutQueue.length > 0) {\n\t\tconst next = stdoutQueue.shift() as string;\n\t\tstdoutQueuedBytes -= Buffer.byteLength(next);\n\t\t// A false return means the stream accepted the chunk but its buffer is\n\t\t// full again — stop writing and wait for the next drain.\n\t\tif (!writeToStdout(next)) {\n\t\t\tstdoutBackpressured = true;\n\t\t\trequestDrainFlush();\n\t\t\treturn;\n\t\t}\n\t}\n\tif (stdoutDrainWaiters.length > 0) {\n\t\tconst waiters = stdoutDrainWaiters;\n\t\tstdoutDrainWaiters = [];\n\t\tfor (const resolve of waiters) resolve();\n\t}\n}\n\nfunction enqueueStdout(text: string): void {\n\tconst bytes = Buffer.byteLength(text);\n\t// Bound accumulated ordinary backlog, but allow one legitimate protocol frame\n\t// larger than the cap (for example, a complete dashboard snapshot). Once that\n\t// oversized frame is queued, any subsequent write still fails the cap check.\n\tconst exceedsAggregateCap = stdoutQueuedBytes + bytes > MAX_QUEUED_STDOUT_BYTES;\n\tconst isSingleOversizedFrame = bytes > MAX_QUEUED_STDOUT_BYTES && stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES;\n\tif (exceedsAggregateCap && !isSingleOversizedFrame) {\n\t\tconst diagnostic =\n\t\t\t`Fatal: stdout write queue exceeded ${MAX_QUEUED_STDOUT_BYTES} bytes while the stream was ` +\n\t\t\t\"backpressured. The consumer of this process's stdout is not reading; refusing \" +\n\t\t\t\"unbounded memory growth. Aborting.\\n\";\n\t\tlet exiting = false;\n\t\tconst exit = (): void => {\n\t\t\tif (exiting) return;\n\t\t\texiting = true;\n\t\t\tclearTimeout(forceExit);\n\t\t\tprocess.exit(1);\n\t\t};\n\t\tconst forceExit = setTimeout(exit, FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS);\n\t\tforceExit.unref();\n\t\tprocess.stderr.write(diagnostic, exit);\n\t\treturn;\n\t}\n\tstdoutQueue.push(text);\n\tstdoutQueuedBytes += bytes;\n}\n\nexport function writeRawStdout(text: string): void {\n\t// Queue behind any backpressured/queued writes to preserve ordering.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tenqueueStdout(text);\n\t\treturn;\n\t}\n\tif (!writeToStdout(text)) {\n\t\tstdoutBackpressured = true;\n\t\trequestDrainFlush();\n\t}\n}\n\nexport async function flushRawStdout(): Promise<void> {\n\t// Wait for any queued output to drain so flushes observe true end-of-stream.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tstdoutDrainWaiters.push(resolve);\n\t\t});\n\t}\n\n\tif (stdoutTakeoverState) {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tstdoutTakeoverState?.rawStdoutWrite(\"\", (err) => {\n\t\t\t\tif (err) reject(err);\n\t\t\t\telse resolve();\n\t\t\t});\n\t\t});\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tprocess.stdout.write(\"\", (err) => {\n\t\t\tif (err) reject(err);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"output-guard.js","sourceRoot":"","sources":["../../src/core/output-guard.ts"],"names":[],"mappings":"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","sourcesContent":["interface StdoutTakeoverState {\n\trawStdoutWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\trawStderrWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;\n\toriginalStdoutWrite: typeof process.stdout.write;\n}\n\nlet stdoutTakeoverState: StdoutTakeoverState | undefined;\n\nexport function takeOverStdout(): void {\n\tif (stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tconst rawStdoutWrite = process.stdout.write.bind(process.stdout) as StdoutTakeoverState[\"rawStdoutWrite\"];\n\tconst rawStderrWrite = process.stderr.write.bind(process.stderr) as StdoutTakeoverState[\"rawStderrWrite\"];\n\tconst originalStdoutWrite = process.stdout.write;\n\n\tprocess.stdout.write = ((\n\t\tchunk: string | Uint8Array,\n\t\tencodingOrCallback?: BufferEncoding | ((error?: Error | null) => void),\n\t\tcallback?: (error?: Error | null) => void,\n\t): boolean => {\n\t\tif (typeof encodingOrCallback === \"function\") {\n\t\t\treturn rawStderrWrite(String(chunk), encodingOrCallback);\n\t\t}\n\t\treturn rawStderrWrite(String(chunk), callback);\n\t}) as typeof process.stdout.write;\n\n\tstdoutTakeoverState = {\n\t\trawStdoutWrite,\n\t\trawStderrWrite,\n\t\toriginalStdoutWrite,\n\t};\n}\n\nexport function restoreStdout(): void {\n\tif (!stdoutTakeoverState) {\n\t\treturn;\n\t}\n\n\tprocess.stdout.write = stdoutTakeoverState.originalStdoutWrite;\n\tstdoutTakeoverState = undefined;\n}\n\nexport function isStdoutTakenOver(): boolean {\n\treturn stdoutTakeoverState !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Backpressure-aware bounded write queue\n//\n// stream.write() returns false when the stream's internal buffer is full\n// (backpressure). Ignoring that signal during high-rate event streaming lets\n// output queue up unboundedly inside the process, which is what produced the\n// multi-thousand-event end-of-response bursts in issue 448. While the stream\n// is backpressured we queue subsequent writes and flush them in order on\n// \"drain\". The queue is byte-capped: legitimate multi-MiB bursts (for example\n// dashboard turns with several inline images, issue 495) can push the backlog\n// past the cap while the consumer is merely slow, so the cap is a grace\n// trigger, not an immediate kill — we only abort when the consumer makes no\n// drain progress for the grace window, which means the output channel is dead\n// or hopelessly behind. That keeps us from growing memory without bound.\n// ---------------------------------------------------------------------------\n\n/** Maximum aggregate bytes allowed for ordinary queued writes before the no-drain window starts. */\nexport const MAX_QUEUED_STDOUT_BYTES = 16 * 1024 * 1024; // 16 MiB\n\n/** Abort if the queue stays above the cap with no drain progress for this long. */\nexport const MAX_NO_DRAIN_GRACE_MS = 30_000;\n\nconst FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS = 1_000;\n\nconst stdoutQueue: string[] = [];\nlet stdoutQueuedBytes = 0;\nlet stdoutBackpressured = false;\nlet stdoutDrainListening = false;\nlet stdoutDrainWaiters: Array<() => void> = [];\nlet noDrainAbortTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction writeToStdout(text: string): boolean {\n\tif (stdoutTakeoverState) {\n\t\treturn stdoutTakeoverState.rawStdoutWrite(text);\n\t}\n\treturn process.stdout.write(text);\n}\n\nfunction requestDrainFlush(): void {\n\tif (stdoutDrainListening) return;\n\tstdoutDrainListening = true;\n\tprocess.stdout.once(\"drain\", () => {\n\t\tstdoutDrainListening = false;\n\t\t// Any drain is forward progress by the consumer: reset the no-drain\n\t\t// abort window. If the backlog is still above the cap after this flush,\n\t\t// the next queued write starts a fresh window.\n\t\tdisarmNoDrainAbort();\n\t\tflushStdoutQueue();\n\t});\n}\n\n/** Start (or keep) the no-drain abort window for an over-cap backlog. */\nfunction armNoDrainAbort(): void {\n\tif (noDrainAbortTimer) return;\n\tconst timer = setTimeout(() => {\n\t\tnoDrainAbortTimer = undefined;\n\t\tabortForStalledConsumer();\n\t}, MAX_NO_DRAIN_GRACE_MS);\n\ttimer.unref();\n\tnoDrainAbortTimer = timer;\n}\n\nfunction disarmNoDrainAbort(): void {\n\tif (!noDrainAbortTimer) return;\n\tclearTimeout(noDrainAbortTimer);\n\tnoDrainAbortTimer = undefined;\n}\n\n/**\n * The consumer exceeded the queue cap and then made no drain progress for the\n * full grace window — treat the output channel as dead and abort loudly\n * instead of holding payloads in memory forever.\n */\nfunction abortForStalledConsumer(): void {\n\tif (stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES) {\n\t\t// The backlog drained back under the cap before the window expired.\n\t\treturn;\n\t}\n\tconst diagnostic =\n\t\t`Fatal: stdout write queue exceeded ${MAX_QUEUED_STDOUT_BYTES} bytes with no drain progress ` +\n\t\t`for ${MAX_NO_DRAIN_GRACE_MS} ms. The consumer of this process's stdout is not reading; ` +\n\t\t\"refusing unbounded memory growth. Aborting.\\n\";\n\tlet exiting = false;\n\tconst exit = (): void => {\n\t\tif (exiting) return;\n\t\texiting = true;\n\t\tclearTimeout(forceExit);\n\t\tprocess.exit(1);\n\t};\n\tconst forceExit = setTimeout(exit, FATAL_DIAGNOSTIC_FLUSH_TIMEOUT_MS);\n\tforceExit.unref();\n\tprocess.stderr.write(diagnostic, exit);\n}\n\nfunction flushStdoutQueue(): void {\n\tstdoutBackpressured = false;\n\twhile (stdoutQueue.length > 0) {\n\t\tconst next = stdoutQueue.shift() as string;\n\t\tstdoutQueuedBytes -= Buffer.byteLength(next);\n\t\t// A false return means the stream accepted the chunk but its buffer is\n\t\t// full again — stop writing and wait for the next drain.\n\t\tif (!writeToStdout(next)) {\n\t\t\tstdoutBackpressured = true;\n\t\t\trequestDrainFlush();\n\t\t\treturn;\n\t\t}\n\t}\n\tif (stdoutDrainWaiters.length > 0) {\n\t\tconst waiters = stdoutDrainWaiters;\n\t\tstdoutDrainWaiters = [];\n\t\tfor (const resolve of waiters) resolve();\n\t}\n}\n\nfunction enqueueStdout(text: string): void {\n\tconst bytes = Buffer.byteLength(text);\n\t// Bound accumulated ordinary backlog, but allow one legitimate protocol frame\n\t// larger than the cap (for example, a complete dashboard snapshot). Once that\n\t// oversized frame is queued, any subsequent write still counts against the cap.\n\tconst exceedsAggregateCap = stdoutQueuedBytes + bytes > MAX_QUEUED_STDOUT_BYTES;\n\tconst isSingleOversizedFrame = bytes > MAX_QUEUED_STDOUT_BYTES && stdoutQueuedBytes <= MAX_QUEUED_STDOUT_BYTES;\n\tif (exceedsAggregateCap && !isSingleOversizedFrame) {\n\t\t// Over the cap: keep queuing (this process has no other output channel)\n\t\t// and start the no-drain abort window. A slow-but-alive consumer — for\n\t\t// example a dashboard synchronously decoding multi-MiB image lines —\n\t\t// makes drain progress before the window expires and is not killed\n\t\t// mid-turn (issue 495); a dead one is.\n\t\tarmNoDrainAbort();\n\t}\n\tstdoutQueue.push(text);\n\tstdoutQueuedBytes += bytes;\n}\n\nexport function writeRawStdout(text: string): void {\n\t// Queue behind any backpressured/queued writes to preserve ordering.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tenqueueStdout(text);\n\t\treturn;\n\t}\n\tif (!writeToStdout(text)) {\n\t\tstdoutBackpressured = true;\n\t\trequestDrainFlush();\n\t}\n}\n\nexport async function flushRawStdout(): Promise<void> {\n\t// Wait for any queued output to drain so flushes observe true end-of-stream.\n\tif (stdoutBackpressured || stdoutQueue.length > 0) {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tstdoutDrainWaiters.push(resolve);\n\t\t});\n\t}\n\n\tif (stdoutTakeoverState) {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tstdoutTakeoverState?.rawStdoutWrite(\"\", (err) => {\n\t\t\t\tif (err) reject(err);\n\t\t\t\telse resolve();\n\t\t\t});\n\t\t});\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tprocess.stdout.write(\"\", (err) => {\n\t\t\tif (err) reject(err);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n\n/**\n * Test-only: clear all queue state (backlog, byte count, backpressure flag,\n * drain waiters, and the no-drain abort timer) so tests start from a clean\n * process-global slate.\n */\nexport function resetOutputGuardForTests(): void {\n\tstdoutQueue.length = 0;\n\tstdoutQueuedBytes = 0;\n\tstdoutBackpressured = false;\n\tstdoutDrainWaiters.length = 0;\n\tdisarmNoDrainAbort();\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import type { Transport } from "@dreb/ai";
|
|
|
2
2
|
import type { ContextTrustPolicy } from "./context-trust.js";
|
|
3
3
|
export interface CompactionSettings {
|
|
4
4
|
enabled?: boolean;
|
|
5
|
+
continueAfterAutoCompaction?: boolean;
|
|
5
6
|
reserveTokens?: number;
|
|
6
7
|
keepRecentTokens?: number;
|
|
7
8
|
}
|
|
@@ -246,6 +247,8 @@ export declare class SettingsManager {
|
|
|
246
247
|
setTransport(transport: TransportSetting): void;
|
|
247
248
|
getCompactionEnabled(): boolean;
|
|
248
249
|
setCompactionEnabled(enabled: boolean): void;
|
|
250
|
+
getContinueAfterAutoCompaction(): boolean;
|
|
251
|
+
setContinueAfterAutoCompaction(enabled: boolean): void;
|
|
249
252
|
/**
|
|
250
253
|
* Read the global-only lazy-context policy. File-backed managers re-read this small
|
|
251
254
|
* policy on each decision so independently running main/subagent processes observe
|
|
@@ -274,6 +277,7 @@ export declare class SettingsManager {
|
|
|
274
277
|
getCompactionKeepRecentTokens(): number;
|
|
275
278
|
getCompactionSettings(): {
|
|
276
279
|
enabled: boolean;
|
|
280
|
+
continueAfterAutoCompaction: boolean;
|
|
277
281
|
reserveTokens: number;
|
|
278
282
|
keepRecentTokens: number;
|
|
279
283
|
};
|