@0xmaxma/claude-gateway 1.7.7 → 1.7.9
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/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +64 -3
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/line-webhook-router.d.ts.map +1 -1
- package/dist/api/line-webhook-router.js +7 -15
- package/dist/api/line-webhook-router.js.map +1 -1
- package/dist/api/slack-client.d.ts +25 -2
- package/dist/api/slack-client.d.ts.map +1 -1
- package/dist/api/slack-client.js +102 -0
- package/dist/api/slack-client.js.map +1 -1
- package/dist/api/slack-webhook-router.d.ts +15 -1
- package/dist/api/slack-webhook-router.d.ts.map +1 -1
- package/dist/api/slack-webhook-router.js +128 -1
- package/dist/api/slack-webhook-router.js.map +1 -1
- package/dist/session/compactor.d.ts +32 -2
- package/dist/session/compactor.d.ts.map +1 -1
- package/dist/session/compactor.js +134 -19
- package/dist/session/compactor.js.map +1 -1
- package/dist/session/process.d.ts +14 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +85 -3
- package/dist/session/process.js.map +1 -1
- package/dist/shared/image-sniff.d.ts +15 -0
- package/dist/shared/image-sniff.d.ts.map +1 -0
- package/dist/shared/image-sniff.js +29 -0
- package/dist/shared/image-sniff.js.map +1 -0
- package/dist/shell/claude-pty-shell.js +13 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/mcp/tools/discord/module.ts +1 -2
- package/mcp/tools/shared/limits.ts +14 -0
- package/mcp/tools/slack/module.ts +66 -4
- package/mcp/tools/telegram/module.ts +1 -1
- package/mcp/tools/telegram/receiver-server.ts +1 -1
- package/package.json +1 -1
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.SessionCompactor = exports.NotEnoughMessagesError = void 0;
|
|
37
|
+
exports.defaultSpawnClaude = defaultSpawnClaude;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const child_process_1 = require("child_process");
|
|
@@ -55,15 +56,83 @@ const MERGE_SUMMARIES_PROMPT = 'These are summaries of sequential parts of a con
|
|
|
55
56
|
const SINGLE_SUMMARY_INSTRUCTION = 'Summarize this conversation concisely, preserving key facts, decisions, context the assistant should remember, and any open questions or unfinished tasks.';
|
|
56
57
|
// System prompt prepended to every claude CLI summarization call
|
|
57
58
|
const COMPACTOR_SYSTEM_PROMPT = 'You are a conversation archiver. Your ONLY task is to produce a concise summary of the conversation transcript below. Do NOT respond to the conversation. Do NOT ask questions. Output ONLY the summary.';
|
|
59
|
+
// Max concurrent `claude --print` chunk-summary calls in flight at once. Bounded
|
|
60
|
+
// (not "all N chunks at once") to cap worst-case concurrent model calls/memory,
|
|
61
|
+
// while still cutting sequential wall time roughly by this factor on large sessions.
|
|
62
|
+
const CHUNK_CONCURRENCY = 4;
|
|
63
|
+
// Attempts per chunk (1 initial + retries) before degrading to a truncated raw
|
|
64
|
+
// excerpt instead of failing the whole compact job over one bad chunk.
|
|
65
|
+
const CHUNK_MAX_ATTEMPTS = 2;
|
|
66
|
+
// Raw-excerpt length used when a chunk exhausts its summarization attempts.
|
|
67
|
+
const DEGRADED_EXCERPT_CHARS = 2000;
|
|
68
|
+
// Hard ceiling on a single `claude --print` summarization spawn — mirrors the
|
|
69
|
+
// prior spawnSync timeout, but non-blocking (see defaultSpawnClaude below).
|
|
70
|
+
const SUMMARY_TIMEOUT_MS = 300000;
|
|
58
71
|
// Rough token estimate: ~4 chars per token
|
|
59
72
|
function estimateTokens(messages) {
|
|
60
73
|
return Math.round(messages.reduce((acc, m) => acc + m.content.length, 0) / 4);
|
|
61
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Async `claude` CLI spawn. Replaces the prior `spawnSync` call: `spawnSync`
|
|
77
|
+
* blocks the daemon's single event loop for the full duration of the call — and
|
|
78
|
+
* every agent shares one process, so one compaction call froze ALL agents/
|
|
79
|
+
* channels until it returned (up to SUMMARY_TIMEOUT_MS, x N chunks sequentially
|
|
80
|
+
* on large sessions). `spawn` yields the event loop while the child runs.
|
|
81
|
+
* Mirrors the sync `{status, stdout, stderr, error}` contract so callers'
|
|
82
|
+
* existing error handling is unchanged.
|
|
83
|
+
*/
|
|
84
|
+
function defaultSpawnClaude(bin, args, input) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
let settled = false;
|
|
87
|
+
let stdout = '';
|
|
88
|
+
let stderr = '';
|
|
89
|
+
const settle = (r) => {
|
|
90
|
+
if (settled)
|
|
91
|
+
return;
|
|
92
|
+
settled = true;
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
resolve(r);
|
|
95
|
+
};
|
|
96
|
+
const timer = setTimeout(() => {
|
|
97
|
+
try {
|
|
98
|
+
child.kill('SIGKILL');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* already gone */
|
|
102
|
+
}
|
|
103
|
+
settle({ status: null, stdout: '', stderr, error: new Error(`claude CLI timed out after ${SUMMARY_TIMEOUT_MS}ms`) });
|
|
104
|
+
}, SUMMARY_TIMEOUT_MS);
|
|
105
|
+
const child = (0, child_process_1.spawn)(bin, args, {
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
env: { ...process.env, PATH: (0, claude_bin_1.pathWithNativeBin)() },
|
|
108
|
+
});
|
|
109
|
+
child.stdout?.on('data', (d) => {
|
|
110
|
+
stdout += String(d);
|
|
111
|
+
});
|
|
112
|
+
child.stderr?.on('data', (d) => {
|
|
113
|
+
stderr += String(d);
|
|
114
|
+
});
|
|
115
|
+
child.on('error', (err) => settle({ status: null, stdout: '', stderr, error: err }));
|
|
116
|
+
child.on('close', (code) => settle({ status: code, stdout, stderr }));
|
|
117
|
+
// stdin emits its own async 'error' (e.g. EPIPE if the child exits before we
|
|
118
|
+
// finish writing) separate from the child's 'error' event — must be handled
|
|
119
|
+
// or an unhandled stream error crashes the daemon.
|
|
120
|
+
child.stdin?.on('error', () => { });
|
|
121
|
+
try {
|
|
122
|
+
child.stdin?.write(input);
|
|
123
|
+
child.stdin?.end();
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
settle({ status: null, stdout: '', stderr, error: err });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
62
130
|
class SessionCompactor {
|
|
63
|
-
constructor(sessionStore) {
|
|
131
|
+
constructor(sessionStore, spawnClaude = defaultSpawnClaude) {
|
|
64
132
|
this.sessionStore = sessionStore;
|
|
133
|
+
this.spawnClaude = spawnClaude;
|
|
65
134
|
}
|
|
66
|
-
async compact(agentId, chatId, sessionId, model, contextWindow, channel = 'telegram') {
|
|
135
|
+
async compact(agentId, chatId, sessionId, model, contextWindow, channel = 'telegram', onProgress) {
|
|
67
136
|
// Load current history
|
|
68
137
|
const messages = await this.sessionStore.loadTelegramSession(agentId, chatId, sessionId, channel);
|
|
69
138
|
if (messages.length < 5) {
|
|
@@ -83,7 +152,7 @@ class SessionCompactor {
|
|
|
83
152
|
const historyText = toSummarize
|
|
84
153
|
.map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
|
|
85
154
|
.join('\n\n');
|
|
86
|
-
const summaryText = await this.summarizeWithChunking(historyText, model);
|
|
155
|
+
const summaryText = await this.summarizeWithChunking(historyText, model, onProgress);
|
|
87
156
|
const compacted = [
|
|
88
157
|
{ role: 'system', content: `[Conversation Summary]\n${summaryText}`, ts: Date.now() },
|
|
89
158
|
...tail,
|
|
@@ -97,21 +166,72 @@ class SessionCompactor {
|
|
|
97
166
|
const contextPctAfter = Math.round((afterTokens / contextWindow) * 100);
|
|
98
167
|
return { beforeMessages, afterMessages, beforeTokens, afterTokens, reductionPct, contextPctBefore, contextPctAfter };
|
|
99
168
|
}
|
|
100
|
-
async summarizeWithChunking(historyText, model) {
|
|
101
|
-
// If small enough, summarize directly
|
|
169
|
+
async summarizeWithChunking(historyText, model, onProgress) {
|
|
170
|
+
// If small enough, summarize directly. No chunking → no chunk to retry/degrade
|
|
171
|
+
// against, so a failure here still fails the compact job (original behavior).
|
|
102
172
|
if (historyText.length <= CHUNK_CHARS) {
|
|
103
173
|
return this.callClaudeForSummary(historyText, model);
|
|
104
174
|
}
|
|
105
|
-
// Split into chunks and summarize
|
|
175
|
+
// Split into chunks and summarize with bounded concurrency; a single bad
|
|
176
|
+
// chunk degrades to a raw excerpt instead of failing the whole job.
|
|
106
177
|
const chunks = this.splitIntoChunks(historyText, CHUNK_CHARS);
|
|
107
|
-
const chunkSummaries =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
// Merge chunk summaries into final summary
|
|
178
|
+
const chunkSummaries = await this.summarizeChunksConcurrently(chunks, model, onProgress);
|
|
179
|
+
// Merge chunk summaries into final summary. If the merge call itself can't
|
|
180
|
+
// be completed, fall back to the concatenated per-part summaries rather than
|
|
181
|
+
// losing the whole compaction — still far smaller than the raw history.
|
|
113
182
|
const mergedText = chunkSummaries.join('\n\n');
|
|
114
|
-
return this.
|
|
183
|
+
return this.mergeChunkSummaries(mergedText, model);
|
|
184
|
+
}
|
|
185
|
+
/** Bounded worker pool (mirrors the pattern in apps/installer.ts restoreRunningApps): at
|
|
186
|
+
* most CHUNK_CONCURRENCY `claude --print` calls in flight; results land by index so
|
|
187
|
+
* output order matches chunk order regardless of completion order. */
|
|
188
|
+
async summarizeChunksConcurrently(chunks, model, onProgress) {
|
|
189
|
+
const results = new Array(chunks.length);
|
|
190
|
+
let completed = 0;
|
|
191
|
+
let cursor = 0;
|
|
192
|
+
const worker = async () => {
|
|
193
|
+
while (cursor < chunks.length) {
|
|
194
|
+
const i = cursor++;
|
|
195
|
+
results[i] = await this.summarizeChunkWithFallback(chunks[i], model, i, chunks.length);
|
|
196
|
+
completed++;
|
|
197
|
+
onProgress?.(completed, chunks.length);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const poolSize = Math.min(CHUNK_CONCURRENCY, chunks.length);
|
|
201
|
+
await Promise.all(Array.from({ length: poolSize }, () => worker()));
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
/** Summarize one chunk; after CHUNK_MAX_ATTEMPTS failures, degrade to a truncated raw
|
|
205
|
+
* excerpt instead of throwing, so one bad chunk can't fail the whole compact job. */
|
|
206
|
+
async summarizeChunkWithFallback(chunk, model, index, total) {
|
|
207
|
+
const instruction = `This is part ${index + 1} of ${total} of a longer conversation. Summarize this segment concisely.`;
|
|
208
|
+
let lastErr;
|
|
209
|
+
for (let attempt = 1; attempt <= CHUNK_MAX_ATTEMPTS; attempt++) {
|
|
210
|
+
try {
|
|
211
|
+
const summary = await this.callClaudeForSummary(chunk, model, instruction);
|
|
212
|
+
return `[Part ${index + 1}/${total}]\n${summary}`;
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
lastErr = err;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const excerpt = chunk.length > DEGRADED_EXCERPT_CHARS ? `${chunk.slice(0, DEGRADED_EXCERPT_CHARS)}…(truncated)` : chunk;
|
|
219
|
+
console.error(`[SessionCompactor] Chunk ${index + 1}/${total} summarization failed after ${CHUNK_MAX_ATTEMPTS} attempts (${lastErr?.message ?? 'unknown error'}); using truncated raw excerpt.`);
|
|
220
|
+
return `[Part ${index + 1}/${total} — summarization failed after ${CHUNK_MAX_ATTEMPTS} attempts (${lastErr?.message ?? 'unknown error'}); showing truncated raw excerpt]\n${excerpt}`;
|
|
221
|
+
}
|
|
222
|
+
/** Merge per-chunk summaries into one; degrade to the unmerged concatenation on repeated failure. */
|
|
223
|
+
async mergeChunkSummaries(mergedText, model) {
|
|
224
|
+
let lastErr;
|
|
225
|
+
for (let attempt = 1; attempt <= CHUNK_MAX_ATTEMPTS; attempt++) {
|
|
226
|
+
try {
|
|
227
|
+
return await this.callClaudeForSummary(mergedText, model, MERGE_SUMMARIES_PROMPT);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
lastErr = err;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
console.error(`[SessionCompactor] Merge-summaries call failed after ${CHUNK_MAX_ATTEMPTS} attempts (${lastErr?.message ?? 'unknown error'}); using unmerged per-chunk summaries.`);
|
|
234
|
+
return mergedText;
|
|
115
235
|
}
|
|
116
236
|
splitIntoChunks(text, maxChars) {
|
|
117
237
|
const chunks = [];
|
|
@@ -139,12 +259,7 @@ class SessionCompactor {
|
|
|
139
259
|
// spawn after the native-installer migration.
|
|
140
260
|
const claudeBinRaw = process.env.CLAUDE_BIN ?? (0, claude_bin_1.resolveClaudeBin)().bin;
|
|
141
261
|
const [claudeBin, ...claudeBinArgs] = claudeBinRaw.split(' ');
|
|
142
|
-
const result =
|
|
143
|
-
input: prompt,
|
|
144
|
-
encoding: 'utf-8',
|
|
145
|
-
timeout: 300000,
|
|
146
|
-
env: { ...process.env, PATH: (0, claude_bin_1.pathWithNativeBin)() },
|
|
147
|
-
});
|
|
262
|
+
const result = await this.spawnClaude(claudeBin, [...claudeBinArgs, '--print', '--model', model], prompt);
|
|
148
263
|
if (result.error) {
|
|
149
264
|
throw new Error(`claude CLI error: ${result.error.message}`);
|
|
150
265
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compactor.js","sourceRoot":"","sources":["../../src/session/compactor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"compactor.js","sourceRoot":"","sources":["../../src/session/compactor.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,9 +32,12 @@ export declare class SessionProcess extends EventEmitter {
|
|
|
32
32
|
private process;
|
|
33
33
|
private stopping;
|
|
34
34
|
private restartCount;
|
|
35
|
+
private lastSpawnAt;
|
|
36
|
+
private _restartScheduled;
|
|
35
37
|
private restartRequested;
|
|
36
38
|
private _processing;
|
|
37
39
|
private _pendingRestart;
|
|
40
|
+
private _exited;
|
|
38
41
|
private interruptRequested;
|
|
39
42
|
private restartWatcher;
|
|
40
43
|
private readonly sessionStore;
|
|
@@ -159,6 +162,17 @@ export declare class SessionProcess extends EventEmitter {
|
|
|
159
162
|
* the restart instead (see the deferredRestartReady handler in runner.ts).
|
|
160
163
|
*/
|
|
161
164
|
hasPendingRestart(): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* True while a crash-triggered auto-restart is in flight — armed by
|
|
167
|
+
* scheduleRestart() on child death and cleared once the replacement child
|
|
168
|
+
* attaches ('restarted'), the respawn fails ('restartFailed'), or the restart
|
|
169
|
+
* is abandoned because stop() raced in. Distinct from hasPendingRestart(),
|
|
170
|
+
* which tracks a *deferred* (graceful, turn-boundary) restart. The runner
|
|
171
|
+
* reads this to tell "a restart is coming, wait for it" apart from "dead with
|
|
172
|
+
* nothing coming, respawn a fresh session" — the latter would otherwise block
|
|
173
|
+
* on waitForSessionRestart's full timeout and wedge the caller. See #371.
|
|
174
|
+
*/
|
|
175
|
+
isRestartScheduled(): boolean;
|
|
162
176
|
touch(): void;
|
|
163
177
|
isIdle(idleMs: number): boolean;
|
|
164
178
|
isRunning(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/session/process.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAKtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGtD,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAYvC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAkBvC,eAAO,MAAM,yBAAyB,oDAAoD,CAAC;AAE3F;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAMR;
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/session/process.ts"],"names":[],"mappings":"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"}
|
package/dist/session/process.js
CHANGED
|
@@ -86,6 +86,14 @@ function resolveMaxHistoryMessages(agentMax, globalMax) {
|
|
|
86
86
|
}
|
|
87
87
|
const AUTO_RESTART_DELAY_MS = 5000;
|
|
88
88
|
const MAX_RESTARTS = 3;
|
|
89
|
+
// A respawned child that stays alive at least this long counts as a healthy run
|
|
90
|
+
// rather than a member of a crash loop: on its eventual death the crash budget
|
|
91
|
+
// (restartCount) is reset so sporadic crashes spread across a long-lived
|
|
92
|
+
// session don't slowly accumulate toward a false permanent `failed`. Must be
|
|
93
|
+
// comfortably longer than the crash-loop window (AUTO_RESTART_DELAY_MS *
|
|
94
|
+
// MAX_RESTARTS = 15s) so a genuine tight crash loop never survives it and the
|
|
95
|
+
// MAX_RESTARTS backstop still fires. See issue #371.
|
|
96
|
+
const RESTART_COUNT_RESET_MS = 60000;
|
|
89
97
|
// Bound how many times a single session may auto-respawn to recover from a
|
|
90
98
|
// corrupted thinking block, so a recovery that never helps can't loop forever.
|
|
91
99
|
const MAX_THINKING_RECOVERIES = 2;
|
|
@@ -133,9 +141,31 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
133
141
|
this.process = null;
|
|
134
142
|
this.stopping = false;
|
|
135
143
|
this.restartCount = 0;
|
|
144
|
+
// Wall-clock time the current (or most recent) child was spawned. Used by the
|
|
145
|
+
// exit handler to measure how long that child survived, so a death after
|
|
146
|
+
// sustained healthy uptime resets the crash budget instead of counting toward
|
|
147
|
+
// MAX_RESTARTS. See RESTART_COUNT_RESET_MS.
|
|
148
|
+
this.lastSpawnAt = 0;
|
|
149
|
+
// True while a crash-triggered respawn is in flight — from the moment
|
|
150
|
+
// scheduleRestart() arms the AUTO_RESTART_DELAY_MS timer until the replacement
|
|
151
|
+
// child has attached (emit 'restarted') or the respawn failed/was abandoned.
|
|
152
|
+
// The runner reads this via isRestartScheduled() to distinguish "a restart is
|
|
153
|
+
// coming, wait for it" from "dead with nothing coming, respawn fresh" — the
|
|
154
|
+
// latter would otherwise wedge on waitForSessionRestart's full timeout. #371.
|
|
155
|
+
this._restartScheduled = false;
|
|
136
156
|
this.restartRequested = false;
|
|
137
157
|
this._processing = false;
|
|
138
158
|
this._pendingRestart = false;
|
|
159
|
+
// True once we have OBSERVED the child actually exit (the 'exit' handler
|
|
160
|
+
// fired). Distinct from Node's ChildProcess.killed, which flips true the
|
|
161
|
+
// instant ANY signal is delivered — including a graceful /stop SIGINT that
|
|
162
|
+
// the pty-shell traps and SURVIVES (it forwards ESC to interrupt the TUI turn
|
|
163
|
+
// and keeps running). Liveness checks must use _exited, never .killed: a
|
|
164
|
+
// survived-SIGINT child is still alive, and treating it as dead wedges the
|
|
165
|
+
// session forever (isRunning() → false → the next turn waits on a restart
|
|
166
|
+
// that never comes; see runner.ts waitForSessionRestart). Reset to false on
|
|
167
|
+
// every fresh spawn.
|
|
168
|
+
this._exited = false;
|
|
139
169
|
// Set by interrupt() right before SIGINT, cleared at the start of the next
|
|
140
170
|
// sendMessage(). Lets a caller's process-exit handler tell a /stop-triggered
|
|
141
171
|
// exit (CLI's SIGINT handler fell through to default termination instead of
|
|
@@ -658,6 +688,10 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
658
688
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
659
689
|
});
|
|
660
690
|
this.process = proc;
|
|
691
|
+
// Fresh child is alive: clear any exit observed for a prior process (e.g.
|
|
692
|
+
// after an auto-restart), so isRunning()/interrupt() see it as live.
|
|
693
|
+
this._exited = false;
|
|
694
|
+
this.lastSpawnAt = Date.now();
|
|
661
695
|
// Send initial prompt only for Telegram/Discord sessions.
|
|
662
696
|
// API sessions receive the first message directly via sendApiMessage(),
|
|
663
697
|
// so we cannot send an activation prompt here — it would race with the
|
|
@@ -956,6 +990,7 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
956
990
|
if (ptyStreamSocketPath)
|
|
957
991
|
pty_stream_registry_1.ptyStreamRegistry.close(ptyStreamSocketPath);
|
|
958
992
|
this.process = null;
|
|
993
|
+
this._exited = true;
|
|
959
994
|
// Notify listeners that the underlying subprocess died. The runner relies
|
|
960
995
|
// on this to tear down per-chat typing/processing state when a session is
|
|
961
996
|
// stopped or restarted mid-turn (without a final result/session_idle).
|
|
@@ -964,6 +999,16 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
964
999
|
// rmSync(force)), so emitting on every child exit — including auto-restart
|
|
965
1000
|
// — is safe.
|
|
966
1001
|
this.emit('exit', code, signal);
|
|
1002
|
+
// A child that ran healthily for a sustained period before dying is not
|
|
1003
|
+
// part of a crash loop — reset the crash budget so an occasional crash
|
|
1004
|
+
// over a long-lived session doesn't slowly accumulate toward MAX_RESTARTS
|
|
1005
|
+
// and trip a false permanent `failed` (which would tear down a healthy
|
|
1006
|
+
// session and, for an API session, previously wedge it — see #371). A
|
|
1007
|
+
// rapid crash loop never survives RESTART_COUNT_RESET_MS, so the
|
|
1008
|
+
// MAX_RESTARTS backstop is preserved.
|
|
1009
|
+
if (this.lastSpawnAt && Date.now() - this.lastSpawnAt >= RESTART_COUNT_RESET_MS) {
|
|
1010
|
+
this.restartCount = 0;
|
|
1011
|
+
}
|
|
967
1012
|
if (!this.stopping)
|
|
968
1013
|
this.scheduleRestart();
|
|
969
1014
|
});
|
|
@@ -999,6 +1044,10 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
999
1044
|
return;
|
|
1000
1045
|
}
|
|
1001
1046
|
this.restartCount++;
|
|
1047
|
+
// A crash-triggered respawn is now committed (timer armed below). Mark it in
|
|
1048
|
+
// flight so waitForSessionRestart knows to wait for the replacement child
|
|
1049
|
+
// rather than fail fast. Cleared once the respawn settles (or is skipped).
|
|
1050
|
+
this._restartScheduled = true;
|
|
1002
1051
|
this.logger.warn(`Scheduling session restart in ${AUTO_RESTART_DELAY_MS}ms`, {
|
|
1003
1052
|
attempt: this.restartCount,
|
|
1004
1053
|
});
|
|
@@ -1010,12 +1059,26 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
1010
1059
|
// sendMessage() will no longer silently no-op. Emitted on every
|
|
1011
1060
|
// successful crash-triggered respawn — cheap, and nothing currently
|
|
1012
1061
|
// listens outside that one call site.
|
|
1013
|
-
.then(() =>
|
|
1062
|
+
.then(() => {
|
|
1063
|
+
this._restartScheduled = false;
|
|
1064
|
+
this.emit('restarted');
|
|
1065
|
+
})
|
|
1014
1066
|
.catch(err => {
|
|
1067
|
+
this._restartScheduled = false;
|
|
1015
1068
|
this.logger.error('restart failed', { error: err.message });
|
|
1016
1069
|
this.emit('restartFailed', err);
|
|
1017
1070
|
});
|
|
1018
1071
|
}
|
|
1072
|
+
else {
|
|
1073
|
+
// stop() won the race before the timer fired — no respawn will happen.
|
|
1074
|
+
// Wake any waiter blocked on this restart (waitForSessionRestart) so it
|
|
1075
|
+
// rejects immediately instead of hanging for the full timeout: the
|
|
1076
|
+
// restarted/restartFailed/failed event it waits on would otherwise never
|
|
1077
|
+
// fire. Clearing the flag first also lets a fresh getOrSpawnSession call
|
|
1078
|
+
// take the respawn-fresh path. See #371.
|
|
1079
|
+
this._restartScheduled = false;
|
|
1080
|
+
this.emit('restartFailed', new Error('Session restart abandoned: session stopping'));
|
|
1081
|
+
}
|
|
1019
1082
|
}, AUTO_RESTART_DELAY_MS);
|
|
1020
1083
|
}
|
|
1021
1084
|
/**
|
|
@@ -1183,7 +1246,10 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
1183
1246
|
}
|
|
1184
1247
|
}
|
|
1185
1248
|
interrupt() {
|
|
1186
|
-
|
|
1249
|
+
// .killed is NOT liveness — it flips true on the FIRST SIGINT and stays
|
|
1250
|
+
// true, so a prior /stop would make every later interrupt() no-op even on a
|
|
1251
|
+
// live, actively-processing turn. Gate on _exited (child actually gone).
|
|
1252
|
+
if (!this.process || this._exited)
|
|
1187
1253
|
return false;
|
|
1188
1254
|
if (!this._processing)
|
|
1189
1255
|
return false;
|
|
@@ -1221,6 +1287,19 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
1221
1287
|
hasPendingRestart() {
|
|
1222
1288
|
return this._pendingRestart;
|
|
1223
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* True while a crash-triggered auto-restart is in flight — armed by
|
|
1292
|
+
* scheduleRestart() on child death and cleared once the replacement child
|
|
1293
|
+
* attaches ('restarted'), the respawn fails ('restartFailed'), or the restart
|
|
1294
|
+
* is abandoned because stop() raced in. Distinct from hasPendingRestart(),
|
|
1295
|
+
* which tracks a *deferred* (graceful, turn-boundary) restart. The runner
|
|
1296
|
+
* reads this to tell "a restart is coming, wait for it" apart from "dead with
|
|
1297
|
+
* nothing coming, respawn a fresh session" — the latter would otherwise block
|
|
1298
|
+
* on waitForSessionRestart's full timeout and wedge the caller. See #371.
|
|
1299
|
+
*/
|
|
1300
|
+
isRestartScheduled() {
|
|
1301
|
+
return this._restartScheduled;
|
|
1302
|
+
}
|
|
1224
1303
|
touch() {
|
|
1225
1304
|
this.lastActivityAt = Date.now();
|
|
1226
1305
|
}
|
|
@@ -1228,7 +1307,10 @@ class SessionProcess extends events_1.EventEmitter {
|
|
|
1228
1307
|
return Date.now() - this.lastActivityAt > idleMs;
|
|
1229
1308
|
}
|
|
1230
1309
|
isRunning() {
|
|
1231
|
-
|
|
1310
|
+
// Use _exited (child's 'exit' observed), NOT .killed — .killed is true after
|
|
1311
|
+
// any signal send, including a /stop SIGINT the pty-shell survives, which
|
|
1312
|
+
// would falsely report a healthy interrupted session as not-running.
|
|
1313
|
+
return this.process !== null && !this._exited;
|
|
1232
1314
|
}
|
|
1233
1315
|
async stop() {
|
|
1234
1316
|
this.stopping = true;
|