switchroom 0.16.23 → 0.16.27
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/agent-scheduler/index.js +80 -80
- package/dist/auth-broker/index.js +80 -80
- package/dist/cli/autoaccept-poll.js +8 -8
- package/dist/cli/drive-write-pretool.mjs +10 -10
- package/dist/cli/notion-write-pretool.mjs +82 -82
- package/dist/cli/self-improve-apply-guard-pretool.mjs +6 -0
- package/dist/cli/skill-validate-pretool.mjs +2936 -119
- package/dist/cli/switchroom.js +804 -465
- package/dist/host-control/main.js +169 -163
- package/dist/vault/approvals/kernel-server.js +82 -82
- package/dist/vault/broker/server.js +83 -83
- package/package.json +4 -4
- package/telegram-plugin/answer-stream.ts +20 -49
- package/telegram-plugin/auth-snapshot-format.ts +27 -30
- package/telegram-plugin/auto-fallback-fleet.ts +6 -11
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/card-format.ts +28 -25
- package/telegram-plugin/credits-watch.ts +5 -10
- package/telegram-plugin/dist/bridge/bridge.js +113 -113
- package/telegram-plugin/dist/gateway/gateway.js +2085 -2102
- package/telegram-plugin/dist/server.js +161 -161
- package/telegram-plugin/draft-stream.ts +4 -4
- package/telegram-plugin/format.ts +427 -680
- package/telegram-plugin/gateway/approval-callback.ts +2 -3
- package/telegram-plugin/gateway/approval-card.test.ts +17 -4
- package/telegram-plugin/gateway/approval-card.ts +16 -6
- package/telegram-plugin/gateway/approvals-commands.ts +18 -24
- package/telegram-plugin/gateway/auth-command.ts +74 -74
- package/telegram-plugin/gateway/auth-line.ts +5 -15
- package/telegram-plugin/gateway/boot-card.ts +20 -22
- package/telegram-plugin/gateway/boot-version.ts +3 -2
- package/telegram-plugin/gateway/config-approval-handler.test.ts +35 -33
- package/telegram-plugin/gateway/config-approval-handler.ts +24 -24
- package/telegram-plugin/gateway/config-snapshot.ts +9 -9
- package/telegram-plugin/gateway/diff-preview-card.test.ts +8 -8
- package/telegram-plugin/gateway/diff-preview-card.ts +2 -5
- package/telegram-plugin/gateway/disconnect-flush.ts +0 -4
- package/telegram-plugin/gateway/drive-write-approval.test.ts +10 -10
- package/telegram-plugin/gateway/drive-write-approval.ts +14 -8
- package/telegram-plugin/gateway/effort-command.ts +17 -17
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +8 -2
- package/telegram-plugin/gateway/folder-picker-handler.ts +3 -4
- package/telegram-plugin/gateway/gateway.ts +881 -633
- package/telegram-plugin/gateway/inject-handler.test.ts +15 -13
- package/telegram-plugin/gateway/inject-handler.ts +5 -5
- package/telegram-plugin/gateway/ipc-protocol.ts +33 -1
- package/telegram-plugin/gateway/ipc-server.ts +39 -6
- package/telegram-plugin/gateway/linear-activity.ts +16 -14
- package/telegram-plugin/gateway/linear-setup.ts +1 -1
- package/telegram-plugin/gateway/model-command.ts +25 -25
- package/telegram-plugin/gateway/oversize-card-body.ts +6 -7
- package/telegram-plugin/gateway/permission-timeout.ts +76 -0
- package/telegram-plugin/gateway/skill-proposal-card.ts +167 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +19 -13
- package/telegram-plugin/issues-card.ts +6 -7
- package/telegram-plugin/model-unavailable.ts +8 -12
- package/telegram-plugin/operator-events-history.ts +1 -1
- package/telegram-plugin/operator-events.ts +24 -28
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/pending-work-progress.ts +36 -36
- package/telegram-plugin/permission-title.ts +39 -20
- package/telegram-plugin/pty-partial-handler.ts +5 -13
- package/telegram-plugin/quota-check.ts +5 -5
- package/telegram-plugin/quota-watch.ts +13 -18
- package/telegram-plugin/recent-outbound-dedup.ts +5 -5
- package/telegram-plugin/registry/turns-schema.ts +43 -3
- package/telegram-plugin/retry-api-call.ts +42 -7
- package/telegram-plugin/rich-send.ts +86 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +6 -6
- package/telegram-plugin/secret-detect/vault-error.ts +29 -22
- package/telegram-plugin/shared/bot-runtime.ts +29 -7
- package/telegram-plugin/silence-poke.ts +26 -69
- package/telegram-plugin/silent-reply-anchor.ts +9 -2
- package/telegram-plugin/slot-banner-driver.ts +9 -6
- package/telegram-plugin/slot-banner.ts +5 -8
- package/telegram-plugin/status-no-truncate.ts +11 -5
- package/telegram-plugin/steering.ts +0 -4
- package/telegram-plugin/stream-controller.ts +59 -62
- package/telegram-plugin/stream-reply-handler.ts +49 -98
- package/telegram-plugin/subagent-watcher.ts +2 -2
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +5 -2
- package/telegram-plugin/tests/answer-stream.test.ts +54 -63
- package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +3 -2
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +19 -18
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +15 -13
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +27 -12
- package/telegram-plugin/tests/boot-card-render.test.ts +59 -48
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/bot-api.harness.ts +23 -1
- package/telegram-plugin/tests/bot-runtime.test.ts +23 -18
- package/telegram-plugin/tests/card-format.test.ts +6 -4
- package/telegram-plugin/tests/config-snapshot.test.ts +1 -1
- package/telegram-plugin/tests/credits-watch.test.ts +5 -5
- package/telegram-plugin/tests/fake-bot-api.ts +58 -4
- package/telegram-plugin/tests/finalize-callback.test.ts +11 -9
- package/telegram-plugin/tests/foreground-nesting.test.ts +1 -1
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +3 -13
- package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +6 -2
- package/telegram-plugin/tests/issues-card.test.ts +15 -12
- package/telegram-plugin/tests/length-error-classify.test.ts +131 -0
- package/telegram-plugin/tests/linear-agent-activity.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +2 -2
- package/telegram-plugin/tests/model-unavailable.test.ts +13 -13
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +6 -10
- package/telegram-plugin/tests/operator-events.test.ts +7 -9
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +273 -0
- package/telegram-plugin/tests/pending-work-progress.test.ts +20 -21
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +12 -2
- package/telegram-plugin/tests/permission-timeout.test.ts +77 -0
- package/telegram-plugin/tests/permission-title.test.ts +88 -41
- package/telegram-plugin/tests/pty-partial-handler.test.ts +8 -8
- package/telegram-plugin/tests/quota-check.test.ts +3 -3
- package/telegram-plugin/tests/quota-watch.test.ts +8 -4
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +4 -3
- package/telegram-plugin/tests/silence-poke.test.ts +75 -112
- package/telegram-plugin/tests/single-mode-stream-reply.test.ts +137 -0
- package/telegram-plugin/tests/skill-proposal-card.test.ts +103 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +36 -24
- package/telegram-plugin/tests/slot-banner.test.ts +9 -6
- package/telegram-plugin/tests/status-accent.test.ts +29 -32
- package/telegram-plugin/tests/{stream-controller-html-fallback.test.ts → stream-controller-parse-fallback.test.ts} +40 -42
- package/telegram-plugin/tests/stream-controller.test.ts +63 -52
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +43 -38
- package/telegram-plugin/tests/stream-reply-handler.test.ts +122 -249
- package/telegram-plugin/tests/streaming-e2e.test.ts +35 -30
- package/telegram-plugin/tests/streaming-orchestration.test.ts +29 -28
- package/telegram-plugin/tests/telegram-format.test.ts +120 -1083
- package/telegram-plugin/tests/tool-activity-summary.test.ts +144 -145
- package/telegram-plugin/tests/welcome-text.test.ts +72 -65
- package/telegram-plugin/tests/worker-activity-feed.test.ts +119 -137
- package/telegram-plugin/text-voice-scrub.ts +8 -11
- package/telegram-plugin/tool-activity-summary.ts +29 -29
- package/telegram-plugin/welcome-text.ts +82 -83
- package/telegram-plugin/worker-activity-feed.ts +2 -3
- package/telegram-plugin/html-sanitize.ts +0 -244
- package/telegram-plugin/tests/html-sanitize.test.ts +0 -146
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -162
|
@@ -30,9 +30,12 @@ const baseMeta: AgentMetadata = {
|
|
|
30
30
|
auth: null,
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
describe("escapeHtml", () => {
|
|
34
|
-
it("escapes
|
|
35
|
-
|
|
33
|
+
describe("escapeHtml (now a markdown escaper, kept under the legacy name, #2669)", () => {
|
|
34
|
+
it("escapes the inline-markdown specials (\\ ` * _ ~ = [ ] |); leaves < > & \" literal", () => {
|
|
35
|
+
// The HTML escaper is gone — this is escapeMarkdown under the old name.
|
|
36
|
+
// `< > & "` are literal in rich markdown; `* _ =` etc. are escaped.
|
|
37
|
+
expect(escapeHtml('<foo bar="baz">&')).toBe('<foo bar\\="baz">&');
|
|
38
|
+
expect(escapeHtml("a *b* _c_")).toBe("a \\*b\\* \\_c\\_");
|
|
36
39
|
});
|
|
37
40
|
it("leaves safe text alone", () => {
|
|
38
41
|
expect(escapeHtml("hello world")).toBe("hello world");
|
|
@@ -63,19 +66,19 @@ describe("formatAuthLine", () => {
|
|
|
63
66
|
expect(formatAuthLine({ authenticated: true, subscription_type: null, expires_in: null, auth_source: "oauth" }))
|
|
64
67
|
.toBe("✓ subscription");
|
|
65
68
|
});
|
|
66
|
-
it("
|
|
69
|
+
it("passes < > literally (markdown, #2669) in subscription type", () => {
|
|
67
70
|
expect(formatAuthLine({ authenticated: true, subscription_type: "<injected>", expires_in: null, auth_source: "oauth" }))
|
|
68
|
-
.toContain("
|
|
71
|
+
.toContain("<injected>");
|
|
69
72
|
});
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
describe("formatAgentLine", () => {
|
|
73
76
|
it("includes model inline", () => {
|
|
74
|
-
expect(formatAgentLine(baseMeta)).toContain("
|
|
77
|
+
expect(formatAgentLine(baseMeta)).toContain("`sonnet`");
|
|
75
78
|
});
|
|
76
79
|
it("falls back to 'default' when model is null/empty", () => {
|
|
77
|
-
expect(formatAgentLine({ ...baseMeta, model: null })).toContain("
|
|
78
|
-
expect(formatAgentLine({ ...baseMeta, model: "" })).toContain("
|
|
80
|
+
expect(formatAgentLine({ ...baseMeta, model: null })).toContain("`default`");
|
|
81
|
+
expect(formatAgentLine({ ...baseMeta, model: "" })).toContain("`default`");
|
|
79
82
|
});
|
|
80
83
|
it("appends topic when present", () => {
|
|
81
84
|
const out = formatAgentLine({ ...baseMeta, topicName: "Planning", topicEmoji: "🗓" });
|
|
@@ -84,8 +87,8 @@ describe("formatAgentLine", () => {
|
|
|
84
87
|
it("shows the live session model alongside the configured model when a /model switch is active", () => {
|
|
85
88
|
const out = formatAgentLine({ ...baseMeta, model: "claude-fable-5[1m]", sessionModel: "Opus 4.8 (1M context)" });
|
|
86
89
|
// Both surfaces present + agree: configured AND what's actually running.
|
|
87
|
-
expect(out).toContain("
|
|
88
|
-
expect(out).toContain("live session:
|
|
90
|
+
expect(out).toContain("`claude-fable-5[1m]`");
|
|
91
|
+
expect(out).toContain("live session: `Opus 4.8 (1M context)`");
|
|
89
92
|
});
|
|
90
93
|
it("omits the session line when no override is active", () => {
|
|
91
94
|
expect(formatAgentLine({ ...baseMeta, sessionModel: null })).not.toContain("live session");
|
|
@@ -96,9 +99,9 @@ describe("formatAgentLine", () => {
|
|
|
96
99
|
// topicName null → no topic chunk. Keeps the line clean.
|
|
97
100
|
expect(formatAgentLine({ ...baseMeta, topicEmoji: "🗓" })).not.toContain("topic");
|
|
98
101
|
});
|
|
99
|
-
it("
|
|
102
|
+
it("passes < > literally (markdown, #2669) in agent name", () => {
|
|
100
103
|
expect(formatAgentLine({ ...baseMeta, agentName: "<script>" }))
|
|
101
|
-
.toContain("
|
|
104
|
+
.toContain("<script>");
|
|
102
105
|
});
|
|
103
106
|
});
|
|
104
107
|
|
|
@@ -108,7 +111,7 @@ describe("startText", () => {
|
|
|
108
111
|
});
|
|
109
112
|
it("names the agent, not 'Claude Code session'", () => {
|
|
110
113
|
const out = startText("assistant", false);
|
|
111
|
-
expect(out).toContain("
|
|
114
|
+
expect(out).toContain("**assistant**");
|
|
112
115
|
expect(out).not.toMatch(/Claude Code session/);
|
|
113
116
|
});
|
|
114
117
|
it("mentions pairing code flow", () => {
|
|
@@ -121,14 +124,14 @@ describe("startText", () => {
|
|
|
121
124
|
expect(out).toContain("/status");
|
|
122
125
|
expect(out).toContain("/commands");
|
|
123
126
|
});
|
|
124
|
-
it("
|
|
125
|
-
expect(startText("<x>", false)).toContain("
|
|
127
|
+
it("passes < > literally (markdown, #2669) in agent name", () => {
|
|
128
|
+
expect(startText("<x>", false)).toContain("<x>");
|
|
126
129
|
});
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
describe("helpText", () => {
|
|
130
133
|
it("names the agent", () => {
|
|
131
|
-
expect(helpText("klanker")).toContain("
|
|
134
|
+
expect(helpText("klanker")).toContain("**klanker**");
|
|
132
135
|
});
|
|
133
136
|
it("mentions the new Sprint 2/3 commands", () => {
|
|
134
137
|
const out = helpText("assistant");
|
|
@@ -162,7 +165,7 @@ describe("statusPairedText", () => {
|
|
|
162
165
|
const out = statusPairedText({ user: "@ken", meta });
|
|
163
166
|
expect(out).toContain("Agent:");
|
|
164
167
|
expect(out).toContain("Auth: ✓ Max · expires 29 days");
|
|
165
|
-
expect(out).toContain("
|
|
168
|
+
expect(out).toContain("`sonnet`");
|
|
166
169
|
});
|
|
167
170
|
it("omits status/uptime when absent", () => {
|
|
168
171
|
expect(statusPairedText({ user: "@ken", meta })).not.toContain("Status:");
|
|
@@ -170,29 +173,32 @@ describe("statusPairedText", () => {
|
|
|
170
173
|
it("includes status/uptime when present", () => {
|
|
171
174
|
const withStatus: AgentMetadata = { ...meta, status: "running", uptime: "3h 12m" };
|
|
172
175
|
const out = statusPairedText({ user: "@ken", meta: withStatus });
|
|
173
|
-
expect(out).toContain("Status:
|
|
176
|
+
expect(out).toContain("Status: `running` · up 3h 12m");
|
|
174
177
|
});
|
|
175
178
|
|
|
176
179
|
// demo mode (the `/status demo` suffix) — masks the paired-user tag only.
|
|
177
180
|
describe("demo mode", () => {
|
|
178
|
-
it("WITHOUT demo, the real handle still renders", () => {
|
|
179
|
-
|
|
181
|
+
it("WITHOUT demo, the real handle still renders (underscore markdown-escaped, #2669)", () => {
|
|
182
|
+
// The `_` in the handle is escaped so it can't open an italic run in
|
|
183
|
+
// the rich-markdown body — the handle renders as @ken\_real verbatim.
|
|
184
|
+
expect(statusPairedText({ user: "@ken_real", meta })).toContain("Paired as @ken\\_real.");
|
|
180
185
|
});
|
|
181
186
|
it("WITH demo, the handle is masked to a @demo_user form", () => {
|
|
182
187
|
const out = statusPairedText({ user: "@ken_real", meta, demo: true });
|
|
183
188
|
expect(out).not.toContain("@ken_real");
|
|
184
|
-
|
|
189
|
+
// The masked handle's underscore is markdown-escaped too.
|
|
190
|
+
expect(out).toMatch(/Paired as @demo\\_user\d*\./);
|
|
185
191
|
});
|
|
186
192
|
it("WITH demo, a numeric sender id is masked to a @handle, not a raw number", () => {
|
|
187
193
|
const out = statusPairedText({ user: "12345", meta, demo: true });
|
|
188
194
|
expect(out).not.toContain("12345");
|
|
189
|
-
expect(out).toMatch(/Paired as @
|
|
195
|
+
expect(out).toMatch(/Paired as @demo\\_user\d*\./);
|
|
190
196
|
});
|
|
191
197
|
it("WITH demo, the agent/model/auth topology is NOT masked", () => {
|
|
192
198
|
const out = statusPairedText({ user: "@ken_real", meta, demo: true });
|
|
193
199
|
// Out-of-scope fields stay real.
|
|
194
200
|
expect(out).toContain("Auth: ✓ Max · expires 29 days");
|
|
195
|
-
expect(out).toContain("
|
|
201
|
+
expect(out).toContain("`sonnet`");
|
|
196
202
|
});
|
|
197
203
|
});
|
|
198
204
|
|
|
@@ -220,21 +226,21 @@ describe("statusPairedText", () => {
|
|
|
220
226
|
it("renders all audit rows when meta.audit is fully populated", () => {
|
|
221
227
|
const withAudit: AgentMetadata = { ...meta, extendsProfile: "klanker", audit };
|
|
222
228
|
const out = statusPairedText({ user: "@ken", meta: withAudit });
|
|
223
|
-
expect(out).toContain("
|
|
224
|
-
expect(out).toContain("
|
|
225
|
-
expect(out).toContain("
|
|
226
|
-
expect(out).toContain("
|
|
227
|
-
expect(out).toContain("
|
|
228
|
-
expect(out).toContain("
|
|
229
|
-
expect(out).toContain("
|
|
230
|
-
expect(out).toContain("
|
|
229
|
+
expect(out).toContain("**Version** v0.3.0+44 · 2h ago");
|
|
230
|
+
expect(out).toContain("**Profile** klanker");
|
|
231
|
+
expect(out).toContain("**Tools** Read, Write, Bash, Edit, Grep +12 more");
|
|
232
|
+
expect(out).toContain("**Deny** WebFetch");
|
|
233
|
+
expect(out).toContain("**Skills** git, telegram, vault, +3 more");
|
|
234
|
+
expect(out).toContain("**Limits** idle 30m, 50 turns");
|
|
235
|
+
expect(out).toContain("**Channel** switchroom (default)");
|
|
236
|
+
expect(out).toContain("**Memory** assistant");
|
|
231
237
|
});
|
|
232
238
|
|
|
233
239
|
it("omits Deny row when toolsDeny is null", () => {
|
|
234
240
|
const partial: AgentMetadata = { ...meta, audit: { ...audit, toolsDeny: null } };
|
|
235
241
|
const out = statusPairedText({ user: "@ken", meta: partial });
|
|
236
242
|
expect(out).not.toContain("Deny");
|
|
237
|
-
expect(out).toContain("
|
|
243
|
+
expect(out).toContain("**Tools**");
|
|
238
244
|
});
|
|
239
245
|
|
|
240
246
|
it("omits Skills row when skills is null (agent has no bundled skills)", () => {
|
|
@@ -252,7 +258,7 @@ describe("statusPairedText", () => {
|
|
|
252
258
|
expect(versionIdx).toBeGreaterThan(statusIdx);
|
|
253
259
|
});
|
|
254
260
|
|
|
255
|
-
it("
|
|
261
|
+
it("passes < > & literally (markdown, #2669) in audit values", () => {
|
|
256
262
|
const hostile: AgentAuditLike = {
|
|
257
263
|
version: "<script>alert(1)</script>",
|
|
258
264
|
tools: "Read & <Write>",
|
|
@@ -263,18 +269,20 @@ describe("statusPairedText", () => {
|
|
|
263
269
|
memoryBank: "bank<>name",
|
|
264
270
|
};
|
|
265
271
|
const out = statusPairedText({ user: "@ken", meta: { ...meta, audit: hostile } });
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
expect(out).toContain("
|
|
272
|
+
// < > & are literal in rich markdown — no HTML entities. The hostile
|
|
273
|
+
// input is shown verbatim (it cannot inject formatting; only * _ ` etc.
|
|
274
|
+
// would be, and those are escaped).
|
|
275
|
+
expect(out).toContain("<script>alert(1)</script>");
|
|
276
|
+
expect(out).toContain("Read & <Write>");
|
|
277
|
+
expect(out).toContain("bank<>name");
|
|
270
278
|
});
|
|
271
279
|
|
|
272
280
|
it("handles empty extendsProfile (no Profile row when meta.extendsProfile is null)", () => {
|
|
273
281
|
const withAudit: AgentMetadata = { ...meta, extendsProfile: null, audit };
|
|
274
282
|
const out = statusPairedText({ user: "@ken", meta: withAudit });
|
|
275
|
-
expect(out).not.toContain("
|
|
283
|
+
expect(out).not.toContain("**Profile**");
|
|
276
284
|
// But other audit rows still render.
|
|
277
|
-
expect(out).toContain("
|
|
285
|
+
expect(out).toContain("**Version**");
|
|
278
286
|
});
|
|
279
287
|
});
|
|
280
288
|
|
|
@@ -284,12 +292,12 @@ describe("statusPairedText", () => {
|
|
|
284
292
|
describe("live health block", () => {
|
|
285
293
|
it("does NOT render a Health section when meta.live is undefined", () => {
|
|
286
294
|
const out = statusPairedText({ user: "@ken", meta });
|
|
287
|
-
expect(out).not.toContain("
|
|
295
|
+
expect(out).not.toContain("**Health**");
|
|
288
296
|
});
|
|
289
297
|
|
|
290
298
|
it("does NOT render a Health section when meta.live is empty array", () => {
|
|
291
299
|
const out = statusPairedText({ user: "@ken", meta: { ...meta, live: [] } });
|
|
292
|
-
expect(out).not.toContain("
|
|
300
|
+
expect(out).not.toContain("**Health**");
|
|
293
301
|
});
|
|
294
302
|
|
|
295
303
|
it("renders all probe rows including green ones", () => {
|
|
@@ -300,11 +308,11 @@ describe("statusPairedText", () => {
|
|
|
300
308
|
{ status: "fail", label: "Scheduler", detail: "sidecar not running" },
|
|
301
309
|
];
|
|
302
310
|
const out = statusPairedText({ user: "@ken", meta: { ...meta, live } });
|
|
303
|
-
expect(out).toContain("
|
|
304
|
-
expect(out).toContain("🟢
|
|
305
|
-
expect(out).toContain("🟢
|
|
306
|
-
expect(out).toContain("🟡
|
|
307
|
-
expect(out).toContain("🔴
|
|
311
|
+
expect(out).toContain("**Health**");
|
|
312
|
+
expect(out).toContain("🟢 **Account** ken@x.com · Max · token 60d");
|
|
313
|
+
expect(out).toContain("🟢 **Broker** reachable");
|
|
314
|
+
expect(out).toContain("🟡 **Skills** 1/5 dangling: foo");
|
|
315
|
+
expect(out).toContain("🔴 **Scheduler** sidecar not running");
|
|
308
316
|
});
|
|
309
317
|
|
|
310
318
|
it("renders Health section before the audit block", () => {
|
|
@@ -319,19 +327,18 @@ describe("statusPairedText", () => {
|
|
|
319
327
|
user: "@ken",
|
|
320
328
|
meta: { ...meta, live, audit },
|
|
321
329
|
});
|
|
322
|
-
const healthIdx = out.indexOf("
|
|
323
|
-
const versionIdx = out.indexOf("
|
|
330
|
+
const healthIdx = out.indexOf("**Health**");
|
|
331
|
+
const versionIdx = out.indexOf("**Version**");
|
|
324
332
|
expect(healthIdx).toBeGreaterThan(-1);
|
|
325
333
|
expect(versionIdx).toBeGreaterThan(healthIdx);
|
|
326
334
|
});
|
|
327
335
|
|
|
328
|
-
it("
|
|
336
|
+
it("passes < > literally (markdown, #2669) in probe detail strings", () => {
|
|
329
337
|
const live: AgentMetadata["live"] = [
|
|
330
338
|
{ status: "fail", label: "Skills", detail: "<script>alert(1)</script>" },
|
|
331
339
|
];
|
|
332
340
|
const out = statusPairedText({ user: "@ken", meta: { ...meta, live } });
|
|
333
|
-
expect(out).
|
|
334
|
-
expect(out).toContain("<script>");
|
|
341
|
+
expect(out).toContain("<script>alert(1)</script>");
|
|
335
342
|
});
|
|
336
343
|
});
|
|
337
344
|
});
|
|
@@ -348,14 +355,14 @@ describe("statusPendingText / statusUnpairedText", () => {
|
|
|
348
355
|
it("unpaired prompts the user to DM", () => {
|
|
349
356
|
expect(statusUnpairedText()).toMatch(/Send me a message/);
|
|
350
357
|
});
|
|
351
|
-
it("
|
|
352
|
-
expect(statusPendingText("<x>")).toContain("
|
|
358
|
+
it("passes < > literally (markdown, #2669) in the pending code value", () => {
|
|
359
|
+
expect(statusPendingText("<x>")).toContain("<x>");
|
|
353
360
|
});
|
|
354
361
|
});
|
|
355
362
|
|
|
356
363
|
describe("switchroomHelpText + switchroomHelpCommandNames", () => {
|
|
357
364
|
it("agent name appears in header", () => {
|
|
358
|
-
expect(switchroomHelpText("klanker")).toContain("
|
|
365
|
+
expect(switchroomHelpText("klanker")).toContain("**klanker**");
|
|
359
366
|
});
|
|
360
367
|
it("every command in the autocomplete array is documented here", () => {
|
|
361
368
|
const out = switchroomHelpText("assistant");
|
|
@@ -365,9 +372,9 @@ describe("switchroomHelpText + switchroomHelpCommandNames", () => {
|
|
|
365
372
|
});
|
|
366
373
|
it("groups commands into sections", () => {
|
|
367
374
|
const out = switchroomHelpText("assistant");
|
|
368
|
-
expect(out).toContain("
|
|
369
|
-
expect(out).toContain("
|
|
370
|
-
expect(out).toContain("
|
|
375
|
+
expect(out).toContain("**Session & approvals**");
|
|
376
|
+
expect(out).toContain("**Agents**");
|
|
377
|
+
expect(out).toContain("**Auth & config**");
|
|
371
378
|
});
|
|
372
379
|
it("the name array contains the Sprint 2/3 additions", () => {
|
|
373
380
|
for (const needed of ["new", "compact", "clear", "approve", "deny", "pending"]) {
|
|
@@ -455,11 +462,11 @@ describe("TELEGRAM_MENU_COMMANDS (slash-menu shape)", () => {
|
|
|
455
462
|
const helpDoc = switchroomHelpText("clerk");
|
|
456
463
|
// The /auth description string still mentions "reauth" as a
|
|
457
464
|
// dashboard verb — that's intentional, not a registered command.
|
|
458
|
-
// Pin that there's no top-level
|
|
465
|
+
// Pin that there's no top-level `/reauth ...` entry.
|
|
459
466
|
expect(
|
|
460
467
|
helpDoc,
|
|
461
468
|
"/reauth must not appear as a top-level command in help text",
|
|
462
|
-
).not.toMatch(
|
|
469
|
+
).not.toMatch(/`\/reauth\b/);
|
|
463
470
|
});
|
|
464
471
|
|
|
465
472
|
it("menu is short enough for a mobile keyboard (<= 21 entries)", () => {
|
|
@@ -486,18 +493,18 @@ describe("TELEGRAM_MENU_COMMANDS (slash-menu shape)", () => {
|
|
|
486
493
|
|
|
487
494
|
describe("restart / new ack text", () => {
|
|
488
495
|
it("restartAckText is consistent", () => {
|
|
489
|
-
expect(restartAckText("assistant")).toBe("🔄 Restarting
|
|
496
|
+
expect(restartAckText("assistant")).toBe("🔄 Restarting **assistant**…");
|
|
490
497
|
});
|
|
491
498
|
it("newSessionAckText with flush", () => {
|
|
492
499
|
expect(newSessionAckText("assistant", true))
|
|
493
|
-
.toBe("🆕 Started fresh session for
|
|
500
|
+
.toBe("🆕 Started fresh session for **assistant** · flushed handoff · restarting…");
|
|
494
501
|
});
|
|
495
502
|
it("newSessionAckText without flush", () => {
|
|
496
503
|
expect(newSessionAckText("assistant", false))
|
|
497
|
-
.toBe("🆕 Started fresh session for
|
|
504
|
+
.toBe("🆕 Started fresh session for **assistant** · restarting…");
|
|
498
505
|
});
|
|
499
|
-
it("
|
|
500
|
-
expect(restartAckText("<x>")).toContain("
|
|
501
|
-
expect(newSessionAckText("<x>", true)).toContain("
|
|
506
|
+
it("passes < > literally (markdown, #2669) in agent name in both", () => {
|
|
507
|
+
expect(restartAckText("<x>")).toContain("<x>");
|
|
508
|
+
expect(newSessionAckText("<x>", true)).toContain("<x>");
|
|
502
509
|
});
|
|
503
510
|
});
|