switchroom 0.7.13 → 0.7.15
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 +4 -4
- package/dist/agent-scheduler/index.js +80 -80
- package/dist/cli/switchroom.js +1881 -2878
- package/dist/vault/approvals/kernel-server.js +88 -85
- package/dist/vault/broker/server.js +495 -1785
- package/package.json +2 -4
- package/telegram-plugin/bridge/bridge.ts +17 -0
- package/telegram-plugin/dist/bridge/bridge.js +128 -112
- package/telegram-plugin/dist/foreman/foreman.js +185 -1696
- package/telegram-plugin/dist/gateway/gateway.js +542 -1682
- package/telegram-plugin/dist/server.js +176 -160
- package/telegram-plugin/gateway/gateway.ts +495 -7
- package/telegram-plugin/secret-detect/vault-error.test.ts +134 -0
- package/telegram-plugin/secret-detect/vault-error.ts +202 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/telegram-plugin/server.js +0 -41795
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the vault-CLI error parser + renderer (issue #969 P0b).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { parseVaultCliError, renderVaultCliError } from "./vault-error.js";
|
|
7
|
+
|
|
8
|
+
describe("parseVaultCliError", () => {
|
|
9
|
+
it("classifies VAULT-SANDBOX-CONTEXT", () => {
|
|
10
|
+
const stderr =
|
|
11
|
+
"VAULT-SANDBOX-CONTEXT: direct vault access is unavailable inside an " +
|
|
12
|
+
"agent sandbox. The vault file is not mounted into agent containers; " +
|
|
13
|
+
"only the broker socket is. Run 'switchroom vault init' on the host shell, " +
|
|
14
|
+
"or use a broker-supported operation.";
|
|
15
|
+
const err = parseVaultCliError(stderr);
|
|
16
|
+
expect(err.kind).toBe("sandbox_context");
|
|
17
|
+
// The hint contains 'switchroom vault init' — parser must skip it.
|
|
18
|
+
expect(err.key).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("classifies VAULT-NEEDS-APPROVAL and extracts the affected key", () => {
|
|
22
|
+
const stderr =
|
|
23
|
+
"VAULT-NEEDS-APPROVAL [unknown_key]: secret 'telegram_bot_token_klanker_20260510' " +
|
|
24
|
+
"does not exist in the vault yet. Agents can rotate existing keys via " +
|
|
25
|
+
"the broker but cannot create new ones; this requires operator approval.";
|
|
26
|
+
const err = parseVaultCliError(stderr);
|
|
27
|
+
expect(err.kind).toBe("needs_approval");
|
|
28
|
+
expect(err.key).toBe("telegram_bot_token_klanker_20260510");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("classifies VAULT-BROKER-UNREACHABLE", () => {
|
|
32
|
+
const stderr =
|
|
33
|
+
"VAULT-BROKER-UNREACHABLE: cannot reach vault broker " +
|
|
34
|
+
"(ENOENT: no such file or directory, connect '/run/switchroom/broker/sock'). " +
|
|
35
|
+
"From inside the agent sandbox, direct vault access is not possible.";
|
|
36
|
+
const err = parseVaultCliError(stderr);
|
|
37
|
+
expect(err.kind).toBe("broker_unreachable");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("classifies VAULT-BROKER-DENIED and extracts the KEY (not the agent name)", () => {
|
|
41
|
+
// Regression test: the canonical stderr has TWO single-quoted
|
|
42
|
+
// tokens — the agent name and the key name. The parser must pick
|
|
43
|
+
// the key (anchored after "key '") so the rendered host hint
|
|
44
|
+
// suggests granting access to the right key.
|
|
45
|
+
const stderr =
|
|
46
|
+
"VAULT-BROKER-DENIED [DENIED]: agent 'klanker' is not in the allow list for key 'shared_token'";
|
|
47
|
+
const err = parseVaultCliError(stderr);
|
|
48
|
+
expect(err.kind).toBe("broker_denied");
|
|
49
|
+
expect(err.key).toBe("shared_token");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns 'other' for unrecognised errors", () => {
|
|
53
|
+
const err = parseVaultCliError("Error: something broke\nstack trace …");
|
|
54
|
+
expect(err.kind).toBe("other");
|
|
55
|
+
expect(err.key).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("handles empty / undefined stderr gracefully", () => {
|
|
59
|
+
expect(parseVaultCliError("").kind).toBe("other");
|
|
60
|
+
expect(parseVaultCliError(undefined as unknown as string).kind).toBe("other");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("renderVaultCliError", () => {
|
|
65
|
+
it("renders sandbox_context with a host-CLI suggestion", () => {
|
|
66
|
+
const out = renderVaultCliError(
|
|
67
|
+
{ kind: "sandbox_context", original: "x" },
|
|
68
|
+
{ verb: "set", key: "my_key" },
|
|
69
|
+
);
|
|
70
|
+
expect(out.suppressRaw).toBe(true);
|
|
71
|
+
expect(out.html).toContain("must run on the host");
|
|
72
|
+
expect(out.html).toContain("switchroom vault set my_key");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("renders needs_approval with the affected key + host hint + P1a teaser", () => {
|
|
76
|
+
const out = renderVaultCliError(
|
|
77
|
+
{ kind: "needs_approval", original: "x", key: "telegram_bot_token" },
|
|
78
|
+
{ verb: "save" },
|
|
79
|
+
);
|
|
80
|
+
expect(out.suppressRaw).toBe(true);
|
|
81
|
+
expect(out.html).toContain("operator approval required");
|
|
82
|
+
expect(out.html).toContain("<code>telegram_bot_token</code>");
|
|
83
|
+
expect(out.html).toContain("switchroom vault set telegram_bot_token");
|
|
84
|
+
expect(out.html).toContain("P1a"); // forward-pointer to the upcoming flow
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders broker_unreachable with the status command", () => {
|
|
88
|
+
const out = renderVaultCliError(
|
|
89
|
+
{ kind: "broker_unreachable", original: "x" },
|
|
90
|
+
{ verb: "set" },
|
|
91
|
+
);
|
|
92
|
+
expect(out.suppressRaw).toBe(true);
|
|
93
|
+
expect(out.html).toContain("broker isn't reachable");
|
|
94
|
+
expect(out.html).toContain("switchroom vault broker status");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders broker_denied with a grant command + key", () => {
|
|
98
|
+
const out = renderVaultCliError(
|
|
99
|
+
{ kind: "broker_denied", original: "x", key: "shared_token" },
|
|
100
|
+
{ verb: "set" },
|
|
101
|
+
);
|
|
102
|
+
expect(out.suppressRaw).toBe(true);
|
|
103
|
+
expect(out.html).toContain("refused the request");
|
|
104
|
+
expect(out.html).toContain("switchroom vault grant");
|
|
105
|
+
expect(out.html).toContain("shared_token");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("prefers verbHint.key over parser-extracted key (verbHint wins for host suggestion)", () => {
|
|
109
|
+
// The gateway always knows the key the user asked for; rendering
|
|
110
|
+
// must use that over any heuristic extraction so a parser glitch
|
|
111
|
+
// can't surface the wrong key in the host command suggestion.
|
|
112
|
+
const out = renderVaultCliError(
|
|
113
|
+
{ kind: "broker_denied", original: "x", key: "parser-extracted" },
|
|
114
|
+
{ verb: "set", key: "gateway-supplied" },
|
|
115
|
+
);
|
|
116
|
+
expect(out.html).toContain("gateway-supplied");
|
|
117
|
+
expect(out.html).not.toContain("parser-extracted");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("returns suppressRaw=false for 'other' so the gateway falls back to a raw pre-block", () => {
|
|
121
|
+
const out = renderVaultCliError({ kind: "other", original: "weird error" }, { verb: "set" });
|
|
122
|
+
expect(out.suppressRaw).toBe(false);
|
|
123
|
+
expect(out.html).toBe("");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("escapes HTML special characters in the key", () => {
|
|
127
|
+
const out = renderVaultCliError(
|
|
128
|
+
{ kind: "needs_approval", original: "x", key: "key<with>html" },
|
|
129
|
+
{ verb: "save" },
|
|
130
|
+
);
|
|
131
|
+
expect(out.html).not.toContain("<with>");
|
|
132
|
+
expect(out.html).toContain("key<with>html");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault CLI error classification (issue #969 P0b).
|
|
3
|
+
*
|
|
4
|
+
* `switchroom vault` emits stable stderr markers + exit codes when it
|
|
5
|
+
* detects a failure mode the Telegram gateway should surface with
|
|
6
|
+
* specific UX rather than a raw pre-block of error text. This module
|
|
7
|
+
* parses those markers (introduced in #971 / P0a) and translates them
|
|
8
|
+
* into a structured result the gateway can render against.
|
|
9
|
+
*
|
|
10
|
+
* Markers (from `src/cli/vault.ts`):
|
|
11
|
+
*
|
|
12
|
+
* VAULT-SANDBOX-CONTEXT (exit 7) — direct vault file IO is
|
|
13
|
+
* unavailable inside an agent
|
|
14
|
+
* container; the requested verb
|
|
15
|
+
* has no broker equivalent.
|
|
16
|
+
*
|
|
17
|
+
* VAULT-NEEDS-APPROVAL (exit 5) — agent tried to write to a key
|
|
18
|
+
* that doesn't exist yet. Broker
|
|
19
|
+
* PUT cannot introduce new keys;
|
|
20
|
+
* operator approval is required.
|
|
21
|
+
* (P1a will wire up an inline
|
|
22
|
+
* approval card; until then we
|
|
23
|
+
* surface the host-CLI hint.)
|
|
24
|
+
*
|
|
25
|
+
* VAULT-BROKER-UNREACHABLE (exit 6) — broker socket missing/dead;
|
|
26
|
+
* operator needs to inspect on
|
|
27
|
+
* the host.
|
|
28
|
+
*
|
|
29
|
+
* VAULT-BROKER-DENIED (exit 2) — broker reachable but ACL or
|
|
30
|
+
* grant refused; operator needs
|
|
31
|
+
* to add an explicit grant.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export type VaultCliErrorKind =
|
|
35
|
+
| "sandbox_context"
|
|
36
|
+
| "needs_approval"
|
|
37
|
+
| "broker_unreachable"
|
|
38
|
+
| "broker_denied"
|
|
39
|
+
| "other";
|
|
40
|
+
|
|
41
|
+
export interface VaultCliError {
|
|
42
|
+
kind: VaultCliErrorKind;
|
|
43
|
+
/** The original stderr text (kept for fallback rendering / audit log). */
|
|
44
|
+
original: string;
|
|
45
|
+
/**
|
|
46
|
+
* Best-effort extraction of the affected key name, if surfaced by the
|
|
47
|
+
* marker. Used to compose host-CLI hints like
|
|
48
|
+
* `switchroom vault set <key>`.
|
|
49
|
+
*/
|
|
50
|
+
key?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const MARKER_TO_KIND: ReadonlyArray<readonly [string, VaultCliErrorKind]> = [
|
|
54
|
+
["VAULT-SANDBOX-CONTEXT", "sandbox_context"],
|
|
55
|
+
["VAULT-NEEDS-APPROVAL", "needs_approval"],
|
|
56
|
+
["VAULT-BROKER-UNREACHABLE", "broker_unreachable"],
|
|
57
|
+
["VAULT-BROKER-DENIED", "broker_denied"],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Classify a vault-CLI stderr blob into a structured error. Returns
|
|
62
|
+
* `kind: "other"` when no recognized marker is present — caller should
|
|
63
|
+
* fall back to a raw pre-block.
|
|
64
|
+
*
|
|
65
|
+
* Key extraction is marker-aware. The four markers don't all surface
|
|
66
|
+
* the key the same way:
|
|
67
|
+
*
|
|
68
|
+
* VAULT-NEEDS-APPROVAL [unknown_key]: secret '<KEY>' does not …
|
|
69
|
+
* VAULT-BROKER-DENIED [<CODE>]: agent '<AGENT>' is not in the
|
|
70
|
+
* allow list for key '<KEY>'
|
|
71
|
+
* VAULT-SANDBOX-CONTEXT: … 'switchroom vault set <KEY>' on the host.
|
|
72
|
+
*
|
|
73
|
+
* A naïve "first single-quoted token" regex would grab the agent name
|
|
74
|
+
* for broker_denied. Prefer the token after a `key '` anchor when the
|
|
75
|
+
* marker is broker_denied / needs_approval; for sandbox_context the
|
|
76
|
+
* gateway supplies the key directly via verbHint (extraction here is
|
|
77
|
+
* best-effort and may stay undefined).
|
|
78
|
+
*/
|
|
79
|
+
export function parseVaultCliError(stderr: string): VaultCliError {
|
|
80
|
+
const text = stderr ?? "";
|
|
81
|
+
for (const [marker, kind] of MARKER_TO_KIND) {
|
|
82
|
+
if (text.includes(marker)) {
|
|
83
|
+
const key = extractKey(text, kind);
|
|
84
|
+
return { kind, original: text, key };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { kind: "other", original: text };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extractKey(text: string, kind: VaultCliErrorKind): string | undefined {
|
|
91
|
+
// Prefer the explicit `key '<X>'` anchor when present — it's the
|
|
92
|
+
// unambiguous form used in broker_denied's stderr.
|
|
93
|
+
const anchored = text.match(/key '([A-Za-z0-9_.-]+)'/);
|
|
94
|
+
if (anchored) return anchored[1];
|
|
95
|
+
|
|
96
|
+
// needs_approval: `secret '<X>' does not …` is the canonical form;
|
|
97
|
+
// the only other quoted token in that message is the agent name in
|
|
98
|
+
// the suggestion clause (which is never present today, but guard
|
|
99
|
+
// against drift).
|
|
100
|
+
if (kind === "needs_approval") {
|
|
101
|
+
const m = text.match(/secret '([A-Za-z0-9_.-]+)'/);
|
|
102
|
+
if (m) return m[1];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fall back to first single-quoted token, skipping the
|
|
106
|
+
// `'switchroom vault …'` hint that sandbox_context emits.
|
|
107
|
+
const allQuoted = [...text.matchAll(/'([^']+)'/g)];
|
|
108
|
+
for (const m of allQuoted) {
|
|
109
|
+
const candidate = m[1];
|
|
110
|
+
if (candidate.includes("switchroom")) continue;
|
|
111
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(candidate)) continue;
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface VaultErrorRendering {
|
|
118
|
+
/** Telegram HTML message (no surrounding decoration). */
|
|
119
|
+
html: string;
|
|
120
|
+
/**
|
|
121
|
+
* When true, the gateway should NOT print the raw stderr blob — the
|
|
122
|
+
* rendered message already conveys the actionable next step. When
|
|
123
|
+
* false (kind="other"), the gateway should append the original
|
|
124
|
+
* output in a <pre> block as before.
|
|
125
|
+
*/
|
|
126
|
+
suppressRaw: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function htmlEscape(s: string): string {
|
|
130
|
+
return s
|
|
131
|
+
.replace(/&/g, "&")
|
|
132
|
+
.replace(/</g, "<")
|
|
133
|
+
.replace(/>/g, ">");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Compose the user-facing Telegram HTML for a parsed vault error.
|
|
138
|
+
*
|
|
139
|
+
* @param err Result from parseVaultCliError.
|
|
140
|
+
* @param verbHint Optional descriptor of the in-progress action used
|
|
141
|
+
* to compose the host-side command suggestion (e.g.
|
|
142
|
+
* "set", "remove", "init").
|
|
143
|
+
*/
|
|
144
|
+
export function renderVaultCliError(
|
|
145
|
+
err: VaultCliError,
|
|
146
|
+
verbHint: { verb: "set" | "get" | "list" | "init" | "remove" | "save"; key?: string } = { verb: "set" },
|
|
147
|
+
): VaultErrorRendering {
|
|
148
|
+
// The gateway always knows which key the user was acting on for
|
|
149
|
+
// get/set/remove — prefer that over the parser's best-effort
|
|
150
|
+
// extraction so a renderer mistake can't surface the wrong key in
|
|
151
|
+
// the host command suggestion (e.g. an agent name from a
|
|
152
|
+
// broker_denied message). Fall back to parser extraction only when
|
|
153
|
+
// the gateway didn't pass a key (e.g. list).
|
|
154
|
+
const key = verbHint.key ?? err.key;
|
|
155
|
+
switch (err.kind) {
|
|
156
|
+
case "sandbox_context":
|
|
157
|
+
return {
|
|
158
|
+
suppressRaw: true,
|
|
159
|
+
html:
|
|
160
|
+
`⚠️ <b>This action must run on the host.</b>\n` +
|
|
161
|
+
`The vault file isn't mounted inside the agent sandbox; only ` +
|
|
162
|
+
`the broker socket is. Open a host shell and run:\n` +
|
|
163
|
+
`<pre>switchroom vault ${verbHint.verb}${key ? ` ${htmlEscape(key)}` : ""}</pre>`,
|
|
164
|
+
};
|
|
165
|
+
case "needs_approval":
|
|
166
|
+
return {
|
|
167
|
+
suppressRaw: true,
|
|
168
|
+
html:
|
|
169
|
+
`⚠️ <b>New vault key — operator approval required.</b>\n` +
|
|
170
|
+
(key
|
|
171
|
+
? `The agent tried to save <code>${htmlEscape(key)}</code>, but `
|
|
172
|
+
: `The agent tried to save a new key, but `) +
|
|
173
|
+
`agents can only rotate existing keys via the broker; introducing ` +
|
|
174
|
+
`a new key needs an operator action.\n\n` +
|
|
175
|
+
`For now, run on a host shell:\n` +
|
|
176
|
+
`<pre>switchroom vault set${key ? ` ${htmlEscape(key)}` : " <key>"}</pre>\n` +
|
|
177
|
+
`<i>A one-tap approval card is on the way (#969 P1a).</i>`,
|
|
178
|
+
};
|
|
179
|
+
case "broker_unreachable":
|
|
180
|
+
return {
|
|
181
|
+
suppressRaw: true,
|
|
182
|
+
html:
|
|
183
|
+
`⚠️ <b>Vault broker isn't reachable.</b>\n` +
|
|
184
|
+
`From inside the agent sandbox there's no fallback path. ` +
|
|
185
|
+
`Operator can check on the host:\n` +
|
|
186
|
+
`<pre>switchroom vault broker status</pre>`,
|
|
187
|
+
};
|
|
188
|
+
case "broker_denied":
|
|
189
|
+
return {
|
|
190
|
+
suppressRaw: true,
|
|
191
|
+
html:
|
|
192
|
+
`⚠️ <b>Vault broker refused the request.</b>\n` +
|
|
193
|
+
(key
|
|
194
|
+
? `The agent isn't authorized to access <code>${htmlEscape(key)}</code>. `
|
|
195
|
+
: `The agent isn't authorized to access this key. `) +
|
|
196
|
+
`Operator can grant access from a host shell:\n` +
|
|
197
|
+
`<pre>switchroom vault grant <agent> --keys ${key ? htmlEscape(key) : "<key>"}</pre>`,
|
|
198
|
+
};
|
|
199
|
+
case "other":
|
|
200
|
+
return { suppressRaw: false, html: "" };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":"3.2.4","results":[[":tests/progress-card-driver.test.ts",{"duration":82.55005700000004,"failed":false}],[":tests/progress-card.test.ts",{"duration":50.04072000000002,"failed":false}],[":tests/telegram-format.test.ts",{"duration":0,"failed":false}],[":tests/stream-reply-handler.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-harness.test.ts",{"duration":4955.245276000001,"failed":false}],[":tests/streaming-orchestration.test.ts",{"duration":0,"failed":false}],[":tests/races.test.ts",{"duration":0,"failed":false}],[":tests/subagent-watcher.test.ts",{"duration":0,"failed":false}],[":tests/gateway-bridge.test.ts",{"duration":0,"failed":true}],[":tests/answer-stream.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-pin-manager.test.ts",{"duration":25.365711999999974,"failed":false}],[":tests/pty-tail.test.ts",{"duration":0,"failed":false}],[":tests/turn-end-regressions.test.ts",{"duration":0,"failed":false}],[":tests/session-tail.test.ts",{"duration":0,"failed":false}],[":tests/gateway-clean-shutdown-marker.test.ts",{"duration":0,"failed":true}],[":tests/e2e.test.ts",{"duration":0,"failed":false}],[":tests/setup-flow.test.ts",{"duration":0,"failed":false}],[":tests/tool-labels.test.ts",{"duration":0,"failed":false}],[":tests/registry-turns.test.ts",{"duration":0,"failed":true}],[":tests/welcome-text.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-oauth-code.test.ts",{"duration":0,"failed":false}],[":tests/draft-stream.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-stuck-warning.test.ts",{"duration":21.23773799999998,"failed":false}],[":tests/history.test.ts",{"duration":0,"failed":false}],[":tests/streaming-e2e.test.ts",{"duration":0,"failed":false}],[":tests/foreman-handlers.test.ts",{"duration":0,"failed":false}],[":tests/foreman-create-flow.test.ts",{"duration":0,"failed":false}],[":tests/operator-events.test.ts",{"duration":0,"failed":false}],[":tests/vault-grants-revoke.test.ts",{"duration":0,"failed":false}],[":tests/boot-probes.test.ts",{"duration":0,"failed":true}],[":tests/pty-partial-handler.test.ts",{"duration":0,"failed":false}],[":tests/auth-slot-commands.test.ts",{"duration":0,"failed":false}],[":tests/vault-subcommands.test.ts",{"duration":0,"failed":false}],[":tests/auth-dashboard.test.ts",{"duration":0,"failed":false}],[":tests/active-pins-sweep.test.ts",{"duration":0,"failed":false}],[":tests/turns-writer.test.ts",{"duration":0,"failed":true}],[":tests/retry-api-call.test.ts",{"duration":0,"failed":false}],[":tests/steering.test.ts",{"duration":0,"failed":false}],[":tests/outbound-ordering.test.ts",{"duration":0,"failed":false}],[":tests/ipc-server-client.test.ts",{"duration":0,"failed":false}],[":tests/gateway-startup-mutex.test.ts",{"duration":0,"failed":true}],[":tests/auth-dashboard-edge-cases.test.ts",{"duration":0,"failed":false}],[":tests/status-reactions.test.ts",{"duration":0,"failed":false}],[":tests/auto-fallback.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect.test.ts",{"duration":0,"failed":false}],[":tests/foreman-write-ops.test.ts",{"duration":0,"failed":false}],[":tests/boot-card-render.test.ts",{"duration":0,"failed":false}],[":tests/handoff-continuity.test.ts",{"duration":0,"failed":false}],[":tests/false-restart-banner.test.ts",{"duration":0,"failed":false}],[":tests/answer-stream-silent-markers.test.ts",{"duration":0,"failed":false}],[":tests/ipc-validator.test.ts",{"duration":0,"failed":false}],[":tests/stream-controller.test.ts",{"duration":0,"failed":false}],[":tests/gateway-409-retry-banner.test.ts",{"duration":0,"failed":false}],[":tests/stream-reply-error-paths.test.ts",{"duration":0,"failed":false}],[":tests/ipc-server-race.test.ts",{"duration":0,"failed":false}],[":tests/progress-update.test.ts",{"duration":0,"failed":true}],[":tests/restart-watchdog.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-cross-turn.test.ts",{"duration":0,"failed":false}],[":tests/boot-card-probe-target.test.ts",{"duration":0,"failed":false}],[":tests/active-reactions.test.ts",{"duration":0,"failed":false}],[":tests/quota-cache.test.ts",{"duration":0,"failed":true}],[":tests/streaming-metrics.test.ts",{"duration":0,"failed":false}],[":tests/operator-events-session-tail.test.ts",{"duration":0,"failed":false}],[":tests/foreman-state.test.ts",{"duration":0,"failed":true}],[":tests/ipc-protocol.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-pin-watchdog.test.ts",{"duration":0,"failed":false}],[":tests/telegram-button-constraints.test.ts",{"duration":0,"failed":false}],[":tests/bot-runtime.test.ts",{"duration":0,"failed":false}],[":tests/gateway-startup-network-retry.test.ts",{"duration":0,"failed":false}],[":tests/attachment-path.test.ts",{"duration":0,"failed":false}],[":tests/active-pins.test.ts",{"duration":0,"failed":false}],[":tests/subagent-tracker-hooks.test.ts",{"duration":0,"failed":true}],[":tests/fake-bot-api.test.ts",{"duration":0,"failed":false}],[":tests/quota-check.test.ts",{"duration":0,"failed":false}],[":tests/parse-mode-rotation.test.ts",{"duration":0,"failed":false}],[":tests/gateway-secret-detect.test.ts",{"duration":0,"failed":false}],[":tests/turn-flush-safety.test.ts",{"duration":0,"failed":false}],[":tests/multi-turn-continuity.test.ts",{"duration":0,"failed":false}],[":tests/status-accent.test.ts",{"duration":0,"failed":false}],[":tests/auth-code-auto-capture.test.ts",{"duration":0,"failed":false}],[":tests/auth-login-url-button.test.ts",{"duration":0,"failed":false}],[":tests/auth-dashboard-restart-flow.test.ts",{"duration":0,"failed":false}],[":tests/setup-state.test.ts",{"duration":0,"failed":true}],[":tests/progress-card-golden.test.ts",{"duration":6.658348999999987,"failed":false}],[":tests/unhandled-rejection-policy.test.ts",{"duration":0,"failed":true}],[":tests/typing-wrap.test.ts",{"duration":0,"failed":false}],[":tests/auth-account-identity-surface.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-secretlint.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-pipeline.test.ts",{"duration":0,"failed":false}],[":tests/operator-events-history.test.ts",{"duration":0,"failed":false}],[":tests/gateway-message-validator.test.ts",{"duration":0,"failed":false}],[":tests/silent-reply-guard.test.ts",{"duration":0,"failed":false}],[":tests/active-reactions-sweep.test.ts",{"duration":0,"failed":false}],[":tests/pin-event-log.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-fail-closed.test.ts",{"duration":0,"failed":false}],[":tests/ipc-server-validate-operator.test.ts",{"duration":0,"failed":false}],[":tests/idle-footer-wiring.test.ts",{"duration":0,"failed":true}],[":tests/boot-card-reason.test.ts",{"duration":0,"failed":true}],[":tests/plugin-logger.test.ts",{"duration":0,"failed":false}],[":tests/vault-grant-wizard.test.ts",{"duration":0,"failed":false}],[":tests/turn-signal-tracker.test.ts",{"duration":0,"failed":false}],[":tests/context-exhaustion.test.ts",{"duration":0,"failed":false}],[":tests/gateway-startup-reset.test.ts",{"duration":0,"failed":false}],[":tests/idle-footer.test.ts",{"duration":0,"failed":false}],[":tests/poll-health.test.ts",{"duration":0,"failed":false}],[":tests/turn-flush-prose-recovery.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-suppressor-no-silent-allow.test.ts",{"duration":0,"failed":false}],[":tests/boot-card-dedupe.test.ts",{"duration":0,"failed":true}],[":tests/protocol-fixtures.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-staging.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-audit.test.ts",{"duration":0,"failed":false}],[":tests/secret-detect-gitleaks.test.ts",{"duration":0,"failed":false}],[":tests/subagent-registry-bugs.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-driver-eviction.test.ts",{"duration":22.822707999999977,"failed":false}],[":tests/progress-card-driver-fleet-shadow.test.ts",{"duration":7.463677000000018,"failed":false}],[":tests/two-zone-card-lifecycle.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-concurrent-turns-isolation.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-bg-survives-next-turn.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-snapshot.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-stuck-edit-throttle.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-bg-done-when-all-terminal.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-html-balance.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-bg-detection.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-draft-flag.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-stuck-per-member.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-header-phases.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-stuck-recovery.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-stuck-header-escalation.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-fleet-row.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-cap.test.ts",{"duration":0,"failed":false}],[":tests/two-zone-card-sanitise.test.ts",{"duration":0,"failed":false}],[":tests/progress-card-close-paths-converge.test.ts",{"duration":10.167681000000016,"failed":false}],[":registry/subagents.test.ts",{"duration":0,"failed":true}],[":tests/issues-card.test.ts",{"duration":0,"failed":false}],[":registry/subagents-bugs.test.ts",{"duration":0,"failed":true}],[":tests/answer-stream-dedup.test.ts",{"duration":0,"failed":false}],[":tests/model-unavailable.test.ts",{"duration":0,"failed":false}],[":tests/preamble-suppressor.test.ts",{"duration":0,"failed":false}],[":tests/harness-ordering-invariants.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-spec.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-ipc-lifecycle.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-i6-turn-flush-replay-dedup.test.ts",{"duration":0,"failed":false}],[":tests/slot-banner-driver.e2e.test.ts",{"duration":0,"failed":false}],[":tests/waiting-ux.e2e.test.ts",{"duration":0,"failed":false}],[":tests/subagent-watcher-parent-marker.test.ts",{"duration":0,"failed":false}],[":tests/auth-code-redact.test.ts",{"duration":0,"failed":false}],[":tests/subagent-watcher-stall-notification.test.ts",{"duration":0,"failed":false}],[":tests/telegraph.test.ts",{"duration":0,"failed":true}],[":tests/recent-outbound-dedup.test.ts",{"duration":0,"failed":true}],[":tests/turn-flush-card-takeover.test.ts",{"duration":0,"failed":false}],[":tests/resolve-calling-subagent.test.ts",{"duration":0,"failed":true}],[":tests/secret-guard-pretool.test.ts",{"duration":0,"failed":true}],[":tests/first-paint.test.ts",{"duration":0,"failed":false}],[":tests/ask-user.test.ts",{"duration":0,"failed":true}],[":registry/api-registry.test.ts",{"duration":0,"failed":true}],[":tests/credits-watch.test.ts",{"duration":0,"failed":false}],[":admin-commands/dispatch.test.ts",{"duration":0,"failed":false}],[":tests/fleet-state.test.ts",{"duration":0,"failed":false}],[":tests/voice-transcribe.test.ts",{"duration":0,"failed":true}],[":tests/turn-active-marker.test.ts",{"duration":0,"failed":false}],[":tests/inline-keyboard-callbacks.test.ts",{"duration":0,"failed":false}],[":tests/issues-watcher.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-f1-ladder-integrity.test.ts",{"duration":0,"failed":false}],[":tests/gateway-disconnect-flush.test.ts",{"duration":0,"failed":false}],[":tests/reply-terminal-reaction.test.ts",{"duration":0,"failed":false}],[":tests/turn-flush-dedup-controller.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-f3-late-card.test.ts",{"duration":0,"failed":false}],[":tests/status-reactions-allowed-filter.test.ts",{"duration":0,"failed":false}],[":tests/pty-tail-real-fixture.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway.smoke.test.ts",{"duration":0,"failed":false}],[":tests/harness-parse-mode-validation.test.ts",{"duration":0,"failed":false}],[":tests/inbound-coalesce.test.ts",{"duration":0,"failed":false}],[":tests/gateway-update-placeholder-dispatch.test.ts",{"duration":0,"failed":true}],[":tests/interrupt-marker.test.ts",{"duration":0,"failed":true}],[":tests/dm-command-gate.test.ts",{"duration":0,"failed":false}],[":tests/auto-fallback-dispatcher.e2e.test.ts",{"duration":0,"failed":false}],[":tests/sync-chat-running-subagents.test.ts",{"duration":0,"failed":false}],[":tests/subagents-schema-init-order.test.ts",{"duration":0,"failed":true}],[":tests/draft-transport.test.ts",{"duration":0,"failed":false}],[":gateway/access-validator.test.ts",{"duration":0,"failed":false}],[":registry/turns-schema.test.ts",{"duration":0,"failed":true}],[":tests/update-factory-edited-and-reactions.test.ts",{"duration":0,"failed":false}],[":tests/gateway-no-reply-single-emit.test.ts",{"duration":0,"failed":false}],[":tests/real-gateway-f2-instant-draft.test.ts",{"duration":0,"failed":false}],[":tests/gateway-boot-marker-clear.test.ts",{"duration":0,"failed":false}],[":tests/fleet-state-watcher.test.ts",{"duration":0,"failed":false}],[":tests/ipc-server-validate-update-placeholder.test.ts",{"duration":0,"failed":false}],[":tests/permission-title.test.ts",{"duration":0,"failed":false}],[":tests/slot-banner.test.ts",{"duration":0,"failed":false}],[":tests/sticker-aliases.test.ts",{"duration":0,"failed":true}],[":tests/ipc-server-anonymous-refuse.test.ts",{"duration":0,"failed":false}],[":tests/ipc-server-validate-pty-partial.test.ts",{"duration":0,"failed":false}],[":channel-envelope-safety.test.ts",{"duration":0,"failed":false}],[":tests/bridge-anonymous-refuse.test.ts",{"duration":0,"failed":false}],[":tests/send-typing-action-validation.test.ts",{"duration":0,"failed":false}],[":tests/spawn-detached-cgroup-escape.test.ts",{"duration":0,"failed":false}],[":gateway/boot-sweep-filter.test.ts",{"duration":0,"failed":false}]]}
|