switchroom 0.14.58 → 0.14.59
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 +1 -1
- package/dist/auth-broker/index.js +19 -9
- package/dist/cli/notion-write-pretool.mjs +1 -1
- package/dist/cli/switchroom.js +29 -70
- package/dist/host-control/main.js +1 -1
- package/dist/vault/approvals/kernel-server.js +1 -1
- package/dist/vault/broker/server.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +213 -12
- package/telegram-plugin/gateway/gateway.ts +201 -2
- package/telegram-plugin/gateway/obligation-ledger.ts +216 -0
- package/telegram-plugin/tests/obligation-ledger.test.ts +167 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +44 -0
- package/telegram-plugin/tool-activity-summary.ts +14 -4
|
@@ -146,6 +146,33 @@ describe("appendActivityLine + renderActivityFeed — accumulating activity feed
|
|
|
146
146
|
it("final defaults false (live render keeps the → in-progress newest line)", () => {
|
|
147
147
|
expect(renderActivityFeed(["Reading a.ts"])).toBe("<b>→ Reading a.ts</b>");
|
|
148
148
|
});
|
|
149
|
+
|
|
150
|
+
// liveSuffix (PR1 heartbeat): appended INSIDE the newest in-progress line so a
|
|
151
|
+
// long single step visibly advances ("→ Pulling Meta data · 18s") even though
|
|
152
|
+
// the feed is pull-only and no new tool label arrived.
|
|
153
|
+
describe("liveSuffix (heartbeat)", () => {
|
|
154
|
+
it("appends the suffix to the newest in-progress line only", () => {
|
|
155
|
+
expect(renderActivityFeed(["Reading a.ts", "Running a command"], false, " · 18s")).toBe(
|
|
156
|
+
"<i>✓ Reading a.ts</i>\n<b>→ Running a command · 18s</b>",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
it("single live line gets the suffix", () => {
|
|
160
|
+
expect(renderActivityFeed(["Pulling Meta data"], false, " · 1m05s")).toBe(
|
|
161
|
+
"<b>→ Pulling Meta data · 1m05s</b>",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
it("final=true ignores the suffix (a finalized record never ticks)", () => {
|
|
165
|
+
const out = renderActivityFeed(["Reading a.ts", "Running a command"], true, " · 18s")!;
|
|
166
|
+
expect(out).not.toContain("·");
|
|
167
|
+
expect(out).not.toContain("→");
|
|
168
|
+
expect(out).toBe("<i>✓ Reading a.ts</i>\n<i>✓ Running a command</i>");
|
|
169
|
+
});
|
|
170
|
+
it("default empty suffix is byte-identical to no suffix", () => {
|
|
171
|
+
expect(renderActivityFeed(["Reading a.ts"], false, "")).toBe(
|
|
172
|
+
renderActivityFeed(["Reading a.ts"]),
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
149
176
|
});
|
|
150
177
|
|
|
151
178
|
describe("appendActivityLabel — precomputed label feed (tool_label path)", () => {
|
|
@@ -223,6 +250,23 @@ describe("renderActivityFeedWithNested — foreground sub-agent nesting (Model A
|
|
|
223
250
|
);
|
|
224
251
|
});
|
|
225
252
|
|
|
253
|
+
it("liveSuffix (heartbeat) lands on the nested newest in-progress step", () => {
|
|
254
|
+
const out = renderActivityFeedWithNested(
|
|
255
|
+
["Delegating: x"],
|
|
256
|
+
["Reading schema.ts", "Looking for foreign keys"],
|
|
257
|
+
false,
|
|
258
|
+
" · 22s",
|
|
259
|
+
)!;
|
|
260
|
+
expect(out).toContain(" ↳ <b>→ Looking for foreign keys · 22s</b>");
|
|
261
|
+
expect(out).not.toContain("Reading schema.ts · "); // only the newest line ticks
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("liveSuffix passes through to the flat render when there are no children", () => {
|
|
265
|
+
expect(renderActivityFeedWithNested(["Reading a.ts"], [], false, " · 9s")).toBe(
|
|
266
|
+
"<b>→ Reading a.ts · 9s</b>",
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
226
270
|
// Pins the invariant the gateway's foreground handoff-clear path relies on:
|
|
227
271
|
// on an ack-first turn the parent feed is empty (mirrorLines=[]) and the only
|
|
228
272
|
// content is the foreground sub-agent's nested narrative. The finalized
|
|
@@ -200,7 +200,11 @@ function escapeFeedHtml(s: string): string {
|
|
|
200
200
|
* `✓ +N earlier…` header when the turn ran longer. Returns null when empty.
|
|
201
201
|
* Callers send the result verbatim — do NOT re-escape or re-wrap it.
|
|
202
202
|
*/
|
|
203
|
-
export function renderActivityFeed(
|
|
203
|
+
export function renderActivityFeed(
|
|
204
|
+
lines: string[],
|
|
205
|
+
final = false,
|
|
206
|
+
liveSuffix = "",
|
|
207
|
+
): string | null {
|
|
204
208
|
if (lines.length === 0) return null;
|
|
205
209
|
const shown = lines.slice(-MIRROR_MAX_LINES);
|
|
206
210
|
const hidden = lines.length - shown.length;
|
|
@@ -210,10 +214,14 @@ export function renderActivityFeed(lines: string[], final = false): string | nul
|
|
|
210
214
|
// Newest line = in-progress step (bold, →); earlier = done (italic, ✓).
|
|
211
215
|
// `final` (turn complete, feed left as a record): ALL lines render done (✓)
|
|
212
216
|
// so the persisted message doesn't freeze on a misleading "→ in-progress".
|
|
217
|
+
// `liveSuffix` (heartbeat): appended INSIDE the newest in-progress line only
|
|
218
|
+
// (e.g. " · 18s") so the feed visibly advances during a long single step that
|
|
219
|
+
// emits no new tool label — the feed is otherwise pull-only and freezes.
|
|
220
|
+
// Caller passes framework-generated, HTML-safe text; never final + suffix.
|
|
213
221
|
// Returns ready Telegram HTML — callers must NOT re-escape or re-wrap it.
|
|
214
222
|
shown.forEach((l, i) => {
|
|
215
223
|
const esc = escapeFeedHtml(l);
|
|
216
|
-
out.push(i === lastIdx && !final ? `<b>→ ${esc}</b>` : `<i>✓ ${esc}</i>`);
|
|
224
|
+
out.push(i === lastIdx && !final ? `<b>→ ${esc}${liveSuffix}</b>` : `<i>✓ ${esc}</i>`);
|
|
217
225
|
});
|
|
218
226
|
return out.join("\n");
|
|
219
227
|
}
|
|
@@ -248,9 +256,10 @@ export function renderActivityFeedWithNested(
|
|
|
248
256
|
lines: string[],
|
|
249
257
|
childLines: string[],
|
|
250
258
|
final = false,
|
|
259
|
+
liveSuffix = "",
|
|
251
260
|
): string | null {
|
|
252
261
|
const children = childLines.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
253
|
-
if (children.length === 0) return renderActivityFeed(lines, final);
|
|
262
|
+
if (children.length === 0) return renderActivityFeed(lines, final, liveSuffix);
|
|
254
263
|
|
|
255
264
|
const out: string[] = [];
|
|
256
265
|
const shownParent = lines.slice(-MIRROR_MAX_LINES);
|
|
@@ -264,12 +273,13 @@ export function renderActivityFeedWithNested(
|
|
|
264
273
|
const lastChildIdx = shownChild.length - 1;
|
|
265
274
|
// `final`: the nested newest step also renders done (✓) so the left-behind
|
|
266
275
|
// feed reads as completed, not stuck on a "→ in-progress" child step.
|
|
276
|
+
// `liveSuffix` (heartbeat): appended to the nested newest in-progress step.
|
|
267
277
|
shownChild.forEach((l, i) => {
|
|
268
278
|
const t = l.length > NESTED_LINE_MAX ? l.slice(0, NESTED_LINE_MAX - 1) + "…" : l;
|
|
269
279
|
const esc = escapeFeedHtml(t);
|
|
270
280
|
out.push(
|
|
271
281
|
i === lastChildIdx && !final
|
|
272
|
-
? `${NESTED_PREFIX}<b>→ ${esc}</b>`
|
|
282
|
+
? `${NESTED_PREFIX}<b>→ ${esc}${liveSuffix}</b>`
|
|
273
283
|
: `${NESTED_PREFIX}<i>${esc}</i>`,
|
|
274
284
|
);
|
|
275
285
|
});
|