@0xmaxma/claude-gateway 1.6.9 → 1.7.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 +149 -20
- package/config.template.json +59 -1
- package/dist/agent/dreaming/accept.d.ts +43 -0
- package/dist/agent/dreaming/accept.d.ts.map +1 -0
- package/dist/agent/dreaming/accept.js +230 -0
- package/dist/agent/dreaming/accept.js.map +1 -0
- package/dist/agent/dreaming/applier.d.ts +77 -0
- package/dist/agent/dreaming/applier.d.ts.map +1 -0
- package/dist/agent/dreaming/applier.js +406 -0
- package/dist/agent/dreaming/applier.js.map +1 -0
- package/dist/agent/dreaming/audit.d.ts +27 -0
- package/dist/agent/dreaming/audit.d.ts.map +1 -0
- package/dist/agent/dreaming/audit.js +100 -0
- package/dist/agent/dreaming/audit.js.map +1 -0
- package/dist/agent/dreaming/compactor.d.ts +66 -0
- package/dist/agent/dreaming/compactor.d.ts.map +1 -0
- package/dist/agent/dreaming/compactor.js +306 -0
- package/dist/agent/dreaming/compactor.js.map +1 -0
- package/dist/agent/dreaming/config.d.ts +19 -0
- package/dist/agent/dreaming/config.d.ts.map +1 -0
- package/dist/agent/dreaming/config.js +107 -0
- package/dist/agent/dreaming/config.js.map +1 -0
- package/dist/agent/dreaming/episodic.d.ts +42 -0
- package/dist/agent/dreaming/episodic.d.ts.map +1 -0
- package/dist/agent/dreaming/episodic.js +116 -0
- package/dist/agent/dreaming/episodic.js.map +1 -0
- package/dist/agent/dreaming/gather.d.ts +31 -0
- package/dist/agent/dreaming/gather.d.ts.map +1 -0
- package/dist/agent/dreaming/gather.js +55 -0
- package/dist/agent/dreaming/gather.js.map +1 -0
- package/dist/agent/dreaming/index.d.ts +87 -0
- package/dist/agent/dreaming/index.d.ts.map +1 -0
- package/dist/agent/dreaming/index.js +377 -0
- package/dist/agent/dreaming/index.js.map +1 -0
- package/dist/agent/dreaming/migrate-cli.d.ts +17 -0
- package/dist/agent/dreaming/migrate-cli.d.ts.map +1 -0
- package/dist/agent/dreaming/migrate-cli.js +42 -0
- package/dist/agent/dreaming/migrate-cli.js.map +1 -0
- package/dist/agent/dreaming/migrate.d.ts +72 -0
- package/dist/agent/dreaming/migrate.d.ts.map +1 -0
- package/dist/agent/dreaming/migrate.js +374 -0
- package/dist/agent/dreaming/migrate.js.map +1 -0
- package/dist/agent/dreaming/report.d.ts +39 -0
- package/dist/agent/dreaming/report.d.ts.map +1 -0
- package/dist/agent/dreaming/report.js +121 -0
- package/dist/agent/dreaming/report.js.map +1 -0
- package/dist/agent/dreaming/reviewer.d.ts +43 -0
- package/dist/agent/dreaming/reviewer.d.ts.map +1 -0
- package/dist/agent/dreaming/reviewer.js +249 -0
- package/dist/agent/dreaming/reviewer.js.map +1 -0
- package/dist/agent/dreaming/staleness.d.ts +68 -0
- package/dist/agent/dreaming/staleness.d.ts.map +1 -0
- package/dist/agent/dreaming/staleness.js +281 -0
- package/dist/agent/dreaming/staleness.js.map +1 -0
- package/dist/agent/dreaming/types.d.ts +130 -0
- package/dist/agent/dreaming/types.d.ts.map +1 -0
- package/dist/agent/dreaming/types.js +10 -0
- package/dist/agent/dreaming/types.js.map +1 -0
- package/dist/agent/knowledge/archive-db.d.ts +114 -0
- package/dist/agent/knowledge/archive-db.d.ts.map +1 -0
- package/dist/agent/knowledge/archive-db.js +474 -0
- package/dist/agent/knowledge/archive-db.js.map +1 -0
- package/dist/agent/knowledge/chunk.d.ts +21 -0
- package/dist/agent/knowledge/chunk.d.ts.map +1 -0
- package/dist/agent/knowledge/chunk.js +67 -0
- package/dist/agent/knowledge/chunk.js.map +1 -0
- package/dist/agent/knowledge/config.d.ts +20 -0
- package/dist/agent/knowledge/config.d.ts.map +1 -0
- package/dist/agent/knowledge/config.js +143 -0
- package/dist/agent/knowledge/config.js.map +1 -0
- package/dist/agent/knowledge/graph-demo.d.ts +25 -0
- package/dist/agent/knowledge/graph-demo.d.ts.map +1 -0
- package/dist/agent/knowledge/graph-demo.js +132 -0
- package/dist/agent/knowledge/graph-demo.js.map +1 -0
- package/dist/agent/knowledge/index.d.ts +24 -0
- package/dist/agent/knowledge/index.d.ts.map +1 -0
- package/dist/agent/knowledge/index.js +50 -0
- package/dist/agent/knowledge/index.js.map +1 -0
- package/dist/agent/knowledge/indexer.d.ts +34 -0
- package/dist/agent/knowledge/indexer.d.ts.map +1 -0
- package/dist/agent/knowledge/indexer.js +271 -0
- package/dist/agent/knowledge/indexer.js.map +1 -0
- package/dist/agent/knowledge/lifecycle.d.ts +91 -0
- package/dist/agent/knowledge/lifecycle.d.ts.map +1 -0
- package/dist/agent/knowledge/lifecycle.js +205 -0
- package/dist/agent/knowledge/lifecycle.js.map +1 -0
- package/dist/agent/knowledge/provenance.d.ts +15 -0
- package/dist/agent/knowledge/provenance.d.ts.map +1 -0
- package/dist/agent/knowledge/provenance.js +37 -0
- package/dist/agent/knowledge/provenance.js.map +1 -0
- package/dist/agent/knowledge/reindex-cli.d.ts +13 -0
- package/dist/agent/knowledge/reindex-cli.d.ts.map +1 -0
- package/dist/agent/knowledge/reindex-cli.js +53 -0
- package/dist/agent/knowledge/reindex-cli.js.map +1 -0
- package/dist/agent/knowledge/reindex-spawn.d.ts +30 -0
- package/dist/agent/knowledge/reindex-spawn.d.ts.map +1 -0
- package/dist/agent/knowledge/reindex-spawn.js +97 -0
- package/dist/agent/knowledge/reindex-spawn.js.map +1 -0
- package/dist/agent/knowledge/shared-promote.d.ts +18 -0
- package/dist/agent/knowledge/shared-promote.d.ts.map +1 -0
- package/dist/agent/knowledge/shared-promote.js +29 -0
- package/dist/agent/knowledge/shared-promote.js.map +1 -0
- package/dist/agent/knowledge/shared-writer.d.ts +24 -0
- package/dist/agent/knowledge/shared-writer.d.ts.map +1 -0
- package/dist/agent/knowledge/shared-writer.js +97 -0
- package/dist/agent/knowledge/shared-writer.js.map +1 -0
- package/dist/agent/knowledge/types.d.ts +83 -0
- package/dist/agent/knowledge/types.d.ts.map +1 -0
- package/dist/agent/knowledge/types.js +12 -0
- package/dist/agent/knowledge/types.js.map +1 -0
- package/dist/agent/knowledge/wiki.d.ts +107 -0
- package/dist/agent/knowledge/wiki.d.ts.map +1 -0
- package/dist/agent/knowledge/wiki.js +415 -0
- package/dist/agent/knowledge/wiki.js.map +1 -0
- package/dist/agent/memory-metrics.d.ts +48 -0
- package/dist/agent/memory-metrics.d.ts.map +1 -0
- package/dist/agent/memory-metrics.js +156 -0
- package/dist/agent/memory-metrics.js.map +1 -0
- package/dist/agent/runner.d.ts +34 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +223 -19
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/skill-learning/config.d.ts +18 -0
- package/dist/agent/skill-learning/config.d.ts.map +1 -0
- package/dist/agent/skill-learning/config.js +71 -0
- package/dist/agent/skill-learning/config.js.map +1 -0
- package/dist/agent/skill-learning/curator.d.ts +45 -0
- package/dist/agent/skill-learning/curator.d.ts.map +1 -0
- package/dist/agent/skill-learning/curator.js +171 -0
- package/dist/agent/skill-learning/curator.js.map +1 -0
- package/dist/agent/skill-learning/index.d.ts +79 -0
- package/dist/agent/skill-learning/index.d.ts.map +1 -0
- package/dist/agent/skill-learning/index.js +308 -0
- package/dist/agent/skill-learning/index.js.map +1 -0
- package/dist/agent/skill-learning/metrics.d.ts +13 -0
- package/dist/agent/skill-learning/metrics.d.ts.map +1 -0
- package/dist/agent/skill-learning/metrics.js +109 -0
- package/dist/agent/skill-learning/metrics.js.map +1 -0
- package/dist/agent/skill-learning/notifier.d.ts +111 -0
- package/dist/agent/skill-learning/notifier.d.ts.map +1 -0
- package/dist/agent/skill-learning/notifier.js +398 -0
- package/dist/agent/skill-learning/notifier.js.map +1 -0
- package/dist/agent/skill-learning/reviewer.d.ts +61 -0
- package/dist/agent/skill-learning/reviewer.d.ts.map +1 -0
- package/dist/agent/skill-learning/reviewer.js +271 -0
- package/dist/agent/skill-learning/reviewer.js.map +1 -0
- package/dist/agent/skill-learning/telemetry.d.ts +22 -0
- package/dist/agent/skill-learning/telemetry.d.ts.map +1 -0
- package/dist/agent/skill-learning/telemetry.js +90 -0
- package/dist/agent/skill-learning/telemetry.js.map +1 -0
- package/dist/agent/skill-learning/trigger.d.ts +23 -0
- package/dist/agent/skill-learning/trigger.d.ts.map +1 -0
- package/dist/agent/skill-learning/trigger.js +44 -0
- package/dist/agent/skill-learning/trigger.js.map +1 -0
- package/dist/agent/skill-learning/types.d.ts +165 -0
- package/dist/agent/skill-learning/types.d.ts.map +1 -0
- package/dist/agent/skill-learning/types.js +11 -0
- package/dist/agent/skill-learning/types.js.map +1 -0
- package/dist/agent/skill-learning/writer.d.ts +37 -0
- package/dist/agent/skill-learning/writer.d.ts.map +1 -0
- package/dist/agent/skill-learning/writer.js +223 -0
- package/dist/agent/skill-learning/writer.js.map +1 -0
- package/dist/agent/workspace-loader.d.ts +83 -6
- package/dist/agent/workspace-loader.d.ts.map +1 -1
- package/dist/agent/workspace-loader.js +207 -14
- package/dist/agent/workspace-loader.js.map +1 -1
- package/dist/api/gateway-router.d.ts +2 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +293 -6
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/skills-router.d.ts +2 -2
- package/dist/api/skills-router.d.ts.map +1 -1
- package/dist/api/skills-router.js +61 -1
- package/dist/api/skills-router.js.map +1 -1
- package/dist/apps/agent-manager.d.ts +7 -0
- package/dist/apps/agent-manager.d.ts.map +1 -1
- package/dist/apps/agent-manager.js +13 -1
- package/dist/apps/agent-manager.js.map +1 -1
- package/dist/config/migrator.d.ts +2 -0
- package/dist/config/migrator.d.ts.map +1 -1
- package/dist/config/migrator.js +72 -1
- package/dist/config/migrator.js.map +1 -1
- package/dist/history/cleanup.d.ts +7 -0
- package/dist/history/cleanup.d.ts.map +1 -1
- package/dist/history/cleanup.js +27 -0
- package/dist/history/cleanup.js.map +1 -1
- package/dist/history/db.d.ts +65 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +261 -0
- package/dist/history/db.js.map +1 -1
- package/dist/index.js +122 -20
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +20 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +128 -4
- package/dist/session/process.js.map +1 -1
- package/dist/skills/loader.d.ts +8 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +29 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/web-ui.d.ts.map +1 -1
- package/dist/ui/web-ui.js +1126 -3
- package/dist/ui/web-ui.js.map +1 -1
- package/mcp/package.json +2 -1
- package/mcp/server.ts +8 -2
- package/mcp/tools/image/module.ts +58 -7
- package/mcp/tools/memory/archive-reader.test.ts +212 -0
- package/mcp/tools/memory/archive-reader.ts +253 -0
- package/mcp/tools/memory/module.ts +152 -0
- package/mcp/tools/memory/retrieval-recorder.ts +66 -0
- package/mcp/tools/skills/module.ts +28 -0
- package/mcp/types.ts +9 -3
- package/package.json +1 -1
- package/resource/claude_gateway.svg +1038 -112
package/dist/agent/runner.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AgentConfig, GatewayConfig, Logger, ModelConfig, StreamEvent, ApiAttach
|
|
|
3
3
|
import { type SkillRegistry } from '../skills';
|
|
4
4
|
import { type CliChannel } from '../cli-viewer/pairing-store';
|
|
5
5
|
import { HistoryDB } from '../history/db';
|
|
6
|
+
import type { SkillLearningManager } from './skill-learning';
|
|
6
7
|
export declare const MAX_IMAGE_SIZE_BYTES: number;
|
|
7
8
|
export declare const DEFAULT_MODELS: ModelConfig[];
|
|
8
9
|
export declare const CHANNEL_COALESCE_WINDOW_MS = 1200;
|
|
@@ -57,6 +58,7 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
57
58
|
private readonly sessionHistory;
|
|
58
59
|
private readonly apiChatIds;
|
|
59
60
|
private skillRegistry;
|
|
61
|
+
private skillLearning?;
|
|
60
62
|
private readonly configPath;
|
|
61
63
|
private readonly historyDb;
|
|
62
64
|
private readonly agentsBaseDir;
|
|
@@ -68,6 +70,9 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
68
70
|
*/
|
|
69
71
|
setSkillRegistry(registry: SkillRegistry): void;
|
|
70
72
|
getSkillRegistry(): SkillRegistry;
|
|
73
|
+
/** Wire the skill-learning manager (planning-62). Optional; unset = feature off. */
|
|
74
|
+
setSkillLearning(manager: SkillLearningManager | undefined): void;
|
|
75
|
+
getSkillLearning(): SkillLearningManager | undefined;
|
|
71
76
|
get workspacePath(): string;
|
|
72
77
|
/**
|
|
73
78
|
* Bind a local HTTP server that receives POST /channel from TelegramReceiver.
|
|
@@ -183,6 +188,16 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
183
188
|
*/
|
|
184
189
|
static remapImageParamsRefs(p: ImageParams | undefined, stagedPaths: readonly string[] | undefined, promotedPaths: readonly string[] | undefined): ImageParams | undefined;
|
|
185
190
|
private static buildChannelXml;
|
|
191
|
+
/**
|
|
192
|
+
* Await a SessionProcess's in-flight crash-triggered auto-restart
|
|
193
|
+
* (scheduleRestart's setTimeout → spawnProcess) instead of handing back a
|
|
194
|
+
* process whose sendMessage() will silently no-op. Resolves once the new
|
|
195
|
+
* child is attached ('restarted'); rejects on a definitive restart failure
|
|
196
|
+
* ('restartFailed', or 'failed' once MAX_RESTARTS is exhausted) or, as a
|
|
197
|
+
* last resort, on SESSION_RESTART_WAIT_TIMEOUT_MS so a caller is never left
|
|
198
|
+
* hanging past that bound.
|
|
199
|
+
*/
|
|
200
|
+
private waitForSessionRestart;
|
|
186
201
|
private getOrSpawnSession;
|
|
187
202
|
private spawnSession;
|
|
188
203
|
static isApiBuiltinCommand(content: string): boolean;
|
|
@@ -289,10 +304,28 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
289
304
|
* restarted so the change reaches them on their next spawn. The recomposed
|
|
290
305
|
* CLAUDE.md is already on disk, so a skipped busy session picks up the
|
|
291
306
|
* change on its own next natural spawn.
|
|
307
|
+
* @param opts.deferIdle When true, idle sessions are NOT `stop()`ed now.
|
|
308
|
+
* Instead they are added to the runner-level `pendingRestarts` set, so each
|
|
309
|
+
* respawns cleanly on its next message (the same lossless mechanism used for
|
|
310
|
+
* the image-size-threshold restart) rather than being SIGKILLed mid-idle and
|
|
311
|
+
* losing its in-context state. Since CLAUDE.md is frozen-at-spawn, an idle
|
|
312
|
+
* bystander gains nothing from an immediate stop — the recomposed file
|
|
313
|
+
* applies on its next spawn either way. Use for agent-writable/identity and
|
|
314
|
+
* skills changes, where dropping an unrelated idle session is pure downside.
|
|
315
|
+
* NB: idle sessions are deferred via `pendingRestarts`, NOT
|
|
316
|
+
* `proc.markPendingRestart()` — the latter emits `deferredRestartReady`
|
|
317
|
+
* synchronously on an idle process, which stops it immediately (no deferral).
|
|
318
|
+
* @returns Counts of sessions handled: `immediate` (stopped now), `deferred`
|
|
319
|
+
* (armed to restart on next turn/message), `skipped` (left untouched).
|
|
292
320
|
*/
|
|
293
321
|
restartOrDefer(opts?: {
|
|
294
322
|
skipBusy?: boolean;
|
|
295
|
-
|
|
323
|
+
deferIdle?: boolean;
|
|
324
|
+
}): Promise<{
|
|
325
|
+
immediate: number;
|
|
326
|
+
deferred: number;
|
|
327
|
+
skipped: number;
|
|
328
|
+
}>;
|
|
296
329
|
/**
|
|
297
330
|
* Format a timestamp as a human-readable age string (e.g. "5m ago", "2h ago").
|
|
298
331
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAW,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAS7H,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAOvF,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG7F,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -61,6 +61,7 @@ const screen_1 = require("../shell/screen");
|
|
|
61
61
|
const db_1 = require("../history/db");
|
|
62
62
|
const media_store_1 = require("../history/media-store");
|
|
63
63
|
const cleanup_1 = require("../history/cleanup");
|
|
64
|
+
const knowledge_1 = require("./knowledge");
|
|
64
65
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
|
|
65
66
|
const DEFAULT_MAX_CONCURRENT = 20;
|
|
66
67
|
const ANTHROPIC_SOCKET_ERROR = 'socket connection was closed unexpectedly';
|
|
@@ -99,6 +100,11 @@ const MAX_API_IMAGES = 5;
|
|
|
99
100
|
* CLI turn is still producing a result that must reach history, exactly like
|
|
100
101
|
* the client-disconnect path. This cap bounds a genuinely hung turn. */
|
|
101
102
|
const API_TIMEOUT_HARD_CAP_EXTRA_MS = 600000;
|
|
103
|
+
// Bound on getOrSpawnSession's wait for a crash-triggered auto-restart to land
|
|
104
|
+
// (session/process.ts's AUTO_RESTART_DELAY_MS is 5s, plus real CLI spawn time
|
|
105
|
+
// observed up to a few seconds more) — generous, but must not be the long
|
|
106
|
+
// API_TIMEOUT_HARD_CAP_EXTRA_MS chain: this is a pre-turn gap, not a turn.
|
|
107
|
+
const SESSION_RESTART_WAIT_TIMEOUT_MS = 20000;
|
|
102
108
|
// Hard timeout for the one-shot local `claude -p` triage during recovery
|
|
103
109
|
// (Epic #195, Phase 3b). A slow/hung triage collapses to a safe notify-only.
|
|
104
110
|
const RECOVERY_TRIAGE_TIMEOUT_MS = 15000;
|
|
@@ -327,6 +333,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
327
333
|
getSkillRegistry() {
|
|
328
334
|
return this.skillRegistry;
|
|
329
335
|
}
|
|
336
|
+
/** Wire the skill-learning manager (planning-62). Optional; unset = feature off. */
|
|
337
|
+
setSkillLearning(manager) {
|
|
338
|
+
this.skillLearning = manager;
|
|
339
|
+
}
|
|
340
|
+
getSkillLearning() {
|
|
341
|
+
return this.skillLearning;
|
|
342
|
+
}
|
|
330
343
|
get workspacePath() {
|
|
331
344
|
return this.agentConfig.workspace;
|
|
332
345
|
}
|
|
@@ -481,6 +494,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
481
494
|
failureClass: body.failureClass ?? null,
|
|
482
495
|
turnKey: body.turnKey ?? chatId,
|
|
483
496
|
};
|
|
497
|
+
// Skill-learning: an incident-driven recovery attempt on this chat's turn.
|
|
498
|
+
this.skillLearning?.onRecoveryFired(chatId);
|
|
484
499
|
try {
|
|
485
500
|
const result = await (0, recovery_executor_1.runRecovery)(req, {
|
|
486
501
|
autoRecover,
|
|
@@ -952,15 +967,25 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
952
967
|
ts: userTs,
|
|
953
968
|
});
|
|
954
969
|
}
|
|
955
|
-
//
|
|
970
|
+
// Consume a deferred restart before this turn (armed by the image-size
|
|
971
|
+
// threshold, or by a workspace/skills change that deferred this idle
|
|
972
|
+
// session). Guard on isProcessing: this same chatId key can be driven by
|
|
973
|
+
// a web-UI live-view turn (sendMessageToSession) that does NOT hold
|
|
974
|
+
// turnActive, so the session may be mid-stream here. Stopping it would
|
|
975
|
+
// truncate that live turn — instead leave the flag armed and consume it
|
|
976
|
+
// on the next idle turn. In the normal channel flow the session is idle
|
|
977
|
+
// at this point (turnActive serialises channel turns), so the guard is a
|
|
978
|
+
// no-op and the restart proceeds as before.
|
|
956
979
|
if (this.pendingRestarts.has(chatId)) {
|
|
957
980
|
const existingSession = this.sessions.get(chatId);
|
|
958
|
-
if (existingSession) {
|
|
959
|
-
|
|
960
|
-
|
|
981
|
+
if (!existingSession?.isProcessing) {
|
|
982
|
+
if (existingSession) {
|
|
983
|
+
await existingSession.stop();
|
|
984
|
+
this.sessions.delete(chatId);
|
|
985
|
+
}
|
|
986
|
+
this.pendingRestarts.delete(chatId);
|
|
987
|
+
this.imageSizePerChat.delete(chatId);
|
|
961
988
|
}
|
|
962
|
-
this.pendingRestarts.delete(chatId);
|
|
963
|
-
this.imageSizePerChat.delete(chatId);
|
|
964
989
|
}
|
|
965
990
|
// Route to session process (map key = chatId, actual sessionId passed separately)
|
|
966
991
|
// Channel sessions use agent-level model (not per-session)
|
|
@@ -968,11 +993,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
968
993
|
// Merge buffered messages into one turn: concat channel XML blocks, append
|
|
969
994
|
// any skill context, and accumulate image paths for size tracking.
|
|
970
995
|
const blocks = [];
|
|
996
|
+
const invokedSkills = [];
|
|
971
997
|
for (const entry of entries) {
|
|
972
998
|
let channelXml = AgentRunner.buildChannelXml(entry);
|
|
973
999
|
const skillInvocation = (0, skills_1.detectSkillCommand)(entry.content ?? '', this.skillRegistry);
|
|
974
1000
|
if (skillInvocation) {
|
|
975
1001
|
channelXml += (0, skills_1.formatSkillContext)(skillInvocation);
|
|
1002
|
+
invokedSkills.push(skillInvocation.skillKey);
|
|
976
1003
|
this.logger.info('Skill invoked', {
|
|
977
1004
|
skill: skillInvocation.skillKey,
|
|
978
1005
|
args: skillInvocation.args,
|
|
@@ -998,6 +1025,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
998
1025
|
}
|
|
999
1026
|
session.setProcessing(true);
|
|
1000
1027
|
const turnText = blocks.join('\n');
|
|
1028
|
+
// Skill-learning: mark the start of a user turn (cheap, best-effort).
|
|
1029
|
+
this.skillLearning?.onTurnStart(chatId, sessionId, entries[0]?.content ?? '', invokedSkills);
|
|
1001
1030
|
session.sendMessage(turnText);
|
|
1002
1031
|
// Remember this turn for the C1 guarded resend (Phase 3b): reset the
|
|
1003
1032
|
// delivered/resent flags so a resend can only fire if this turn stalls
|
|
@@ -1138,6 +1167,37 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1138
1167
|
`message_id="${meta['message_id'] ?? ''}" user="${AgentRunner.escapeXmlAttr(meta['user'] ?? '')}" ` +
|
|
1139
1168
|
`ts="${meta['ts'] ?? new Date().toISOString()}"${optionalAttrs}>${repliedBlock}${params.content ?? ''}</channel>`);
|
|
1140
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Await a SessionProcess's in-flight crash-triggered auto-restart
|
|
1172
|
+
* (scheduleRestart's setTimeout → spawnProcess) instead of handing back a
|
|
1173
|
+
* process whose sendMessage() will silently no-op. Resolves once the new
|
|
1174
|
+
* child is attached ('restarted'); rejects on a definitive restart failure
|
|
1175
|
+
* ('restartFailed', or 'failed' once MAX_RESTARTS is exhausted) or, as a
|
|
1176
|
+
* last resort, on SESSION_RESTART_WAIT_TIMEOUT_MS so a caller is never left
|
|
1177
|
+
* hanging past that bound.
|
|
1178
|
+
*/
|
|
1179
|
+
waitForSessionRestart(proc) {
|
|
1180
|
+
if (proc.isRunning())
|
|
1181
|
+
return Promise.resolve();
|
|
1182
|
+
return new Promise((resolve, reject) => {
|
|
1183
|
+
const timer = setTimeout(() => {
|
|
1184
|
+
cleanup();
|
|
1185
|
+
reject(new Error('Session restart did not complete in time'));
|
|
1186
|
+
}, SESSION_RESTART_WAIT_TIMEOUT_MS);
|
|
1187
|
+
const onRestarted = () => { cleanup(); resolve(); };
|
|
1188
|
+
const onRestartFailed = (err) => { cleanup(); reject(err); };
|
|
1189
|
+
const onFailed = () => { cleanup(); reject(new Error('Session failed permanently (max restarts exceeded)')); };
|
|
1190
|
+
const cleanup = () => {
|
|
1191
|
+
clearTimeout(timer);
|
|
1192
|
+
proc.off('restarted', onRestarted);
|
|
1193
|
+
proc.off('restartFailed', onRestartFailed);
|
|
1194
|
+
proc.off('failed', onFailed);
|
|
1195
|
+
};
|
|
1196
|
+
proc.once('restarted', onRestarted);
|
|
1197
|
+
proc.once('restartFailed', onRestartFailed);
|
|
1198
|
+
proc.once('failed', onFailed);
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1141
1201
|
async getOrSpawnSession(mapKey, // Map lookup key (chatId for telegram/discord, sessionId for API)
|
|
1142
1202
|
source, sessionId, // actual session UUID (only for channel sessions; equals mapKey for API)
|
|
1143
1203
|
modelOverride) {
|
|
@@ -1153,6 +1213,19 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1153
1213
|
return pending;
|
|
1154
1214
|
const existing = this.sessions.get(mapKey);
|
|
1155
1215
|
if (existing) {
|
|
1216
|
+
// The mapped SessionProcess can be mid auto-restart (its child crashed —
|
|
1217
|
+
// see the 'exit'/scheduleRestart cycle in session/process.ts — and the
|
|
1218
|
+
// AUTO_RESTART_DELAY_MS timer hasn't fired the replacement child yet).
|
|
1219
|
+
// existing.sendMessage() silently no-ops while .process is null (only a
|
|
1220
|
+
// warning log, no error, no event), which previously left the caller's
|
|
1221
|
+
// pendingApiSessions entry stuck until the long timeout chain (the same
|
|
1222
|
+
// failure mode this whole area was already patched for — see the
|
|
1223
|
+
// session.on('exit', onExit) fix in sendApiMessage/sendApiMessageStream —
|
|
1224
|
+
// but THIS gap is hit before a turn even starts, so that fix can't see
|
|
1225
|
+
// it). Wait for the pending respawn to actually land before handing the
|
|
1226
|
+
// session back.
|
|
1227
|
+
if (!existing.isRunning())
|
|
1228
|
+
await this.waitForSessionRestart(existing);
|
|
1156
1229
|
// Restart if model changed (including switching back to the agent default)
|
|
1157
1230
|
if (existing.modelOverride !== effectiveOverride) {
|
|
1158
1231
|
this.logger.info('Model changed, restarting session', { mapKey, oldModel: existing.modelOverride, newModel: effectiveOverride ?? agentDefaultModel });
|
|
@@ -1204,6 +1277,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1204
1277
|
// Apply per-session model override (caller already normalized to undefined when == agent default)
|
|
1205
1278
|
if (modelOverride)
|
|
1206
1279
|
proc.modelOverride = modelOverride;
|
|
1280
|
+
// Keep the searchable memory archive (planning-64) fresh for the memory_search
|
|
1281
|
+
// tool. Fire-and-forget, detached, OFF the event loop — never blocks the spawn.
|
|
1282
|
+
(0, knowledge_1.spawnArchiveReindex)(this.agentConfig.workspace, this.agentConfig.knowledge, this.gatewayConfig.gateway.knowledge);
|
|
1207
1283
|
// Per-agent → global → MAX_HISTORY_MESSAGES: the configured cap on how many
|
|
1208
1284
|
// history messages a healthy spawn re-injects. Lets an operator lower the
|
|
1209
1285
|
// context loaded at session start (e.g. 50 → 30) without touching code.
|
|
@@ -1293,6 +1369,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1293
1369
|
const msg = obj['message'];
|
|
1294
1370
|
if (Array.isArray(msg?.content)) {
|
|
1295
1371
|
for (const block of msg.content) {
|
|
1372
|
+
// Skill-learning: count every tool_use this turn (deduped by block
|
|
1373
|
+
// id inside the manager, since cumulative assistant snapshots re-emit
|
|
1374
|
+
// completed blocks). Cheap, best-effort.
|
|
1375
|
+
if (block.type === 'tool_use') {
|
|
1376
|
+
this.skillLearning?.onToolUse(mapKey, block['id']);
|
|
1377
|
+
}
|
|
1296
1378
|
// LINE sends images through its own line_image tool (not the reply
|
|
1297
1379
|
// tool's `files`), so capture those sends into history too — the
|
|
1298
1380
|
// web transcript and the session image catalog key off mediaFiles.
|
|
@@ -1426,6 +1508,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1426
1508
|
}
|
|
1427
1509
|
if (obj['type'] === 'result') {
|
|
1428
1510
|
proc.setProcessing(false);
|
|
1511
|
+
// Skill-learning: on the headless backend `result` is the true
|
|
1512
|
+
// end-of-user-turn (exactly one per message) — persist the turn metric
|
|
1513
|
+
// here. pty-shell fires `result` per sub-turn, so its capture is driven
|
|
1514
|
+
// by `session_idle` (below) to avoid over-counting.
|
|
1515
|
+
if (proc.backend !== 'pty-shell') {
|
|
1516
|
+
this.skillLearning?.onTurnEnd(mapKey, actualSessionId);
|
|
1517
|
+
}
|
|
1429
1518
|
// Gateway turn queue: on the headless backend `result` is the true end
|
|
1430
1519
|
// of the user's turn (exactly one per message), so flush the next
|
|
1431
1520
|
// queued turn here. On pty-shell `result` fires per sub-turn (across
|
|
@@ -1579,6 +1668,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1579
1668
|
if (!proc.hasPendingRestart()) {
|
|
1580
1669
|
this.flushNextTurn(mapKey);
|
|
1581
1670
|
}
|
|
1671
|
+
// Skill-learning: pty-shell session_idle is the true end-of-user-turn.
|
|
1672
|
+
this.skillLearning?.onTurnEnd(mapKey, actualSessionId);
|
|
1582
1673
|
typingDoneTimer = setTimeout(() => {
|
|
1583
1674
|
this.writeTypingDone(mapKey);
|
|
1584
1675
|
typingDoneTimer = null;
|
|
@@ -1591,6 +1682,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1591
1682
|
// Use actualSessionId (captured at spawn time) — NOT getActiveSessionId() —
|
|
1592
1683
|
// so tokens are attributed to the session that owns this process.
|
|
1593
1684
|
proc.on('tokenUsage', async ({ inputTokens, totalTokens }) => {
|
|
1685
|
+
this.skillLearning?.onTokenUsage(mapKey, inputTokens, totalTokens);
|
|
1594
1686
|
try {
|
|
1595
1687
|
const ch = this.channelFor(mapKey);
|
|
1596
1688
|
const index = await this.sessionStore.listSessions(this.agentConfig.id, mapKey, ch);
|
|
@@ -1958,6 +2050,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1958
2050
|
*/
|
|
1959
2051
|
handleRequestTooLarge(mapKey, proc) {
|
|
1960
2052
|
proc.setProcessing(false);
|
|
2053
|
+
// Skill-learning: a recovery event this turn (feeds the trigger + recovery-rate measure).
|
|
2054
|
+
this.skillLearning?.onRecoveryFired(mapKey);
|
|
1961
2055
|
// Recovery steps through the ladder rungs strictly below the configured cap;
|
|
1962
2056
|
// the number of those rungs is how many shrink attempts exist before even the
|
|
1963
2057
|
// smallest (0-history) spawn has been tried and still trips 32MB.
|
|
@@ -2012,9 +2106,23 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2012
2106
|
* restarted so the change reaches them on their next spawn. The recomposed
|
|
2013
2107
|
* CLAUDE.md is already on disk, so a skipped busy session picks up the
|
|
2014
2108
|
* change on its own next natural spawn.
|
|
2109
|
+
* @param opts.deferIdle When true, idle sessions are NOT `stop()`ed now.
|
|
2110
|
+
* Instead they are added to the runner-level `pendingRestarts` set, so each
|
|
2111
|
+
* respawns cleanly on its next message (the same lossless mechanism used for
|
|
2112
|
+
* the image-size-threshold restart) rather than being SIGKILLed mid-idle and
|
|
2113
|
+
* losing its in-context state. Since CLAUDE.md is frozen-at-spawn, an idle
|
|
2114
|
+
* bystander gains nothing from an immediate stop — the recomposed file
|
|
2115
|
+
* applies on its next spawn either way. Use for agent-writable/identity and
|
|
2116
|
+
* skills changes, where dropping an unrelated idle session is pure downside.
|
|
2117
|
+
* NB: idle sessions are deferred via `pendingRestarts`, NOT
|
|
2118
|
+
* `proc.markPendingRestart()` — the latter emits `deferredRestartReady`
|
|
2119
|
+
* synchronously on an idle process, which stops it immediately (no deferral).
|
|
2120
|
+
* @returns Counts of sessions handled: `immediate` (stopped now), `deferred`
|
|
2121
|
+
* (armed to restart on next turn/message), `skipped` (left untouched).
|
|
2015
2122
|
*/
|
|
2016
2123
|
async restartOrDefer(opts) {
|
|
2017
2124
|
const skipBusy = opts?.skipBusy ?? false;
|
|
2125
|
+
const deferIdle = opts?.deferIdle ?? false;
|
|
2018
2126
|
let immediate = 0;
|
|
2019
2127
|
let deferred = 0;
|
|
2020
2128
|
let skipped = 0;
|
|
@@ -2028,6 +2136,22 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2028
2136
|
proc.markPendingRestart();
|
|
2029
2137
|
deferred++;
|
|
2030
2138
|
}
|
|
2139
|
+
else if (deferIdle && proc.source !== 'api') {
|
|
2140
|
+
// Lossless deferral for CHANNEL sessions only: leave the idle process
|
|
2141
|
+
// running and arm a restart on its next message (mirrors the
|
|
2142
|
+
// image-size-threshold path). Do NOT call proc.markPendingRestart() here
|
|
2143
|
+
// — on an idle process it fires deferredRestartReady immediately and
|
|
2144
|
+
// stops the session.
|
|
2145
|
+
//
|
|
2146
|
+
// Guard on source: `pendingRestarts` is consumed ONLY in injectTurn (the
|
|
2147
|
+
// channel turn path, keyed by chatId). api / __heartbeat__ sessions are
|
|
2148
|
+
// keyed by sessionId and never flow through injectTurn, so an entry armed
|
|
2149
|
+
// for them would never be consumed — it would leak in the Set and the
|
|
2150
|
+
// change would never reach the session. Fall through to an immediate stop
|
|
2151
|
+
// so those respawn fresh on their next use, exactly as before deferIdle.
|
|
2152
|
+
this.pendingRestarts.add(id);
|
|
2153
|
+
deferred++;
|
|
2154
|
+
}
|
|
2031
2155
|
else {
|
|
2032
2156
|
toStopNow.push(id);
|
|
2033
2157
|
}
|
|
@@ -2041,6 +2165,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2041
2165
|
immediate++;
|
|
2042
2166
|
}
|
|
2043
2167
|
this.logger.info('restartOrDefer: sessions restarted', { immediate, deferred, skipped });
|
|
2168
|
+
return { immediate, deferred, skipped };
|
|
2044
2169
|
}
|
|
2045
2170
|
/**
|
|
2046
2171
|
* Format a timestamp as a human-readable age string (e.g. "5m ago", "2h ago").
|
|
@@ -2439,17 +2564,31 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2439
2564
|
let quietTimer;
|
|
2440
2565
|
// Track partial message text for delta computation (--include-partial-messages)
|
|
2441
2566
|
let lastPartialText = '';
|
|
2442
|
-
|
|
2567
|
+
// Guards done/fail against a double-fire — e.g. onExit racing a 'result'
|
|
2568
|
+
// line that already settled this turn (see onExit below).
|
|
2569
|
+
let settled = false;
|
|
2570
|
+
const done = (result, interrupted = false) => {
|
|
2571
|
+
if (settled)
|
|
2572
|
+
return;
|
|
2573
|
+
settled = true;
|
|
2443
2574
|
cleanup();
|
|
2444
2575
|
const attachments = this.popApiAttachments(sessionId);
|
|
2576
|
+
const trimmed = result.trim();
|
|
2577
|
+
// A /stop that lands before any text or attachment came out of this turn
|
|
2578
|
+
// would otherwise persist nothing at all — the last committed message
|
|
2579
|
+
// stays the user's forever, and the web sidebar's "typing…" indicator
|
|
2580
|
+
// never clears (it only clears once the last message is from the
|
|
2581
|
+
// assistant). Same wording buildInitialPrompt uses to patch this same
|
|
2582
|
+
// dangling-turn shape for the next spawn's in-memory context.
|
|
2583
|
+
const finalText = !trimmed && !attachments.length && interrupted ? process_1.INTERRUPTED_NO_REPLY_TEXT : trimmed;
|
|
2445
2584
|
// Persist assistant reply. Image-only replies (empty text but attachments
|
|
2446
2585
|
// present) must also persist — otherwise the screenshot vanishes from history.
|
|
2447
|
-
if (
|
|
2586
|
+
if (finalText || attachments.length) {
|
|
2448
2587
|
const apiAssistantTs = Date.now();
|
|
2449
2588
|
this.sessionStore
|
|
2450
2589
|
.appendMessage(this.agentConfig.id, sessionId, {
|
|
2451
2590
|
role: 'assistant',
|
|
2452
|
-
content:
|
|
2591
|
+
content: finalText,
|
|
2453
2592
|
ts: apiAssistantTs,
|
|
2454
2593
|
})
|
|
2455
2594
|
.catch(() => { });
|
|
@@ -2458,14 +2597,17 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2458
2597
|
sessionId,
|
|
2459
2598
|
source: 'api',
|
|
2460
2599
|
role: 'assistant',
|
|
2461
|
-
content:
|
|
2600
|
+
content: finalText,
|
|
2462
2601
|
mediaFiles: attachments.length ? attachments.map((a) => `media/${a.relPath}`) : undefined,
|
|
2463
2602
|
ts: apiAssistantTs,
|
|
2464
2603
|
});
|
|
2465
2604
|
}
|
|
2466
|
-
resolve({ text:
|
|
2605
|
+
resolve({ text: finalText, attachments });
|
|
2467
2606
|
};
|
|
2468
2607
|
const fail = (err) => {
|
|
2608
|
+
if (settled)
|
|
2609
|
+
return;
|
|
2610
|
+
settled = true;
|
|
2469
2611
|
// Terminal close for a turn that produced no result (#75): record it so
|
|
2470
2612
|
// history does not end on a dangling user message, and drain the
|
|
2471
2613
|
// attachment buffer so nothing leaks into the next turn.
|
|
@@ -2473,6 +2615,26 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2473
2615
|
cleanup();
|
|
2474
2616
|
reject(err); // no-op when the soft timeout already rejected
|
|
2475
2617
|
};
|
|
2618
|
+
// The subprocess died without ever emitting a final `result` line (crash,
|
|
2619
|
+
// an unexpected signal — distinct from a graceful /stop, whose SIGINT
|
|
2620
|
+
// handler still emits a terminal result before exiting, so `done()` wins
|
|
2621
|
+
// that race and this is a no-op). Without this, a mid-turn crash left
|
|
2622
|
+
// pendingApiSessions — and the caller's pending-response spinner — stuck
|
|
2623
|
+
// until the up-to-15-minute opts.timeoutMs + API_TIMEOUT_HARD_CAP_EXTRA_MS
|
|
2624
|
+
// safety net finally fired.
|
|
2625
|
+
//
|
|
2626
|
+
// If the exit followed a /stop (the CLI's SIGINT handler fell through to
|
|
2627
|
+
// default termination instead of flushing a result line), that's a
|
|
2628
|
+
// successful stop, not a crash — resolve like the graceful path instead
|
|
2629
|
+
// of surfacing "process exited unexpectedly" for a stop the user asked for.
|
|
2630
|
+
const onExit = () => {
|
|
2631
|
+
const interrupted = session.consumeInterruptFlag();
|
|
2632
|
+
if (interrupted) {
|
|
2633
|
+
done(buffer.join(''), true);
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
fail(Object.assign(new Error('Session process exited unexpectedly before responding.'), { code: 'PROCESS_EXITED' }));
|
|
2637
|
+
};
|
|
2476
2638
|
const cleanup = () => {
|
|
2477
2639
|
clearTimeout(globalTimer);
|
|
2478
2640
|
if (hardCapTimer)
|
|
@@ -2480,12 +2642,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2480
2642
|
if (quietTimer)
|
|
2481
2643
|
clearTimeout(quietTimer);
|
|
2482
2644
|
session.off('output', onOutput);
|
|
2645
|
+
session.off('exit', onExit);
|
|
2483
2646
|
this.pendingApiSessions.delete(sessionId);
|
|
2484
2647
|
};
|
|
2485
2648
|
const resetQuiet = () => {
|
|
2486
2649
|
if (quietTimer)
|
|
2487
2650
|
clearTimeout(quietTimer);
|
|
2488
|
-
quietTimer = setTimeout(() => done(buffer.join('')), 2000);
|
|
2651
|
+
quietTimer = setTimeout(() => done(buffer.join(''), session.consumeInterruptFlag()), 2000);
|
|
2489
2652
|
};
|
|
2490
2653
|
const onOutput = (line) => {
|
|
2491
2654
|
try {
|
|
@@ -2521,7 +2684,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2521
2684
|
// accumulated buffer (needed for non-Anthropic models e.g. OpenRouter,
|
|
2522
2685
|
// which emit result:"" even though the text arrived via stream_event chunks).
|
|
2523
2686
|
const resultText = obj['result'] || buffer.join('');
|
|
2524
|
-
|
|
2687
|
+
// Consumed here (rather than left for onExit) so the graceful path — a
|
|
2688
|
+
// result line flushed before the subprocess exits — still gets credit
|
|
2689
|
+
// for the interrupt; onExit's own consumeInterruptFlag() call would
|
|
2690
|
+
// otherwise see it already cleared and take the (harmless, settled-
|
|
2691
|
+
// guarded) fail() branch instead.
|
|
2692
|
+
done(resultText, session.consumeInterruptFlag());
|
|
2525
2693
|
}
|
|
2526
2694
|
}
|
|
2527
2695
|
catch {
|
|
@@ -2541,6 +2709,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2541
2709
|
}, API_TIMEOUT_HARD_CAP_EXTRA_MS);
|
|
2542
2710
|
}, opts.timeoutMs);
|
|
2543
2711
|
session.on('output', onOutput);
|
|
2712
|
+
session.on('exit', onExit);
|
|
2544
2713
|
session.setProcessing(true);
|
|
2545
2714
|
session.sendMessage(channelXml);
|
|
2546
2715
|
// Do NOT call resetQuiet() here — the quiet timer should only start
|
|
@@ -2621,21 +2790,29 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2621
2790
|
let lastPartialText = '';
|
|
2622
2791
|
// Accumulate tool_use blocks from stream_event (content_block_start → delta → stop)
|
|
2623
2792
|
const toolBlocks = new Map();
|
|
2624
|
-
const done = (result) => {
|
|
2793
|
+
const done = (result, interrupted = false) => {
|
|
2625
2794
|
if (settled)
|
|
2626
2795
|
return;
|
|
2627
2796
|
settled = true;
|
|
2628
2797
|
cleanup();
|
|
2629
2798
|
const attachments = this.popApiAttachments(sessionId);
|
|
2799
|
+
const trimmed = result.trim();
|
|
2800
|
+
// A /stop that lands before any text or attachment came out of this turn
|
|
2801
|
+
// would otherwise persist nothing at all — the last committed message
|
|
2802
|
+
// stays the user's forever, and the web sidebar's "typing…" indicator
|
|
2803
|
+
// never clears (it only clears once the last message is from the
|
|
2804
|
+
// assistant). Same wording buildInitialPrompt uses to patch this same
|
|
2805
|
+
// dangling-turn shape for the next spawn's in-memory context.
|
|
2806
|
+
const finalText = !trimmed && !attachments.length && interrupted ? process_1.INTERRUPTED_NO_REPLY_TEXT : trimmed;
|
|
2630
2807
|
// Persist assistant reply regardless of whether the SSE client is still connected.
|
|
2631
2808
|
// Image-only replies (empty text but attachments present) must also persist —
|
|
2632
2809
|
// otherwise the screenshot vanishes from history once the stream ends.
|
|
2633
|
-
if (
|
|
2810
|
+
if (finalText || attachments.length) {
|
|
2634
2811
|
const streamAssistantTs = Date.now();
|
|
2635
2812
|
this.sessionStore
|
|
2636
2813
|
.appendMessage(this.agentConfig.id, sessionId, {
|
|
2637
2814
|
role: 'assistant',
|
|
2638
|
-
content:
|
|
2815
|
+
content: finalText,
|
|
2639
2816
|
ts: streamAssistantTs,
|
|
2640
2817
|
})
|
|
2641
2818
|
.catch(() => { });
|
|
@@ -2644,12 +2821,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2644
2821
|
sessionId,
|
|
2645
2822
|
source: 'api',
|
|
2646
2823
|
role: 'assistant',
|
|
2647
|
-
content:
|
|
2824
|
+
content: finalText,
|
|
2648
2825
|
mediaFiles: attachments.length ? attachments.map((a) => `media/${a.relPath}`) : undefined,
|
|
2649
2826
|
ts: streamAssistantTs,
|
|
2650
2827
|
});
|
|
2651
2828
|
}
|
|
2652
|
-
callbacks.onDone(
|
|
2829
|
+
callbacks.onDone(finalText, attachments);
|
|
2653
2830
|
};
|
|
2654
2831
|
const fail = (err) => {
|
|
2655
2832
|
if (settled)
|
|
@@ -2663,11 +2840,32 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2663
2840
|
this.persistFailedApiTurn(chatId, sessionId, err, opts.skipUserMessage);
|
|
2664
2841
|
notifyError(err);
|
|
2665
2842
|
};
|
|
2843
|
+
// The subprocess died without ever emitting a final `result` line (crash,
|
|
2844
|
+
// an unexpected signal — distinct from a graceful /stop, whose SIGINT
|
|
2845
|
+
// handler still emits a terminal result before exiting, so `done()` wins
|
|
2846
|
+
// that race and this is a no-op via the `settled` guard in fail()).
|
|
2847
|
+
// Without this, a mid-turn crash left pendingApiSessions — and the web's
|
|
2848
|
+
// pending-response spinner — stuck until the up-to-15-minute
|
|
2849
|
+
// opts.timeoutMs + API_TIMEOUT_HARD_CAP_EXTRA_MS safety net finally fired.
|
|
2850
|
+
//
|
|
2851
|
+
// If the exit followed a /stop (the CLI's SIGINT handler fell through to
|
|
2852
|
+
// default termination instead of flushing a result line), that's a
|
|
2853
|
+
// successful stop, not a crash — resolve like the graceful path instead
|
|
2854
|
+
// of surfacing "process exited unexpectedly" for a stop the user asked for.
|
|
2855
|
+
const onExit = () => {
|
|
2856
|
+
const interrupted = session.consumeInterruptFlag();
|
|
2857
|
+
if (interrupted) {
|
|
2858
|
+
done(buffer.join(''), true);
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
fail(Object.assign(new Error('Session process exited unexpectedly before responding.'), { code: 'PROCESS_EXITED' }));
|
|
2862
|
+
};
|
|
2666
2863
|
const cleanup = () => {
|
|
2667
2864
|
clearTimeout(globalTimer);
|
|
2668
2865
|
if (hardCapTimer)
|
|
2669
2866
|
clearTimeout(hardCapTimer);
|
|
2670
2867
|
session.off('output', onOutput);
|
|
2868
|
+
session.off('exit', onExit);
|
|
2671
2869
|
this.pendingApiSessions.delete(sessionId);
|
|
2672
2870
|
};
|
|
2673
2871
|
// Called when the SSE client disconnects. Marks clientGone so SSE writes
|
|
@@ -2740,7 +2938,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2740
2938
|
lastPartialText = ''; // reset for next turn
|
|
2741
2939
|
// Use || so empty-string result falls back to buffer (OpenRouter emits result:"").
|
|
2742
2940
|
const resultText = obj['result'] || buffer.join('');
|
|
2743
|
-
|
|
2941
|
+
// Consumed here (rather than left for onExit) so the graceful path — a
|
|
2942
|
+
// result line flushed before the subprocess exits — still gets credit
|
|
2943
|
+
// for the interrupt; onExit's own consumeInterruptFlag() call would
|
|
2944
|
+
// otherwise see it already cleared and take the (harmless, settled-
|
|
2945
|
+
// guarded) fail() branch instead.
|
|
2946
|
+
done(resultText, session.consumeInterruptFlag());
|
|
2744
2947
|
}
|
|
2745
2948
|
}
|
|
2746
2949
|
catch {
|
|
@@ -2769,6 +2972,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
2769
2972
|
}, API_TIMEOUT_HARD_CAP_EXTRA_MS);
|
|
2770
2973
|
}, opts.timeoutMs);
|
|
2771
2974
|
session.on('output', onOutput);
|
|
2975
|
+
session.on('exit', onExit);
|
|
2772
2976
|
// Image paths only work when allowTools:true — Claude needs the Read tool to access them
|
|
2773
2977
|
const allowToolsStream = opts.allowTools ?? false;
|
|
2774
2978
|
if (!allowToolsStream && imagePathsStream.length) {
|