@integrity-labs/agt-cli 0.28.1067 → 0.28.1068
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/bin/agt.js +5 -5
- package/dist/{chunk-OW6XCSOU.js → chunk-65F44JJJ.js} +2 -2
- package/dist/{chunk-PN7B4DLS.js → chunk-CFJONCHN.js} +11 -1
- package/dist/{chunk-PN7B4DLS.js.map → chunk-CFJONCHN.js.map} +1 -1
- package/dist/{chunk-NZF55T6D.js → chunk-WLB2E3PO.js} +7 -4
- package/dist/{chunk-NZF55T6D.js.map → chunk-WLB2E3PO.js.map} +1 -1
- package/dist/{claude-pair-runtime-NCDM5KHM.js → claude-pair-runtime-4BISLPPZ.js} +2 -2
- package/dist/lib/manager-worker.js +163 -14
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +10 -0
- package/dist/mcp/index.js +10 -0
- package/dist/mcp/origami.js +10 -0
- package/dist/mcp/slack-channel.js +10 -0
- package/dist/mcp/telegram-channel.js +10 -0
- package/dist/pane-fatal-error-EL336LRO.js +127 -0
- package/dist/pane-fatal-error-EL336LRO.js.map +1 -0
- package/dist/{persistent-session-ELDC5JFR.js → persistent-session-VRAVB743.js} +3 -3
- package/dist/{responsiveness-probe-HUUU2MFX.js → responsiveness-probe-QTYKXBH7.js} +3 -3
- package/dist/{session-auth-dead-BRMWKA3Y.js → session-auth-dead-JJPNV3YQ.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-OW6XCSOU.js.map → chunk-65F44JJJ.js.map} +0 -0
- /package/dist/{claude-pair-runtime-NCDM5KHM.js.map → claude-pair-runtime-4BISLPPZ.js.map} +0 -0
- /package/dist/{persistent-session-ELDC5JFR.js.map → persistent-session-VRAVB743.js.map} +0 -0
- /package/dist/{responsiveness-probe-HUUU2MFX.js.map → responsiveness-probe-QTYKXBH7.js.map} +0 -0
- /package/dist/{session-auth-dead-BRMWKA3Y.js.map → session-auth-dead-JJPNV3YQ.js.map} +0 -0
|
@@ -38063,6 +38063,16 @@ var FLAG_REGISTRY = [
|
|
|
38063
38063
|
// blocks customer email - so not `sensitive`, unlike the email-guard enable gates above.
|
|
38064
38064
|
sensitive: false
|
|
38065
38065
|
},
|
|
38066
|
+
{
|
|
38067
|
+
key: "approval-core-manager-console",
|
|
38068
|
+
description: "Console approval authority from the routed approver set (ENG-11206, ADR-0124 Phase 0a). When ON for an org, the Approvals page also lets a non-admin decide a request if they are in its resolved approver set: the approval_assignments rows, else the legacy routed_to JSON, else the rule 6 fallback (the agent's manager). The main effect: an agent's manager can decide its integration and broker approvals in the console, which non-admins cannot do today. Owners and admins are unaffected. OFF (default) keeps the console exactly as before: owners/admins, plus the person named in routed_to.person_id. Evaluated per-org. Ships dark.",
|
|
38069
|
+
flagType: "boolean",
|
|
38070
|
+
// Safe/default value is `false` (today's console authority). A flag-DB read error degrades to
|
|
38071
|
+
// this, so an outage can only narrow who may decide, never widen it.
|
|
38072
|
+
defaultValue: false,
|
|
38073
|
+
// It widens who may DECIDE an approval, so it is a control, not a display toggle.
|
|
38074
|
+
sensitive: true
|
|
38075
|
+
},
|
|
38066
38076
|
{
|
|
38067
38077
|
key: "slack-reply-binding",
|
|
38068
38078
|
description: "Slack reply-target binding (ENG-7396 / WS2, cross-thread reply routing): the model names which inbound it answers (inbound_id) and the server resolves the destination from its own verified record. shadow = classify + count only, routing unchanged; warn = same routing plus a tool-result note so the model self-corrects; enforce = route a bound reply to the registry thread and REJECT a reply whose target matches no inbound this process delivered. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_SLACK_REPLY_BINDING spawn env (the operator/canary override, ADR-0022). Ships shadow.",
|
package/dist/mcp/index.js
CHANGED
|
@@ -30281,6 +30281,16 @@ var FLAG_REGISTRY = [
|
|
|
30281
30281
|
// blocks customer email - so not `sensitive`, unlike the email-guard enable gates above.
|
|
30282
30282
|
sensitive: false
|
|
30283
30283
|
},
|
|
30284
|
+
{
|
|
30285
|
+
key: "approval-core-manager-console",
|
|
30286
|
+
description: "Console approval authority from the routed approver set (ENG-11206, ADR-0124 Phase 0a). When ON for an org, the Approvals page also lets a non-admin decide a request if they are in its resolved approver set: the approval_assignments rows, else the legacy routed_to JSON, else the rule 6 fallback (the agent's manager). The main effect: an agent's manager can decide its integration and broker approvals in the console, which non-admins cannot do today. Owners and admins are unaffected. OFF (default) keeps the console exactly as before: owners/admins, plus the person named in routed_to.person_id. Evaluated per-org. Ships dark.",
|
|
30287
|
+
flagType: "boolean",
|
|
30288
|
+
// Safe/default value is `false` (today's console authority). A flag-DB read error degrades to
|
|
30289
|
+
// this, so an outage can only narrow who may decide, never widen it.
|
|
30290
|
+
defaultValue: false,
|
|
30291
|
+
// It widens who may DECIDE an approval, so it is a control, not a display toggle.
|
|
30292
|
+
sensitive: true
|
|
30293
|
+
},
|
|
30284
30294
|
{
|
|
30285
30295
|
key: "slack-reply-binding",
|
|
30286
30296
|
description: "Slack reply-target binding (ENG-7396 / WS2, cross-thread reply routing): the model names which inbound it answers (inbound_id) and the server resolves the destination from its own verified record. shadow = classify + count only, routing unchanged; warn = same routing plus a tool-result note so the model self-corrects; enforce = route a bound reply to the registry thread and REJECT a reply whose target matches no inbound this process delivered. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_SLACK_REPLY_BINDING spawn env (the operator/canary override, ADR-0022). Ships shadow.",
|
package/dist/mcp/origami.js
CHANGED
|
@@ -44204,6 +44204,16 @@ var FLAG_REGISTRY = [
|
|
|
44204
44204
|
// blocks customer email - so not `sensitive`, unlike the email-guard enable gates above.
|
|
44205
44205
|
sensitive: false
|
|
44206
44206
|
},
|
|
44207
|
+
{
|
|
44208
|
+
key: "approval-core-manager-console",
|
|
44209
|
+
description: "Console approval authority from the routed approver set (ENG-11206, ADR-0124 Phase 0a). When ON for an org, the Approvals page also lets a non-admin decide a request if they are in its resolved approver set: the approval_assignments rows, else the legacy routed_to JSON, else the rule 6 fallback (the agent's manager). The main effect: an agent's manager can decide its integration and broker approvals in the console, which non-admins cannot do today. Owners and admins are unaffected. OFF (default) keeps the console exactly as before: owners/admins, plus the person named in routed_to.person_id. Evaluated per-org. Ships dark.",
|
|
44210
|
+
flagType: "boolean",
|
|
44211
|
+
// Safe/default value is `false` (today's console authority). A flag-DB read error degrades to
|
|
44212
|
+
// this, so an outage can only narrow who may decide, never widen it.
|
|
44213
|
+
defaultValue: false,
|
|
44214
|
+
// It widens who may DECIDE an approval, so it is a control, not a display toggle.
|
|
44215
|
+
sensitive: true
|
|
44216
|
+
},
|
|
44207
44217
|
{
|
|
44208
44218
|
key: "slack-reply-binding",
|
|
44209
44219
|
description: "Slack reply-target binding (ENG-7396 / WS2, cross-thread reply routing): the model names which inbound it answers (inbound_id) and the server resolves the destination from its own verified record. shadow = classify + count only, routing unchanged; warn = same routing plus a tool-result note so the model self-corrects; enforce = route a bound reply to the registry thread and REJECT a reply whose target matches no inbound this process delivered. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_SLACK_REPLY_BINDING spawn env (the operator/canary override, ADR-0022). Ships shadow.",
|
|
@@ -38834,6 +38834,16 @@ var FLAG_REGISTRY = [
|
|
|
38834
38834
|
// blocks customer email - so not `sensitive`, unlike the email-guard enable gates above.
|
|
38835
38835
|
sensitive: false
|
|
38836
38836
|
},
|
|
38837
|
+
{
|
|
38838
|
+
key: "approval-core-manager-console",
|
|
38839
|
+
description: "Console approval authority from the routed approver set (ENG-11206, ADR-0124 Phase 0a). When ON for an org, the Approvals page also lets a non-admin decide a request if they are in its resolved approver set: the approval_assignments rows, else the legacy routed_to JSON, else the rule 6 fallback (the agent's manager). The main effect: an agent's manager can decide its integration and broker approvals in the console, which non-admins cannot do today. Owners and admins are unaffected. OFF (default) keeps the console exactly as before: owners/admins, plus the person named in routed_to.person_id. Evaluated per-org. Ships dark.",
|
|
38840
|
+
flagType: "boolean",
|
|
38841
|
+
// Safe/default value is `false` (today's console authority). A flag-DB read error degrades to
|
|
38842
|
+
// this, so an outage can only narrow who may decide, never widen it.
|
|
38843
|
+
defaultValue: false,
|
|
38844
|
+
// It widens who may DECIDE an approval, so it is a control, not a display toggle.
|
|
38845
|
+
sensitive: true
|
|
38846
|
+
},
|
|
38837
38847
|
{
|
|
38838
38848
|
key: "slack-reply-binding",
|
|
38839
38849
|
description: "Slack reply-target binding (ENG-7396 / WS2, cross-thread reply routing): the model names which inbound it answers (inbound_id) and the server resolves the destination from its own verified record. shadow = classify + count only, routing unchanged; warn = same routing plus a tool-result note so the model self-corrects; enforce = route a bound reply to the registry thread and REJECT a reply whose target matches no inbound this process delivered. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_SLACK_REPLY_BINDING spawn env (the operator/canary override, ADR-0022). Ships shadow.",
|
|
@@ -38940,6 +38940,16 @@ var FLAG_REGISTRY = [
|
|
|
38940
38940
|
// blocks customer email - so not `sensitive`, unlike the email-guard enable gates above.
|
|
38941
38941
|
sensitive: false
|
|
38942
38942
|
},
|
|
38943
|
+
{
|
|
38944
|
+
key: "approval-core-manager-console",
|
|
38945
|
+
description: "Console approval authority from the routed approver set (ENG-11206, ADR-0124 Phase 0a). When ON for an org, the Approvals page also lets a non-admin decide a request if they are in its resolved approver set: the approval_assignments rows, else the legacy routed_to JSON, else the rule 6 fallback (the agent's manager). The main effect: an agent's manager can decide its integration and broker approvals in the console, which non-admins cannot do today. Owners and admins are unaffected. OFF (default) keeps the console exactly as before: owners/admins, plus the person named in routed_to.person_id. Evaluated per-org. Ships dark.",
|
|
38946
|
+
flagType: "boolean",
|
|
38947
|
+
// Safe/default value is `false` (today's console authority). A flag-DB read error degrades to
|
|
38948
|
+
// this, so an outage can only narrow who may decide, never widen it.
|
|
38949
|
+
defaultValue: false,
|
|
38950
|
+
// It widens who may DECIDE an approval, so it is a control, not a display toggle.
|
|
38951
|
+
sensitive: true
|
|
38952
|
+
},
|
|
38943
38953
|
{
|
|
38944
38954
|
key: "slack-reply-binding",
|
|
38945
38955
|
description: "Slack reply-target binding (ENG-7396 / WS2, cross-thread reply routing): the model names which inbound it answers (inbound_id) and the server resolves the destination from its own verified record. shadow = classify + count only, routing unchanged; warn = same routing plus a tool-result note so the model self-corrects; enforce = route a bound reply to the registry thread and REJECT a reply whose target matches no inbound this process delivered. The channel server can't evaluate flags, so the manager materializes the resolved value into the AGT_SLACK_REPLY_BINDING spawn env (the operator/canary override, ADR-0022). Ships shadow.",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/lib/pane-fatal-error.ts
|
|
4
|
+
function isRespawnKind(kind) {
|
|
5
|
+
return kind === "compaction-failed" || kind === "prompt-too-long";
|
|
6
|
+
}
|
|
7
|
+
var DEFAULTS = {
|
|
8
|
+
minCycles: 2,
|
|
9
|
+
maxRestartsInWindow: 3,
|
|
10
|
+
restartWindowMs: 6 * 60 * 60 * 1e3
|
|
11
|
+
};
|
|
12
|
+
var MIN_FATAL_TURNS = 2;
|
|
13
|
+
var RULE = /^\s*[─━]{8,}/;
|
|
14
|
+
var MODEL_HEAD = /^\s*⏺/;
|
|
15
|
+
var STATUS_HEAD = /^[✻✢✳✶✽✺✹·*]/;
|
|
16
|
+
var RESULT_LINE = /^\s+⎿\s*(.*)$/;
|
|
17
|
+
var COMPACTION_FAILED = /^(?:Prompt is too long · automatic compaction failed\b|Error during compaction\b)/;
|
|
18
|
+
var PROMPT_TOO_LONG = /^Prompt is too long\b.*\/clear to start fresh/;
|
|
19
|
+
var TRANSCRIPT_WRITES_FAILING = /Transcript writes are failing/;
|
|
20
|
+
function headType(line) {
|
|
21
|
+
if (MODEL_HEAD.test(line)) return "model";
|
|
22
|
+
if (line.length === 0 || /^\s/.test(line)) return null;
|
|
23
|
+
if (line.startsWith("\u2190")) return "turn";
|
|
24
|
+
if (line.startsWith("\u276F")) return line.slice(1).trim().length > 0 ? "turn" : "empty-prompt";
|
|
25
|
+
if (STATUS_HEAD.test(line)) return "status";
|
|
26
|
+
return "other";
|
|
27
|
+
}
|
|
28
|
+
function parseBlocks(lines) {
|
|
29
|
+
const blocks = [];
|
|
30
|
+
let turn = null;
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const head = headType(line);
|
|
33
|
+
if (head === null) {
|
|
34
|
+
if (turn && !turn.sawContinuation && line.trim().length > 0) {
|
|
35
|
+
turn.sawContinuation = true;
|
|
36
|
+
const m = RESULT_LINE.exec(line);
|
|
37
|
+
turn.result = m ? m[1] : null;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
turn = null;
|
|
42
|
+
if (head === "model") blocks.push({ type: "model" });
|
|
43
|
+
else if (head === "turn") {
|
|
44
|
+
turn = { type: "turn", result: null, sawContinuation: false };
|
|
45
|
+
blocks.push(turn);
|
|
46
|
+
} else blocks.push({ type: "neutral" });
|
|
47
|
+
}
|
|
48
|
+
return blocks;
|
|
49
|
+
}
|
|
50
|
+
function fatalKindOf(result) {
|
|
51
|
+
if (COMPACTION_FAILED.test(result)) return "compaction-failed";
|
|
52
|
+
if (PROMPT_TOO_LONG.test(result)) return "prompt-too-long";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function classifyPane(pane) {
|
|
56
|
+
const clean = { kind: null, fatalTurns: 0, transcriptBanner: false };
|
|
57
|
+
if (!pane) return clean;
|
|
58
|
+
const lines = pane.split("\n").map((l) => l.replace(/\s+$/, ""));
|
|
59
|
+
const rules = [];
|
|
60
|
+
lines.forEach((l, i) => {
|
|
61
|
+
if (RULE.test(l)) rules.push(i);
|
|
62
|
+
});
|
|
63
|
+
const lastRule = rules.length > 0 ? rules[rules.length - 1] : -1;
|
|
64
|
+
const statusBar = lastRule >= 0 ? lines.slice(lastRule + 1) : [];
|
|
65
|
+
const transcriptEnd = rules.length >= 2 ? rules[rules.length - 2] : lastRule >= 0 ? lastRule : lines.length;
|
|
66
|
+
const transcriptBanner = statusBar.some((l) => TRANSCRIPT_WRITES_FAILING.test(l));
|
|
67
|
+
let fatalTurns = 0;
|
|
68
|
+
let lastFatal = null;
|
|
69
|
+
for (const block of parseBlocks(lines.slice(0, transcriptEnd))) {
|
|
70
|
+
if (block.type === "model") {
|
|
71
|
+
fatalTurns = 0;
|
|
72
|
+
lastFatal = null;
|
|
73
|
+
} else if (block.type === "turn" && block.result !== null) {
|
|
74
|
+
const kind2 = fatalKindOf(block.result);
|
|
75
|
+
if (kind2) {
|
|
76
|
+
fatalTurns += 1;
|
|
77
|
+
lastFatal = kind2;
|
|
78
|
+
} else {
|
|
79
|
+
fatalTurns = 0;
|
|
80
|
+
lastFatal = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const kind = fatalTurns >= MIN_FATAL_TURNS && lastFatal ? lastFatal : transcriptBanner ? "transcript-writes-failing" : null;
|
|
85
|
+
return { kind, fatalTurns, transcriptBanner };
|
|
86
|
+
}
|
|
87
|
+
function classifyPaneFatalError(pane) {
|
|
88
|
+
return classifyPane(pane).kind;
|
|
89
|
+
}
|
|
90
|
+
function parseMode(raw) {
|
|
91
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
92
|
+
if (v === "off" || v === "shadow" || v === "enforce") return v;
|
|
93
|
+
return "enforce";
|
|
94
|
+
}
|
|
95
|
+
function parseIntAtLeast(raw, fallback, floor) {
|
|
96
|
+
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
97
|
+
return Number.isInteger(n) && n >= floor ? n : fallback;
|
|
98
|
+
}
|
|
99
|
+
function resolvePaneFatalConfig(env = process.env) {
|
|
100
|
+
return {
|
|
101
|
+
mode: parseMode(env.AGT_PANE_FATAL_RESTART_MODE),
|
|
102
|
+
minCycles: parseIntAtLeast(env.AGT_PANE_FATAL_MIN_CYCLES, DEFAULTS.minCycles, 2),
|
|
103
|
+
maxRestartsInWindow: parseIntAtLeast(env.AGT_PANE_FATAL_MAX_RESTARTS, DEFAULTS.maxRestartsInWindow, 1),
|
|
104
|
+
restartWindowMs: parseIntAtLeast(env.AGT_PANE_FATAL_WINDOW_MINUTES, DEFAULTS.restartWindowMs / 6e4, 30) * 6e4
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function decidePaneFatalAction(input, config) {
|
|
108
|
+
if (config.mode === "off" || input.kind === null) return "none";
|
|
109
|
+
if (input.consecutiveCycles < config.minCycles) return "none";
|
|
110
|
+
if (!isRespawnKind(input.kind)) return "warn";
|
|
111
|
+
if (config.mode === "shadow") return "would-restart";
|
|
112
|
+
const inWindow = input.recentRestartsMs.filter((t) => input.nowMs - t < config.restartWindowMs).length;
|
|
113
|
+
return inWindow >= config.maxRestartsInWindow ? "give-up" : "restart";
|
|
114
|
+
}
|
|
115
|
+
function pruneRestartHistory(history, nowMs, config) {
|
|
116
|
+
return history.filter((t) => nowMs - t < config.restartWindowMs);
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
MIN_FATAL_TURNS,
|
|
120
|
+
classifyPane,
|
|
121
|
+
classifyPaneFatalError,
|
|
122
|
+
decidePaneFatalAction,
|
|
123
|
+
isRespawnKind,
|
|
124
|
+
pruneRestartHistory,
|
|
125
|
+
resolvePaneFatalConfig
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=pane-fatal-error-EL336LRO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/pane-fatal-error.ts"],"sourcesContent":["/**\n * ENG-11278 — a session that answers every turn with a fatal error is not healthy.\n *\n * ## The incident\n *\n * Pedders host v8, 2026-10-02. marty sat for ~2h20m, and grace for hours, replying\n * to every inbound message with\n *\n * ⎿ Prompt is too long · automatic compaction failed: ENOENT: no such file or\n * directory, mkdir '/root/.claude/projects/-root--augmented-grace-project' ·\n * /clear to start fresh\n *\n * while manager.log said `decision=healthy … session_healthy_after=true` on every\n * poll. Nothing was going to change that: `isSessionHealthy` checks tmux + the\n * claude process (both fine), and the ENG-6153 wedge detector keys on UNDRAINED\n * inbound — but this session drains every message, it just answers each one with\n * the error. Its sibling condition, `⚠ Transcript writes are failing (ENOENT)`,\n * is drawn only in Claude Code's status bar (ENG-10957 found it by accident on\n * ada): no log line, no alert, health green.\n *\n * ## What this module decides\n *\n * Pure, no I/O, so the rules are unit-testable (same split as\n * `wedge-detection.ts`). The poll loop owns the pane capture, the per-agent\n * streak and the restart history, and performs the respawn.\n *\n * - {@link classifyPaneFatalError}: is the pane CURRENTLY showing a fatal,\n * self-perpetuating error? Returns a kind, never pane text.\n * - {@link decidePaneFatalAction}: given that verdict, the streak and the\n * recent restarts, what should the manager do this cycle?\n *\n * ## Reading the pane: blocks, not lines (Obi Wan 1436d0c3a9e5, d2dc0c460de5, 50a5cb896fad)\n *\n * The first version scanned every line for the fatal phrases and only excluded\n * `⏺` heads and `←` heads. That read the phrase anywhere else: on a continuation\n * line of the model's own reply, in a `⎿` tool result (a `Bash` that greps\n * manager.log), in a later line of an inbound message. Any of those could\n * force-respawn a working agent and page, and an untrusted sender could put\n * them there on purpose. So the pane is parsed the way Claude Code draws it.\n *\n * The capture is `tmux capture-pane -p -J` (wrapped lines joined), and splits\n * into three regions around the prompt box, whose rules (`────`) are always the\n * bottom of the screen:\n *\n * …transcript… <- turns, model output, status lines\n * ────────────── <- prompt box top\n * ❯ <what is being typed>\n * ────────────── <- prompt box bottom = the LAST rule on screen\n * ⏵⏵ bypass … · ⚠ Transcript writes are failing (ENOENT) <- status bar\n *\n * The transcript is a sequence of BLOCKS. A block head sits at column 0:\n * `⏺` model output (text or a tool call), `←` an inbound channel message, `❯`\n * a submitted prompt, a spinner glyph (`✻ Cogitated for 1m 29s · done 5:26 PM`)\n * for status. Everything indented under a head belongs to it, including the\n * `⎿` lines: a tool's output under its `⏺`, a tip under a spinner, and a turn's\n * RESULT under its `←`/`❯` head. Claude Code indents every continuation, and\n * `-J` stops a long line wrapping into a fake column-0 head, so a reply line\n * that happens to begin with `←` or `⎿` stays inside its `⏺` block.\n *\n * A genuine turn failure has exactly one shape — the turn's FIRST continuation\n * line is a `⎿` carrying Claude Code's error, attached directly to the inbound\n * or prompt that caused it (grace on v8, verbatim):\n *\n * ← telegram · 8837161157: Yes I think it’s best to start on standard…\n * ⎿ Prompt is too long · automatic compaction failed: ENOENT: … · /clear to start fresh\n *\n * Only that counts, and only when it is the state of the session NOW: at least\n * {@link MIN_FATAL_TURNS} consecutive turns ending that way with no model output\n * after the first of them (a wedged session fails EVERY turn; a recovered one\n * shows `⏺` after the old error). A single failed turn, a quoted phrase, or a\n * tool printing the log is never enough.\n *\n * The `Transcript writes are failing` banner is matched only in the status bar,\n * and on its own is NOT fatal: a session can show it and keep answering. It\n * yields `transcript-writes-failing`, which the decision maps to a warning\n * alert, never a respawn. Its root cause (an orphaned transcript bind) is\n * repaired by ENG-11277's mount probe.\n *\n * Hysteresis ({@link PaneFatalConfig.minCycles}) covers the remaining window, a\n * capture taken between an error and the model's next output.\n */\n\nexport type PaneFatalKind =\n /** `automatic compaction failed: …` or a manual `/compact` that errored. */\n | 'compaction-failed'\n /** `Prompt is too long · … · /clear to start fresh` without a compaction clause. */\n | 'prompt-too-long'\n /**\n * Claude Code's status-bar warning that the session transcript cannot be\n * written, with no failing turns. Warn-only: never a respawn on its own.\n */\n | 'transcript-writes-failing';\n\n/** Kinds that mean the session cannot answer at all, so a fresh respawn is the remedy. */\nexport function isRespawnKind(kind: PaneFatalKind | null): kind is 'compaction-failed' | 'prompt-too-long' {\n return kind === 'compaction-failed' || kind === 'prompt-too-long';\n}\n\nexport interface PaneFatalVerdict {\n /** Respawn kind when the session is failing every turn; else the banner kind; else null. */\n kind: PaneFatalKind | null;\n /** Consecutive most-recent turns whose result was a fatal error. */\n fatalTurns: number;\n /** `Transcript writes are failing` is in the status bar. */\n transcriptBanner: boolean;\n}\n\nexport type PaneFatalMode = 'off' | 'shadow' | 'enforce';\n\nexport interface PaneFatalConfig {\n mode: PaneFatalMode;\n /** Consecutive poll cycles a kind must be seen before acting. */\n minCycles: number;\n /** Max fatal-error respawns per agent inside {@link restartWindowMs}. */\n maxRestartsInWindow: number;\n /** Sliding window for {@link maxRestartsInWindow}. */\n restartWindowMs: number;\n}\n\nconst DEFAULTS = {\n minCycles: 2,\n maxRestartsInWindow: 3,\n restartWindowMs: 6 * 60 * 60 * 1000,\n} as const;\n\n/**\n * Consecutive failed turns required on one screen. Grace's pane shows the error\n * once per inbound; one failed turn is a bad turn, two in a row with nothing\n * from the model in between is a session that cannot answer.\n */\nexport const MIN_FATAL_TURNS = 2;\n\n/** A prompt-box rule. Claude Code draws it full width; 8 is a floor, not a guess at width. */\nconst RULE = /^\\s*[─━]{8,}/;\n/** `⏺` anywhere a line starts (indent tolerated): any model output resets the streak. */\nconst MODEL_HEAD = /^\\s*⏺/;\n/** Spinner / timing heads: `✻ Cogitated for 1m 29s · done 5:26 PM`, `* Churning…`. */\nconst STATUS_HEAD = /^[✻✢✳✶✽✺✹·*]/;\n/** A turn's result line: indented, then `⎿`. Captures the text after the glyph. */\nconst RESULT_LINE = /^\\s+⎿\\s*(.*)$/;\n/**\n * The error text Claude Code renders as a turn result, anchored at the START of\n * the `⎿` text so a result that merely contains the phrase does not match.\n */\nconst COMPACTION_FAILED = /^(?:Prompt is too long · automatic compaction failed\\b|Error during compaction\\b)/;\nconst PROMPT_TOO_LONG = /^Prompt is too long\\b.*\\/clear to start fresh/;\nconst TRANSCRIPT_WRITES_FAILING = /Transcript writes are failing/;\n\ntype Block =\n | { type: 'model' }\n | { type: 'turn'; result: string | null }\n | { type: 'neutral' };\n\nfunction headType(line: string): 'model' | 'turn' | 'empty-prompt' | 'status' | 'other' | null {\n if (MODEL_HEAD.test(line)) return 'model';\n if (line.length === 0 || /^\\s/.test(line)) return null; // continuation\n if (line.startsWith('←')) return 'turn';\n if (line.startsWith('❯')) return line.slice(1).trim().length > 0 ? 'turn' : 'empty-prompt';\n if (STATUS_HEAD.test(line)) return 'status';\n return 'other';\n}\n\nfunction parseBlocks(lines: readonly string[]): Block[] {\n const blocks: Block[] = [];\n let turn: { type: 'turn'; result: string | null; sawContinuation: boolean } | null = null;\n for (const line of lines) {\n const head = headType(line);\n if (head === null) {\n // Continuation of the current block. For a turn, only the FIRST non-blank\n // continuation line can be its result; anything after it is the message's\n // own text or later output, never Claude Code's verdict on the turn.\n if (turn && !turn.sawContinuation && line.trim().length > 0) {\n turn.sawContinuation = true;\n const m = RESULT_LINE.exec(line);\n turn.result = m ? m[1]! : null;\n }\n continue;\n }\n turn = null;\n if (head === 'model') blocks.push({ type: 'model' });\n else if (head === 'turn') {\n turn = { type: 'turn', result: null, sawContinuation: false };\n blocks.push(turn);\n } else blocks.push({ type: 'neutral' });\n }\n return blocks;\n}\n\nfunction fatalKindOf(result: string): 'compaction-failed' | 'prompt-too-long' | null {\n if (COMPACTION_FAILED.test(result)) return 'compaction-failed';\n if (PROMPT_TOO_LONG.test(result)) return 'prompt-too-long';\n return null;\n}\n\n/**\n * Parse a `tmux capture-pane -p -J` capture. Pure; never returns pane text.\n * An unreadable (null/empty) capture is \"can't tell\", which the caller treats\n * as healthy, never as a reason to respawn.\n */\nexport function classifyPane(pane: string | null | undefined): PaneFatalVerdict {\n const clean: PaneFatalVerdict = { kind: null, fatalTurns: 0, transcriptBanner: false };\n if (!pane) return clean;\n const lines = pane.split('\\n').map((l) => l.replace(/\\s+$/, ''));\n\n const rules: number[] = [];\n lines.forEach((l, i) => {\n if (RULE.test(l)) rules.push(i);\n });\n // No prompt box on screen: no status bar to read, so the banner fails closed.\n const lastRule = rules.length > 0 ? rules[rules.length - 1]! : -1;\n const statusBar = lastRule >= 0 ? lines.slice(lastRule + 1) : [];\n const transcriptEnd = rules.length >= 2 ? rules[rules.length - 2]! : lastRule >= 0 ? lastRule : lines.length;\n const transcriptBanner = statusBar.some((l) => TRANSCRIPT_WRITES_FAILING.test(l));\n\n let fatalTurns = 0;\n let lastFatal: 'compaction-failed' | 'prompt-too-long' | null = null;\n for (const block of parseBlocks(lines.slice(0, transcriptEnd))) {\n if (block.type === 'model') {\n fatalTurns = 0; // the model answered: whatever failed before is history\n lastFatal = null;\n } else if (block.type === 'turn' && block.result !== null) {\n const kind = fatalKindOf(block.result);\n if (kind) {\n fatalTurns += 1;\n lastFatal = kind;\n } else {\n fatalTurns = 0; // the turn produced a normal result (`/clear`, `/compact` ok)\n lastFatal = null;\n }\n }\n // A turn with no result yet is pending; neutral blocks say nothing.\n }\n\n const kind: PaneFatalKind | null =\n fatalTurns >= MIN_FATAL_TURNS && lastFatal\n ? lastFatal\n : transcriptBanner\n ? 'transcript-writes-failing'\n : null;\n return { kind, fatalTurns, transcriptBanner };\n}\n\n/** {@link classifyPane}'s kind alone. */\nexport function classifyPaneFatalError(pane: string | null | undefined): PaneFatalKind | null {\n return classifyPane(pane).kind;\n}\n\nfunction parseMode(raw: string | undefined): PaneFatalMode {\n const v = (raw ?? '').trim().toLowerCase();\n if (v === 'off' || v === 'shadow' || v === 'enforce') return v;\n // Default ENFORCE, unlike the ENG-6153 wedge detector's shadow default. That\n // detector infers a wedge from timing heuristics that can misread a long\n // turn; this one matches Claude Code's own fatal-error text, which no\n // working session displays as its latest result. The cost of the wrong\n // default is asymmetric too: shadow here is what v8 already had, an agent\n // answering every message with an error for hours while health read green.\n // The restart budget below bounds the downside of enforce.\n return 'enforce';\n}\n\nfunction parseIntAtLeast(raw: string | undefined, fallback: number, floor: number): number {\n const n = raw ? Number.parseInt(raw, 10) : NaN;\n return Number.isInteger(n) && n >= floor ? n : fallback;\n}\n\n/**\n * Config from the environment:\n * AGT_PANE_FATAL_RESTART_MODE off | shadow | enforce (default enforce)\n * AGT_PANE_FATAL_MIN_CYCLES default 2, floor 2 (one sighting never acts)\n * AGT_PANE_FATAL_MAX_RESTARTS default 3 per window, floor 1\n * AGT_PANE_FATAL_WINDOW_MINUTES default 360, floor 30\n */\nexport function resolvePaneFatalConfig(env: NodeJS.ProcessEnv = process.env): PaneFatalConfig {\n return {\n mode: parseMode(env.AGT_PANE_FATAL_RESTART_MODE),\n minCycles: parseIntAtLeast(env.AGT_PANE_FATAL_MIN_CYCLES, DEFAULTS.minCycles, 2),\n maxRestartsInWindow: parseIntAtLeast(env.AGT_PANE_FATAL_MAX_RESTARTS, DEFAULTS.maxRestartsInWindow, 1),\n restartWindowMs:\n parseIntAtLeast(env.AGT_PANE_FATAL_WINDOW_MINUTES, DEFAULTS.restartWindowMs / 60_000, 30) * 60_000,\n };\n}\n\nexport type PaneFatalAction =\n /** Nothing to do: no fatal kind, streak too short, or detection off. */\n | 'none'\n /**\n * The transcript-writes banner alone: the session is still answering, so do\n * NOT respawn it. Log and raise a warning recommending a restart (shadow and\n * enforce alike: a warning touches nothing).\n */\n | 'warn'\n /** Shadow mode: would restart. Log once per episode, do nothing. */\n | 'would-restart'\n /** Enforce: force a fresh respawn now. */\n | 'restart'\n /**\n * The restart budget is spent and the condition is back. A respawn has\n * demonstrably not cleared it, so another would be a restart loop: stop\n * restarting and escalate to a human instead.\n */\n | 'give-up';\n\n/**\n * What to do this cycle. `consecutiveCycles` INCLUDES this one; `recentRestartsMs`\n * are the timestamps of earlier fatal-error respawns of this agent.\n */\nexport function decidePaneFatalAction(\n input: {\n kind: PaneFatalKind | null;\n consecutiveCycles: number;\n recentRestartsMs: readonly number[];\n nowMs: number;\n },\n config: PaneFatalConfig,\n): PaneFatalAction {\n if (config.mode === 'off' || input.kind === null) return 'none';\n if (input.consecutiveCycles < config.minCycles) return 'none';\n if (!isRespawnKind(input.kind)) return 'warn';\n if (config.mode === 'shadow') return 'would-restart';\n const inWindow = input.recentRestartsMs.filter((t) => input.nowMs - t < config.restartWindowMs).length;\n return inWindow >= config.maxRestartsInWindow ? 'give-up' : 'restart';\n}\n\n/** Drop restart timestamps that have aged out of the window (bounded memory). */\nexport function pruneRestartHistory(\n history: readonly number[],\n nowMs: number,\n config: PaneFatalConfig,\n): number[] {\n return history.filter((t) => nowMs - t < config.restartWindowMs);\n}\n"],"mappings":"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","names":["kind"]}
|
|
@@ -65,8 +65,8 @@ import {
|
|
|
65
65
|
writeDirectChatSessionState,
|
|
66
66
|
writeEgressAllowlist,
|
|
67
67
|
writePersistentClaudeWrapper
|
|
68
|
-
} from "./chunk-
|
|
69
|
-
import "./chunk-
|
|
68
|
+
} from "./chunk-65F44JJJ.js";
|
|
69
|
+
import "./chunk-CFJONCHN.js";
|
|
70
70
|
import "./chunk-OCMJ5E6J.js";
|
|
71
71
|
import "./chunk-XWVM4KPK.js";
|
|
72
72
|
import "./chunk-EL5MWGGH.js";
|
|
@@ -139,4 +139,4 @@ export {
|
|
|
139
139
|
writeEgressAllowlist,
|
|
140
140
|
writePersistentClaudeWrapper
|
|
141
141
|
};
|
|
142
|
-
//# sourceMappingURL=persistent-session-
|
|
142
|
+
//# sourceMappingURL=persistent-session-VRAVB743.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-65F44JJJ.js";
|
|
4
|
+
import "./chunk-CFJONCHN.js";
|
|
5
5
|
import "./chunk-OCMJ5E6J.js";
|
|
6
6
|
import "./chunk-XWVM4KPK.js";
|
|
7
7
|
import "./chunk-EL5MWGGH.js";
|
|
@@ -802,4 +802,4 @@ export {
|
|
|
802
802
|
readAndResetSlackReplyBindingClassifications,
|
|
803
803
|
readAndResetSlackReplyTargetClassifications
|
|
804
804
|
};
|
|
805
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
805
|
+
//# sourceMappingURL=responsiveness-probe-QTYKXBH7.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionTranscriptDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CFJONCHN.js";
|
|
4
4
|
import "./chunk-DGUM43GV.js";
|
|
5
5
|
|
|
6
6
|
// src/lib/session-auth-dead.ts
|
|
@@ -204,4 +204,4 @@ export {
|
|
|
204
204
|
decideSessionAuthState,
|
|
205
205
|
probeSessionAuth
|
|
206
206
|
};
|
|
207
|
-
//# sourceMappingURL=session-auth-dead-
|
|
207
|
+
//# sourceMappingURL=session-auth-dead-JJPNV3YQ.js.map
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|