@0xmaxma/claude-gateway 1.3.25 → 1.3.32
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 +50 -2
- package/config.template.json +6 -2
- package/dist/agent/incident-store.d.ts +89 -0
- package/dist/agent/incident-store.d.ts.map +1 -0
- package/dist/agent/incident-store.js +299 -0
- package/dist/agent/incident-store.js.map +1 -0
- package/dist/agent/incident.d.ts +156 -0
- package/dist/agent/incident.d.ts.map +1 -0
- package/dist/agent/incident.js +177 -0
- package/dist/agent/incident.js.map +1 -0
- package/dist/agent/recovery-executor.d.ts +117 -0
- package/dist/agent/recovery-executor.d.ts.map +1 -0
- package/dist/agent/recovery-executor.js +168 -0
- package/dist/agent/recovery-executor.js.map +1 -0
- package/dist/agent/recovery-policy.d.ts +97 -0
- package/dist/agent/recovery-policy.d.ts.map +1 -0
- package/dist/agent/recovery-policy.js +164 -0
- package/dist/agent/recovery-policy.js.map +1 -0
- package/dist/agent/runner.d.ts +66 -6
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +332 -27
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/safe-mode.d.ts +61 -0
- package/dist/agent/safe-mode.d.ts.map +1 -0
- package/dist/agent/safe-mode.js +102 -0
- package/dist/agent/safe-mode.js.map +1 -0
- package/dist/agent/triage.d.ts +94 -0
- package/dist/agent/triage.d.ts.map +1 -0
- package/dist/agent/triage.js +209 -0
- package/dist/agent/triage.js.map +1 -0
- package/dist/agent/turn-trace.d.ts +120 -0
- package/dist/agent/turn-trace.d.ts.map +1 -0
- package/dist/agent/turn-trace.js +122 -0
- package/dist/agent/turn-trace.js.map +1 -0
- package/dist/api/gateway-router.d.ts +21 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +58 -17
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +104 -4
- package/dist/api/router.js.map +1 -1
- package/dist/config/migrator.d.ts +4 -0
- package/dist/config/migrator.d.ts.map +1 -1
- package/dist/config/migrator.js +60 -3
- package/dist/config/migrator.js.map +1 -1
- package/dist/history/db.d.ts +13 -1
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +67 -9
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +10 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +26 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +77 -1
- package/dist/session/process.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +59 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/shell/control-channel.d.ts +74 -0
- package/dist/shell/control-channel.d.ts.map +1 -0
- package/dist/shell/control-channel.js +114 -0
- package/dist/shell/control-channel.js.map +1 -0
- package/dist/types.d.ts +22 -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 +103 -2
- package/dist/ui/web-ui.js.map +1 -1
- package/mcp/tools/skills/handlers.ts +4 -2
- package/mcp/tools/telegram/receiver-server.ts +163 -0
- package/mcp/tools/telegram/typing.ts +124 -0
- package/package.json +3 -1
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
* STATE_DIR/typing/{chatId}.error — written by AgentRunner on session failure
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
// Import compiled dist/, not raw src/ — src/ is not published (files: ["mcp/"]),
|
|
17
|
+
// so a src/ import crashes this bun-run tool on installed packages. Enforced by
|
|
18
|
+
// tests/unit/mcp-no-src-imports.test.ts.
|
|
19
|
+
import {
|
|
20
|
+
classifyTurn,
|
|
21
|
+
type TurnObservation,
|
|
22
|
+
type TurnStage,
|
|
23
|
+
type TurnIncidentSink,
|
|
24
|
+
type TurnIncidentEvidence,
|
|
25
|
+
} from '../../../dist/agent/turn-trace.js'
|
|
26
|
+
|
|
16
27
|
export const TELEGRAM_MAX_CHARS = 4096
|
|
17
28
|
|
|
18
29
|
/**
|
|
@@ -145,6 +156,10 @@ export const ERROR_MESSAGES: Record<string, string> = {
|
|
|
145
156
|
PROCESS_FAILED: '❌ Claude stopped unexpectedly. Please try sending a new message.',
|
|
146
157
|
POOL_FULL: '⚠️ Too many concurrent sessions. Please try again in a moment.',
|
|
147
158
|
SPAWN_FAILED: '❌ Failed to start Claude session. Please try again.',
|
|
159
|
+
// Epic #195, Phase 3: the interactive backend failed repeatedly, so the agent
|
|
160
|
+
// temporarily fell back to the headless backend to keep serving.
|
|
161
|
+
SAFE_MODE_ENABLED:
|
|
162
|
+
'⚠️ The interactive backend kept failing, so I switched to safe mode (headless) for now. Please resend your message — it should go through.',
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
export const STATUS_EMOJI: Record<string, string> = {
|
|
@@ -177,6 +192,10 @@ export interface WorkingState {
|
|
|
177
192
|
currentReaction: string | null
|
|
178
193
|
lastDetail: string | null
|
|
179
194
|
recentDetails: string[]
|
|
195
|
+
/** Last stage an incident was raised for — dedupes the turn-trace watchdog
|
|
196
|
+
* so one contiguous stalled episode emits a single incident, not one per
|
|
197
|
+
* 15s tick. Reset to null once the turn is no longer stalled. */
|
|
198
|
+
lastIncidentStage: TurnStage | null
|
|
180
199
|
}
|
|
181
200
|
|
|
182
201
|
export interface BotApi {
|
|
@@ -295,6 +314,7 @@ export function createWorkingStateManager(
|
|
|
295
314
|
typingDir: string,
|
|
296
315
|
botApi: BotApi,
|
|
297
316
|
fsApi: FsApi,
|
|
317
|
+
onIncident?: TurnIncidentSink,
|
|
298
318
|
) {
|
|
299
319
|
const states = new Map<string, WorkingState>()
|
|
300
320
|
|
|
@@ -330,6 +350,106 @@ export function createWorkingStateManager(
|
|
|
330
350
|
return `${typingDir}/${chatId}.replied`
|
|
331
351
|
}
|
|
332
352
|
|
|
353
|
+
function menuFilePath(chatId: string): string {
|
|
354
|
+
return `${typingDir}/${chatId}.menu`
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ─── Turn-trace watchdog (Epic #195, Phase 1) ──────────────────────────────
|
|
358
|
+
// Read-only: builds an observation of this turn's on-disk artifacts, classifies
|
|
359
|
+
// the current pipeline stage, and emits a (silent) incident when a stage stalls.
|
|
360
|
+
// It never mutates state or messages the user — the existing heartbeat warn/stop
|
|
361
|
+
// below remains the sole user-facing stalled behaviour.
|
|
362
|
+
|
|
363
|
+
function statMtime(path: string): number | null {
|
|
364
|
+
try {
|
|
365
|
+
return fsApi.existsSync(path) ? fsApi.statSync(path).mtimeMs : null
|
|
366
|
+
} catch {
|
|
367
|
+
return null
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function readTurnObservation(chatId: string, startedAt: number): TurnObservation {
|
|
372
|
+
let statusLabel: string | null = null
|
|
373
|
+
const statusPath = statusFilePath(chatId)
|
|
374
|
+
if (fsApi.existsSync(statusPath)) {
|
|
375
|
+
try {
|
|
376
|
+
statusLabel = parseStatusFile(fsApi.readFileSync(statusPath, 'utf8')).status
|
|
377
|
+
} catch {}
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
now: Date.now(),
|
|
381
|
+
// A live WorkingState means the signal file was written at startedAt.
|
|
382
|
+
signalAt: fsApi.existsSync(typingFilePath(chatId)) ? startedAt : null,
|
|
383
|
+
statusLabel,
|
|
384
|
+
heartbeatAt: statMtime(heartbeatFilePath(chatId)),
|
|
385
|
+
processingAt: statMtime(processingFilePath(chatId)),
|
|
386
|
+
forwardAt: statMtime(forwardFilePath(chatId)),
|
|
387
|
+
menuAt: statMtime(menuFilePath(chatId)),
|
|
388
|
+
repliedPresent: fsApi.existsSync(repliedFilePath(chatId)),
|
|
389
|
+
errorPresent: fsApi.existsSync(errorFilePath(chatId)),
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function readFileSafe(path: string): string | null {
|
|
394
|
+
try {
|
|
395
|
+
return fsApi.existsSync(path) ? fsApi.readFileSync(path, 'utf8') : null
|
|
396
|
+
} catch {
|
|
397
|
+
return null
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Build the diagnostic evidence bundle for an incident from the same
|
|
402
|
+
// observation the classifier used, plus the raw status/error file contents.
|
|
403
|
+
// Cheap and read-only — no listing of the whole typing dir, just this turn's
|
|
404
|
+
// known artifacts. The store scrubs everything before it is persisted.
|
|
405
|
+
function readTurnEvidence(chatId: string, obs: TurnObservation): TurnIncidentEvidence {
|
|
406
|
+
const artifacts: string[] = []
|
|
407
|
+
if (obs.signalAt !== null) artifacts.push('signal')
|
|
408
|
+
if (obs.statusLabel !== null) artifacts.push(`status=${obs.statusLabel}`)
|
|
409
|
+
if (obs.heartbeatAt !== null) artifacts.push('heartbeat')
|
|
410
|
+
if (obs.processingAt !== null) artifacts.push('processing')
|
|
411
|
+
if (obs.forwardAt !== null) artifacts.push('forward')
|
|
412
|
+
if (obs.menuAt !== null) artifacts.push('menu')
|
|
413
|
+
if (obs.repliedPresent) artifacts.push('replied')
|
|
414
|
+
if (obs.errorPresent) artifacts.push('error')
|
|
415
|
+
return {
|
|
416
|
+
artifacts,
|
|
417
|
+
statusText: readFileSafe(statusFilePath(chatId)),
|
|
418
|
+
errorText: readFileSafe(errorFilePath(chatId)),
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function checkTurnTrace(chatId: string, startedAt: number): void {
|
|
423
|
+
const state = states.get(chatId)
|
|
424
|
+
if (!state) return
|
|
425
|
+
const obs = readTurnObservation(chatId, startedAt)
|
|
426
|
+
const trace = classifyTurn(obs)
|
|
427
|
+
if (!trace.stalled) {
|
|
428
|
+
state.lastIncidentStage = null
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
// Dedupe: one incident per contiguous stalled-stage episode.
|
|
432
|
+
if (state.lastIncidentStage === trace.stage) return
|
|
433
|
+
state.lastIncidentStage = trace.stage
|
|
434
|
+
if (onIncident) {
|
|
435
|
+
// A fresh .processing sentinel (mtime >= startedAt) means the turn is
|
|
436
|
+
// genuinely mid-work (e.g. a long sub-agent), not silently wedged.
|
|
437
|
+
const midTurn = obs.processingAt !== null && obs.processingAt >= startedAt
|
|
438
|
+
onIncident(
|
|
439
|
+
{
|
|
440
|
+
chatId,
|
|
441
|
+
stage: trace.stage,
|
|
442
|
+
failureClass: trace.failureClass,
|
|
443
|
+
sinceMs: trace.sinceMs,
|
|
444
|
+
budgetMs: trace.budgetMs,
|
|
445
|
+
midTurn,
|
|
446
|
+
at: Date.now(),
|
|
447
|
+
},
|
|
448
|
+
readTurnEvidence(chatId, obs),
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
333
453
|
async function stop(chatId: string): Promise<void> {
|
|
334
454
|
const state = states.get(chatId)
|
|
335
455
|
if (!state) return
|
|
@@ -430,6 +550,7 @@ export function createWorkingStateManager(
|
|
|
430
550
|
currentReaction: null,
|
|
431
551
|
lastDetail: null,
|
|
432
552
|
recentDetails: [],
|
|
553
|
+
lastIncidentStage: null,
|
|
433
554
|
}
|
|
434
555
|
states.set(chatId, state)
|
|
435
556
|
|
|
@@ -530,6 +651,9 @@ export function createWorkingStateManager(
|
|
|
530
651
|
// Heartbeat file is written by SessionProcess on every Claude stdout line.
|
|
531
652
|
state.stalledInterval = setInterval(async () => {
|
|
532
653
|
if (!states.has(chatId)) return
|
|
654
|
+
// Turn-trace watchdog: staged classification + incident telemetry. Silent
|
|
655
|
+
// and side-effect-free — the heartbeat warn/stop below is unchanged.
|
|
656
|
+
checkTurnTrace(chatId, startedAt)
|
|
533
657
|
const hbPath = heartbeatFilePath(chatId)
|
|
534
658
|
let lastActivity = startedAt
|
|
535
659
|
if (fsApi.existsSync(hbPath)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmaxma/claude-gateway",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.32",
|
|
4
4
|
"description": "Multi-agent gateway for Claude",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"build": "tsc",
|
|
29
29
|
"start": "node --no-warnings=ExperimentalWarning --env-file-if-exists=.env dist/index.js",
|
|
30
30
|
"postinstall": "if command -v bun >/dev/null 2>&1; then bun install --cwd mcp; else echo 'Warning: bun not found. Run `cd mcp && bun install` manually to enable MCP tools.'; fi",
|
|
31
|
+
"pretest": "npm run build",
|
|
31
32
|
"test": "jest",
|
|
33
|
+
"pretest:unit": "npm run build",
|
|
32
34
|
"test:unit": "jest --testPathPattern=unit",
|
|
33
35
|
"test:e2e": "jest --testPathPattern=tests/e2e --forceExit",
|
|
34
36
|
"integration": "jest --testPathPattern=integration --testTimeout=15000",
|