@0xmaxma/claude-gateway 1.3.24 → 1.3.31

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.
Files changed (92) hide show
  1. package/README.md +172 -50
  2. package/config.template.json +6 -2
  3. package/dist/agent/incident-store.d.ts +89 -0
  4. package/dist/agent/incident-store.d.ts.map +1 -0
  5. package/dist/agent/incident-store.js +299 -0
  6. package/dist/agent/incident-store.js.map +1 -0
  7. package/dist/agent/incident.d.ts +156 -0
  8. package/dist/agent/incident.d.ts.map +1 -0
  9. package/dist/agent/incident.js +177 -0
  10. package/dist/agent/incident.js.map +1 -0
  11. package/dist/agent/recovery-executor.d.ts +117 -0
  12. package/dist/agent/recovery-executor.d.ts.map +1 -0
  13. package/dist/agent/recovery-executor.js +168 -0
  14. package/dist/agent/recovery-executor.js.map +1 -0
  15. package/dist/agent/recovery-policy.d.ts +97 -0
  16. package/dist/agent/recovery-policy.d.ts.map +1 -0
  17. package/dist/agent/recovery-policy.js +164 -0
  18. package/dist/agent/recovery-policy.js.map +1 -0
  19. package/dist/agent/runner.d.ts +44 -0
  20. package/dist/agent/runner.d.ts.map +1 -1
  21. package/dist/agent/runner.js +272 -2
  22. package/dist/agent/runner.js.map +1 -1
  23. package/dist/agent/safe-mode.d.ts +61 -0
  24. package/dist/agent/safe-mode.d.ts.map +1 -0
  25. package/dist/agent/safe-mode.js +102 -0
  26. package/dist/agent/safe-mode.js.map +1 -0
  27. package/dist/agent/triage.d.ts +94 -0
  28. package/dist/agent/triage.d.ts.map +1 -0
  29. package/dist/agent/triage.js +209 -0
  30. package/dist/agent/triage.js.map +1 -0
  31. package/dist/agent/turn-trace.d.ts +120 -0
  32. package/dist/agent/turn-trace.d.ts.map +1 -0
  33. package/dist/agent/turn-trace.js +122 -0
  34. package/dist/agent/turn-trace.js.map +1 -0
  35. package/dist/api/gateway-router.d.ts +21 -0
  36. package/dist/api/gateway-router.d.ts.map +1 -1
  37. package/dist/api/gateway-router.js +58 -17
  38. package/dist/api/gateway-router.js.map +1 -1
  39. package/dist/api/line-pending-senders.d.ts +7 -2
  40. package/dist/api/line-pending-senders.d.ts.map +1 -1
  41. package/dist/api/line-pending-senders.js +7 -2
  42. package/dist/api/line-pending-senders.js.map +1 -1
  43. package/dist/api/router.d.ts.map +1 -1
  44. package/dist/api/router.js +563 -172
  45. package/dist/api/router.js.map +1 -1
  46. package/dist/api/wizard-state.d.ts +1 -4
  47. package/dist/api/wizard-state.d.ts.map +1 -1
  48. package/dist/api/wizard-state.js.map +1 -1
  49. package/dist/config/migrator.d.ts +4 -0
  50. package/dist/config/migrator.d.ts.map +1 -1
  51. package/dist/config/migrator.js +60 -3
  52. package/dist/config/migrator.js.map +1 -1
  53. package/dist/discord/receiver.d.ts +1 -0
  54. package/dist/discord/receiver.d.ts.map +1 -1
  55. package/dist/discord/receiver.js +24 -6
  56. package/dist/discord/receiver.js.map +1 -1
  57. package/dist/index.js +3 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/session/process.d.ts +18 -0
  60. package/dist/session/process.d.ts.map +1 -1
  61. package/dist/session/process.js +61 -1
  62. package/dist/session/process.js.map +1 -1
  63. package/dist/shell/claude-pty-shell.js +59 -0
  64. package/dist/shell/claude-pty-shell.js.map +1 -1
  65. package/dist/shell/control-channel.d.ts +74 -0
  66. package/dist/shell/control-channel.d.ts.map +1 -0
  67. package/dist/shell/control-channel.js +114 -0
  68. package/dist/shell/control-channel.js.map +1 -0
  69. package/dist/telegram/receiver.d.ts +1 -0
  70. package/dist/telegram/receiver.d.ts.map +1 -1
  71. package/dist/telegram/receiver.js +18 -6
  72. package/dist/telegram/receiver.js.map +1 -1
  73. package/dist/types.d.ts +21 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/ui/web-ui.d.ts.map +1 -1
  76. package/dist/ui/web-ui.js +103 -2
  77. package/dist/ui/web-ui.js.map +1 -1
  78. package/mcp/tools/discord/access.ts +136 -20
  79. package/mcp/tools/discord/client.ts +3 -1
  80. package/mcp/tools/discord/module.ts +75 -4
  81. package/mcp/tools/discord/skills/access/SKILL.md +66 -8
  82. package/mcp/tools/discord/skills/configure/SKILL.md +11 -1
  83. package/mcp/tools/discord/types.ts +21 -2
  84. package/mcp/tools/skills/handlers.ts +4 -2
  85. package/mcp/tools/telegram/dedup.ts +4 -1
  86. package/mcp/tools/telegram/module.ts +5 -14
  87. package/mcp/tools/telegram/pure.ts +169 -31
  88. package/mcp/tools/telegram/receiver-server.ts +271 -78
  89. package/mcp/tools/telegram/skills/access/SKILL.md +93 -23
  90. package/mcp/tools/telegram/skills/configure/SKILL.md +31 -26
  91. package/mcp/tools/telegram/typing.ts +124 -0
  92. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
@@ -49,6 +49,11 @@ const line_reply_manager_1 = require("./line-reply-manager");
49
49
  const markdown_1 = require("../telegram/markdown");
50
50
  const skills_1 = require("../skills");
51
51
  const builtin_commands_1 = require("./builtin-commands");
52
+ const safe_mode_1 = require("./safe-mode");
53
+ const recovery_executor_1 = require("./recovery-executor");
54
+ const recovery_policy_1 = require("./recovery-policy");
55
+ const incident_1 = require("./incident");
56
+ const pty_stream_registry_1 = require("../shell/pty-stream-registry");
52
57
  const screen_1 = require("../shell/screen");
53
58
  const db_1 = require("../history/db");
54
59
  const media_store_1 = require("../history/media-store");
@@ -82,6 +87,14 @@ const PROTECTED_WORKSPACE_FILES = [
82
87
  'IDENTITY.md', 'USER.md', 'HEARTBEAT.md',
83
88
  ];
84
89
  const MAX_API_IMAGES = 5;
90
+ // Hard timeout for the one-shot local `claude -p` triage during recovery
91
+ // (Epic #195, Phase 3b). A slow/hung triage collapses to a safe notify-only.
92
+ const RECOVERY_TRIAGE_TIMEOUT_MS = 15000;
93
+ // TTL after which a per-turn recovery budget entry is evicted. A turn's budget
94
+ // only matters during its active stall window (a few interventions spaced by a
95
+ // 30s cooldown), so anything older is a finished turn — pruned to keep the
96
+ // budget map from growing unbounded over a long-lived runner (Epic #195, 3b).
97
+ const RECOVERY_BUDGET_TTL_MS = 10 * 60000;
85
98
  // Trailing-edge window for coalescing channel messages that carry an image (or that
86
99
  // arrive while an image is already buffered) into a single turn. Reset on every new
87
100
  // related update, so an album burst or a client-split photo+caption is gathered
@@ -157,6 +170,27 @@ class AgentRunner extends events_1.EventEmitter {
157
170
  // restarting would just churn a context that cannot shrink. Cleared together
158
171
  // with the counter on a successful result and on /clear.
159
172
  this.tooLargeExhausted = new Set();
173
+ // Safe-mode manager (Epic #195, Phase 3): tracks repeated PTY-backend failures
174
+ // and, past a threshold, forces this agent to the headless backend so it keeps
175
+ // serving turns without a gateway restart. In-memory only — a restart clears it
176
+ // and re-reads the user's real config. spawnSession reads isActive() to set
177
+ // SessionProcess.forceHeadless.
178
+ this.safeMode = new safe_mode_1.SafeModeManager({
179
+ audit: (e) => this.logger.info('Safe-mode transition', {
180
+ agentId: e.agentId,
181
+ action: e.action,
182
+ reason: e.reason,
183
+ failures: e.failures,
184
+ }),
185
+ });
186
+ // Recovery executor state (Epic #195, Phase 3b). Per-turn intervention budget,
187
+ // keyed by turnKey so it resets each turn. In-memory only.
188
+ this.recoveryBudgets = new Map();
189
+ // Last injected turn per chat, for the C1 guarded resend: `delivered` flips
190
+ // true once assistant output reaches the channel, so a resend after recovery
191
+ // only fires when the stalled turn produced nothing (no double-submit). `resent`
192
+ // guards against resending more than once per turn.
193
+ this.lastTurn = new Map();
160
194
  // Tracks pending Telegram image paths per chatId (queue) for size accumulation after each turn.
161
195
  this.pendingImagePaths = new Map();
162
196
  // Coalescing buffer: collects channel messages that arrive close together so a
@@ -177,6 +211,46 @@ class AgentRunner extends events_1.EventEmitter {
177
211
  this.skillRegistry = { skills: new Map() };
178
212
  // Cancel function for the daily history cleanup timer
179
213
  this.cancelCleanup = null;
214
+ /**
215
+ * One-shot local `claude -p` triage runner (Phase 3b). Feeds the closed
216
+ * classification prompt on stdin and returns raw stdout. Hard timeout →
217
+ * timedOut, which the executor collapses to a safe notify-only verdict. Only
218
+ * invoked when autoRecover is enabled.
219
+ */
220
+ this.recoveryTriageSpawn = async (prompt) => {
221
+ const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
222
+ return new Promise((resolve) => {
223
+ let done = false;
224
+ let out = '';
225
+ const finish = (r) => {
226
+ if (done)
227
+ return;
228
+ done = true;
229
+ clearTimeout(timer);
230
+ resolve(r);
231
+ };
232
+ const timer = setTimeout(() => {
233
+ try {
234
+ child.kill('SIGKILL');
235
+ }
236
+ catch { /* already gone */ }
237
+ finish({ stdout: '', timedOut: true });
238
+ }, RECOVERY_TRIAGE_TIMEOUT_MS);
239
+ const child = spawn('claude', ['-p', '--output-format', 'text'], {
240
+ stdio: ['pipe', 'pipe', 'ignore'],
241
+ });
242
+ child.stdout?.on('data', (d) => { out += String(d); });
243
+ child.on('error', () => finish({ stdout: '', timedOut: true }));
244
+ child.on('close', () => finish({ stdout: out }));
245
+ try {
246
+ child.stdin?.write(prompt);
247
+ child.stdin?.end();
248
+ }
249
+ catch {
250
+ finish({ stdout: '', timedOut: true });
251
+ }
252
+ });
253
+ };
180
254
  this.agentConfig = agentConfig;
181
255
  this.gatewayConfig = gatewayConfig;
182
256
  this.logger = logger ?? (0, logger_1.createLogger)(agentConfig.id, gatewayConfig.gateway.logDir);
@@ -229,6 +303,13 @@ class AgentRunner extends events_1.EventEmitter {
229
303
  this.handleCommandRequest(raw, res);
230
304
  return;
231
305
  }
306
+ if (url.pathname === '/recover') {
307
+ // Cross-process recovery bridge (Epic #195, Phase 3b): the receiver
308
+ // process detects a stall but recovery must run here, where the live
309
+ // control surfaces (session stdin, restart, safe-mode) live.
310
+ this.handleRecoverRequest(raw, res);
311
+ return;
312
+ }
232
313
  // Default: /channel — existing channel message handler
233
314
  // Connection: close prevents keep-alive pool reuse: after the OS's
234
315
  // keepAliveTimeout expires the server closes the TCP connection, and the
@@ -315,6 +396,160 @@ class AgentRunner extends events_1.EventEmitter {
315
396
  });
316
397
  this.logger.info('Channel callback server listening', { port: this.callbackPort });
317
398
  }
399
+ /**
400
+ * Handle POST /recover from the receiver process (Epic #195, Phase 3b).
401
+ * The watchdog detects a stall in the receiver, but recovery must run here in
402
+ * the runner, where the live control surfaces live. Runs the pure executor
403
+ * with effects bound to the affected session and returns the outcome so the
404
+ * receiver can persist it to the incident bundle.
405
+ */
406
+ async handleRecoverRequest(raw, res) {
407
+ const respond = (data, status = 200) => {
408
+ res.writeHead(status, { 'Content-Type': 'application/json' });
409
+ res.end(JSON.stringify(data));
410
+ };
411
+ let body;
412
+ try {
413
+ body = JSON.parse(raw);
414
+ }
415
+ catch {
416
+ respond({ ok: false, error: 'Invalid JSON' }, 400);
417
+ return;
418
+ }
419
+ const chatId = body.chatId ?? '';
420
+ const stage = body.stage ?? '';
421
+ if (!chatId || !stage) {
422
+ respond({ ok: false, error: 'missing chatId/stage' }, 400);
423
+ return;
424
+ }
425
+ const session = this.sessions.get(chatId);
426
+ const autoRecover = this.gatewayConfig.gateway.selfHealing?.autoRecover === true;
427
+ const req = {
428
+ incidentId: body.incidentId ?? '',
429
+ agentId: this.agentConfig.id,
430
+ chatId,
431
+ // Prefer the live session's id (survives a request that names a stale one).
432
+ sessionId: session?.sessionId ?? body.sessionId ?? '',
433
+ stage,
434
+ failureClass: body.failureClass ?? null,
435
+ turnKey: body.turnKey ?? chatId,
436
+ };
437
+ try {
438
+ const result = await (0, recovery_executor_1.runRecovery)(req, {
439
+ autoRecover,
440
+ effects: this.buildRecoveryEffects(chatId),
441
+ now: () => Date.now(),
442
+ budget: {
443
+ get: (turnKey) => this.recoveryBudgets.get(turnKey) ?? (0, recovery_policy_1.initialBudget)(turnKey),
444
+ set: (s) => {
445
+ this.recoveryBudgets.set(s.turnKey, s);
446
+ this.pruneRecoveryBudgets(s.lastAt);
447
+ },
448
+ },
449
+ // Live triage is the most sensitive surface — only ever run when the
450
+ // operator has opted in. When off, the executor uses the deterministic
451
+ // per-stage default action (still whitelist-clamped).
452
+ triageSpawn: autoRecover ? this.recoveryTriageSpawn : undefined,
453
+ gatherEvidence: async () => {
454
+ const s = this.sessions.get(chatId);
455
+ if (!s)
456
+ return null;
457
+ try {
458
+ const snap = await pty_stream_registry_1.ptyStreamRegistry.screenText(s.sessionId);
459
+ if (!snap)
460
+ return null;
461
+ // Scrub the untrusted screen before it leaves the process / reaches
462
+ // the triage model. Redact the chat id explicitly on top of the
463
+ // pattern-based secret scrub.
464
+ return { screenText: (0, incident_1.scrubText)(snap.text, [chatId]) };
465
+ }
466
+ catch {
467
+ return null;
468
+ }
469
+ },
470
+ resendAfterRecover: autoRecover,
471
+ log: (msg, meta) => this.logger.info(msg, meta),
472
+ });
473
+ respond({ ok: true, outcome: (0, recovery_executor_1.toRecoveryOutcome)(result), result });
474
+ }
475
+ catch (err) {
476
+ this.logger.error('Recovery attempt failed', { chatId, stage, error: err.message });
477
+ respond({ ok: false, error: err.message }, 500);
478
+ }
479
+ }
480
+ /**
481
+ * Route raw interactive-terminal input to the session with this actual
482
+ * sessionId (Issue #201). The dashboard's Terminal Viewer input mode
483
+ * streams keystrokes here via the pty-stream WebSocket. Sessions are keyed by
484
+ * chatId internally, so we match on the process's own sessionId. Returns true
485
+ * only when a live pty-shell session accepted the bytes (headless / missing /
486
+ * not-writable → false), so the caller can surface an accurate result.
487
+ */
488
+ sendInputToSession(sessionId, data) {
489
+ if (!sessionId || typeof data !== 'string' || data.length === 0)
490
+ return false;
491
+ for (const proc of this.sessions.values()) {
492
+ if (proc.sessionId === sessionId) {
493
+ return proc.sendInput(data);
494
+ }
495
+ }
496
+ return false;
497
+ }
498
+ /**
499
+ * Build the recovery effects bound to one chat's session (Phase 3b). Each
500
+ * effect resolves the session fresh so it survives a restart mid-recovery.
501
+ * Keystroke effects go through the wrapper control channel; restart/backend
502
+ * effects act on the session and safe-mode manager. Effects intentionally
503
+ * omitted (redeliver-forward, restart-receiver, bridge-menu) are transport /
504
+ * wrapper concerns not bridged to the runner — the executor reports them as
505
+ * unsupported rather than guessing.
506
+ */
507
+ buildRecoveryEffects(chatId) {
508
+ const control = (key, option) => {
509
+ this.sessions.get(chatId)?.sendControl(key, option);
510
+ };
511
+ return {
512
+ esc: () => control('esc'),
513
+ escEsc: () => control('esc-esc'),
514
+ enter: () => control('enter'),
515
+ selectOption: (option) => control('select-option', option),
516
+ restartSession: () => this.restartProcess(chatId),
517
+ fallbackHeadless: async () => {
518
+ // Flip to the headless backend, then restart so the new session respawns
519
+ // headless (spawnSession reads safeMode.isActive → forceHeadless).
520
+ this.safeMode.enter(this.agentConfig.id, 'recovery: fallback-headless');
521
+ await this.restartProcess(chatId);
522
+ },
523
+ resendLast: () => {
524
+ const lt = this.lastTurn.get(chatId);
525
+ // Guard: only resend a turn that produced no output and was not already
526
+ // resent — never double-submit.
527
+ if (!lt || lt.delivered || lt.resent)
528
+ return false;
529
+ const s = this.sessions.get(chatId);
530
+ if (!s)
531
+ return false;
532
+ lt.resent = true;
533
+ s.setProcessing(true);
534
+ s.sendMessage(lt.text);
535
+ s.touch();
536
+ return true;
537
+ },
538
+ };
539
+ }
540
+ /**
541
+ * Evict finished-turn budget entries (Epic #195, Phase 3b). Called on each
542
+ * budget write so the per-turn map cannot grow without bound on a long-lived
543
+ * runner. `nowMs` is the timestamp of the write just made; any entry whose last
544
+ * attempt predates the TTL belongs to a turn that is no longer being recovered.
545
+ */
546
+ pruneRecoveryBudgets(nowMs) {
547
+ const cutoff = nowMs - RECOVERY_BUDGET_TTL_MS;
548
+ for (const [key, state] of this.recoveryBudgets) {
549
+ if (state.lastAt < cutoff)
550
+ this.recoveryBudgets.delete(key);
551
+ }
552
+ }
318
553
  /**
319
554
  * Handle POST /command requests from the receiver process.
320
555
  * Supports: get_model, set_model, restart.
@@ -599,7 +834,12 @@ class AgentRunner extends events_1.EventEmitter {
599
834
  }
600
835
  }
601
836
  session.setProcessing(true);
602
- session.sendMessage(blocks.join('\n'));
837
+ const turnText = blocks.join('\n');
838
+ session.sendMessage(turnText);
839
+ // Remember this turn for the C1 guarded resend (Phase 3b): reset the
840
+ // delivered/resent flags so a resend can only fire if this turn stalls
841
+ // before producing output.
842
+ this.lastTurn.set(chatId, { text: turnText, delivered: false, resent: false });
603
843
  session.touch();
604
844
  this.logger.debug('Injected channel turn into session', {
605
845
  chatId,
@@ -729,6 +969,15 @@ class AgentRunner extends events_1.EventEmitter {
729
969
  mapKey, recoveryCount, historyLimit: proc.historyLimit,
730
970
  });
731
971
  }
972
+ // Safe mode (Epic #195, Phase 3): if the PTY backend has repeatedly failed
973
+ // for this agent, force the headless backend for this spawn. Cleared
974
+ // automatically once safe mode exits (a later healthy turn / user restore).
975
+ if (this.safeMode.isActive(this.agentConfig.id)) {
976
+ proc.forceHeadless = true;
977
+ this.logger.info('Spawning in safe mode (headless backend forced)', {
978
+ mapKey, agentId: this.agentConfig.id,
979
+ });
980
+ }
732
981
  await proc.start();
733
982
  // Forward all session output lines so listeners on AgentRunner (GatewayRouter,
734
983
  // CronScheduler, tests) receive them without needing individual session references.
@@ -736,7 +985,16 @@ class AgentRunner extends events_1.EventEmitter {
736
985
  // Notify typing indicator when session permanently fails (max restarts exceeded)
737
986
  if (source !== 'api') {
738
987
  proc.once('failed', () => {
739
- this.writeTypingError(mapKey, 'PROCESS_FAILED');
988
+ // Safe mode (Epic #195, Phase 3): a hard failure of the PTY (interactive
989
+ // TUI) backend counts toward the safe-mode threshold. Once crossed, the
990
+ // agent auto-flips to headless on the next spawn so the user's next
991
+ // message is served instead of re-wedging. Headless failures don't count
992
+ // (there is no PTY wrapper to blame / fall back from). When we just
993
+ // entered safe mode, tell the user that specifically instead of the
994
+ // generic "stopped" notice (one .error file, so pick the better code).
995
+ const enteredSafeMode = proc.backend === 'pty-shell' &&
996
+ this.safeMode.recordPtyFailure(this.agentConfig.id);
997
+ this.writeTypingError(mapKey, enteredSafeMode ? 'SAFE_MODE_ENABLED' : 'PROCESS_FAILED');
740
998
  this.sessions.delete(mapKey);
741
999
  // LINE: surface an error for any outstanding postback button so a tap
742
1000
  // returns the interrupted notice instead of a stale "still thinking".
@@ -899,6 +1157,13 @@ class AgentRunner extends events_1.EventEmitter {
899
1157
  // the next 32MB (if any) starts fresh at the top of the ladder.
900
1158
  this.tooLargeRecoveries.delete(mapKey);
901
1159
  this.tooLargeExhausted.delete(mapKey);
1160
+ // Safe mode (Epic #195, Phase 3): a healthy PTY turn resets the
1161
+ // consecutive-failure counter and lifts safe mode if it was active
1162
+ // (the interactive backend recovered). Headless successes don't
1163
+ // touch it — that's the fallback doing its job, not the PTY healing.
1164
+ if (proc.backend === 'pty-shell') {
1165
+ this.safeMode.recordSuccess(this.agentConfig.id);
1166
+ }
902
1167
  }
903
1168
  // When a menu was rendered to buttons this turn, the wrapper appends
904
1169
  // the same menu text to the turn's result — strip that suffix so it
@@ -941,6 +1206,11 @@ class AgentRunner extends events_1.EventEmitter {
941
1206
  else {
942
1207
  this.writeAutoForward(mapKey, text);
943
1208
  }
1209
+ // Assistant output reached the channel — mark the turn delivered so
1210
+ // a later recovery does not resend a message that was answered (C1).
1211
+ const lt = this.lastTurn.get(mapKey);
1212
+ if (lt)
1213
+ lt.delivered = true;
944
1214
  }
945
1215
  replyCalled = false; // reset for next turn
946
1216
  replyToolUseId = null;