switchroom 0.14.16 โ 0.14.17
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/cli/switchroom.js +2 -2
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +40 -20
- package/telegram-plugin/gateway/gateway.ts +20 -28
- package/telegram-plugin/gateway/worker-feed-dispatch.ts +37 -0
- package/telegram-plugin/subagent-watcher.ts +10 -1
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +63 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -49413,8 +49413,8 @@ var {
|
|
|
49413
49413
|
} = import__.default;
|
|
49414
49414
|
|
|
49415
49415
|
// src/build-info.ts
|
|
49416
|
-
var VERSION = "0.14.
|
|
49417
|
-
var COMMIT_SHA = "
|
|
49416
|
+
var VERSION = "0.14.17";
|
|
49417
|
+
var COMMIT_SHA = "95c1d475";
|
|
49418
49418
|
|
|
49419
49419
|
// src/cli/agent.ts
|
|
49420
49420
|
init_source();
|
package/package.json
CHANGED
|
@@ -51140,10 +51140,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
51140
51140
|
}
|
|
51141
51141
|
|
|
51142
51142
|
// ../src/build-info.ts
|
|
51143
|
-
var VERSION = "0.14.
|
|
51144
|
-
var COMMIT_SHA = "
|
|
51145
|
-
var COMMIT_DATE = "2026-05-
|
|
51146
|
-
var LATEST_PR =
|
|
51143
|
+
var VERSION = "0.14.17";
|
|
51144
|
+
var COMMIT_SHA = "95c1d475";
|
|
51145
|
+
var COMMIT_DATE = "2026-05-30T05:17:47Z";
|
|
51146
|
+
var LATEST_PR = 2005;
|
|
51147
51147
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
51148
51148
|
|
|
51149
51149
|
// gateway/boot-version.ts
|
|
@@ -51605,6 +51605,34 @@ function applySubagentsSchema(db2) {
|
|
|
51605
51605
|
}
|
|
51606
51606
|
db2.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
|
|
51607
51607
|
}
|
|
51608
|
+
function mapSubagentRow(row) {
|
|
51609
|
+
return {
|
|
51610
|
+
id: row.id,
|
|
51611
|
+
parent_session_id: row.parent_session_id,
|
|
51612
|
+
parent_turn_key: row.parent_turn_key,
|
|
51613
|
+
agent_type: row.agent_type,
|
|
51614
|
+
description: row.description,
|
|
51615
|
+
background: row.background !== 0,
|
|
51616
|
+
started_at: row.started_at,
|
|
51617
|
+
last_activity_at: row.last_activity_at,
|
|
51618
|
+
ended_at: row.ended_at,
|
|
51619
|
+
status: row.status,
|
|
51620
|
+
result_summary: row.result_summary,
|
|
51621
|
+
jsonl_agent_id: row.jsonl_agent_id
|
|
51622
|
+
};
|
|
51623
|
+
}
|
|
51624
|
+
function getSubagentByJsonlId(db2, jsonlAgentId) {
|
|
51625
|
+
const row = db2.prepare("SELECT * FROM subagents WHERE jsonl_agent_id = ?").get(jsonlAgentId);
|
|
51626
|
+
return row ? mapSubagentRow(row) : null;
|
|
51627
|
+
}
|
|
51628
|
+
|
|
51629
|
+
// gateway/worker-feed-dispatch.ts
|
|
51630
|
+
function resolveWorkerFeedDispatch(sub, watcherDescription) {
|
|
51631
|
+
return {
|
|
51632
|
+
isBackground: sub?.background ?? false,
|
|
51633
|
+
feedDescription: (sub?.description ?? "") || watcherDescription
|
|
51634
|
+
};
|
|
51635
|
+
}
|
|
51608
51636
|
|
|
51609
51637
|
// gateway/resolve-calling-subagent.ts
|
|
51610
51638
|
function resolveCallingSubagent(opts) {
|
|
@@ -61332,8 +61360,6 @@ var didOneTimeSetup = false;
|
|
|
61332
61360
|
onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs }) => {
|
|
61333
61361
|
deferredDoneReactions.promote();
|
|
61334
61362
|
let fleetChatId = "";
|
|
61335
|
-
let isBackground = false;
|
|
61336
|
-
let dispatchDesc = "";
|
|
61337
61363
|
try {
|
|
61338
61364
|
const fleets = progressDriver?.peekAllFleets() ?? [];
|
|
61339
61365
|
for (const f of fleets) {
|
|
@@ -61343,18 +61369,16 @@ var didOneTimeSetup = false;
|
|
|
61343
61369
|
}
|
|
61344
61370
|
}
|
|
61345
61371
|
} catch {}
|
|
61372
|
+
let dispatch = resolveWorkerFeedDispatch(null, description);
|
|
61346
61373
|
if (turnsDb != null) {
|
|
61347
61374
|
try {
|
|
61348
|
-
|
|
61349
|
-
if (row != null) {
|
|
61350
|
-
isBackground = row.background === 1;
|
|
61351
|
-
dispatchDesc = row.description ?? "";
|
|
61352
|
-
}
|
|
61375
|
+
dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description);
|
|
61353
61376
|
} catch {}
|
|
61354
61377
|
}
|
|
61378
|
+
const isBackground = dispatch.isBackground;
|
|
61355
61379
|
if (workerFeedEnabled) {
|
|
61356
61380
|
workerActivityFeed.finish(agentId, {
|
|
61357
|
-
description:
|
|
61381
|
+
description: dispatch.feedDescription,
|
|
61358
61382
|
lastTool: null,
|
|
61359
61383
|
toolCount,
|
|
61360
61384
|
latestSummary: resultText,
|
|
@@ -61396,8 +61420,6 @@ var didOneTimeSetup = false;
|
|
|
61396
61420
|
},
|
|
61397
61421
|
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount }) => {
|
|
61398
61422
|
let fleetChatId = "";
|
|
61399
|
-
let isBackground = false;
|
|
61400
|
-
let dispatchDesc = "";
|
|
61401
61423
|
try {
|
|
61402
61424
|
const fleets = progressDriver?.peekAllFleets() ?? [];
|
|
61403
61425
|
for (const f of fleets) {
|
|
@@ -61407,20 +61429,18 @@ var didOneTimeSetup = false;
|
|
|
61407
61429
|
}
|
|
61408
61430
|
}
|
|
61409
61431
|
} catch {}
|
|
61432
|
+
let dispatch = resolveWorkerFeedDispatch(null, description);
|
|
61410
61433
|
if (turnsDb != null) {
|
|
61411
61434
|
try {
|
|
61412
|
-
|
|
61413
|
-
if (row != null) {
|
|
61414
|
-
isBackground = row.background === 1;
|
|
61415
|
-
dispatchDesc = row.description ?? "";
|
|
61416
|
-
}
|
|
61435
|
+
dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description);
|
|
61417
61436
|
} catch {}
|
|
61418
61437
|
}
|
|
61438
|
+
const isBackground = dispatch.isBackground;
|
|
61419
61439
|
if (!isBackground)
|
|
61420
61440
|
return;
|
|
61421
61441
|
if (workerFeedEnabled) {
|
|
61422
61442
|
workerActivityFeed.update(agentId, fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
|
|
61423
|
-
description:
|
|
61443
|
+
description: dispatch.feedDescription,
|
|
61424
61444
|
lastTool,
|
|
61425
61445
|
toolCount,
|
|
61426
61446
|
latestSummary,
|
|
@@ -418,7 +418,8 @@ import {
|
|
|
418
418
|
findMostRecentInterruptedTurn,
|
|
419
419
|
findRecentTurnsForChat,
|
|
420
420
|
} from '../registry/turns-schema.js'
|
|
421
|
-
import { applySubagentsSchema } from '../registry/subagents-schema.js'
|
|
421
|
+
import { applySubagentsSchema, getSubagentByJsonlId } from '../registry/subagents-schema.js'
|
|
422
|
+
import { resolveWorkerFeedDispatch, type WorkerFeedDispatch } from './worker-feed-dispatch.js'
|
|
422
423
|
import { formatIdleFooter } from '../idle-footer.js'
|
|
423
424
|
import { resolveCallingSubagent } from './resolve-calling-subagent.js'
|
|
424
425
|
|
|
@@ -17402,11 +17403,6 @@ void (async () => {
|
|
|
17402
17403
|
// independent of the gateway โ see
|
|
17403
17404
|
// `subagent-handback-decision.test.ts`.
|
|
17404
17405
|
let fleetChatId = ''
|
|
17405
|
-
let isBackground = false
|
|
17406
|
-
// Dispatch-time task description from the registry row โ
|
|
17407
|
-
// see the onProgress note; used for the feed's terminal recap
|
|
17408
|
-
// header so it matches the running header ("ยท <real task>").
|
|
17409
|
-
let dispatchDesc = ''
|
|
17410
17406
|
try {
|
|
17411
17407
|
const fleets = progressDriver?.peekAllFleets() ?? []
|
|
17412
17408
|
for (const f of fleets) {
|
|
@@ -17419,17 +17415,18 @@ void (async () => {
|
|
|
17419
17415
|
// peek failures are non-fatal โ fall through to the
|
|
17420
17416
|
// owner-chat fallback inside decideSubagentHandback.
|
|
17421
17417
|
}
|
|
17418
|
+
// Background flag + feed header description, both derived from
|
|
17419
|
+
// the registry row via the pure resolveWorkerFeedDispatch
|
|
17420
|
+
// (worker-feed-dispatch.ts, pinned by its test). Best-effort:
|
|
17421
|
+
// a DB hiccup keeps the watcher's generic label rather than
|
|
17422
|
+
// throwing out of the terminal handler.
|
|
17423
|
+
let dispatch: WorkerFeedDispatch = resolveWorkerFeedDispatch(null, description)
|
|
17422
17424
|
if (turnsDb != null) {
|
|
17423
17425
|
try {
|
|
17424
|
-
|
|
17425
|
-
.prepare('SELECT background, description FROM subagents WHERE jsonl_agent_id = ?')
|
|
17426
|
-
.get(agentId) as { background: number; description: string | null } | undefined
|
|
17427
|
-
if (row != null) {
|
|
17428
|
-
isBackground = row.background === 1
|
|
17429
|
-
dispatchDesc = row.description ?? ''
|
|
17430
|
-
}
|
|
17426
|
+
dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description)
|
|
17431
17427
|
} catch { /* best-effort */ }
|
|
17432
17428
|
}
|
|
17429
|
+
const isBackground = dispatch.isBackground
|
|
17433
17430
|
// #PR2 live worker-feed: force the terminal recap edit on
|
|
17434
17431
|
// the worker's live message. No-op when no message was ever
|
|
17435
17432
|
// posted (trivial workers stay silent; handback covers them).
|
|
@@ -17437,7 +17434,7 @@ void (async () => {
|
|
|
17437
17434
|
// it to 'done' so an already-posted message still finalizes.
|
|
17438
17435
|
if (workerFeedEnabled) {
|
|
17439
17436
|
void workerActivityFeed.finish(agentId, {
|
|
17440
|
-
description:
|
|
17437
|
+
description: dispatch.feedDescription,
|
|
17441
17438
|
lastTool: null,
|
|
17442
17439
|
toolCount,
|
|
17443
17440
|
latestSummary: resultText,
|
|
@@ -17517,12 +17514,6 @@ void (async () => {
|
|
|
17517
17514
|
// lives in the `onFinish` block just above.
|
|
17518
17515
|
onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount }) => {
|
|
17519
17516
|
let fleetChatId = ''
|
|
17520
|
-
let isBackground = false
|
|
17521
|
-
// The watcher's `description` is its 'sub-agent' default (it
|
|
17522
|
-
// never reassigns it from the worker jsonl). The dispatch-time
|
|
17523
|
-
// task description lives in the registry row โ prefer it so the
|
|
17524
|
-
// feed header reads "๐ง Worker ยท <real task>" not "ยท sub-agent".
|
|
17525
|
-
let dispatchDesc = ''
|
|
17526
17517
|
try {
|
|
17527
17518
|
const fleets = progressDriver?.peekAllFleets() ?? []
|
|
17528
17519
|
for (const f of fleets) {
|
|
@@ -17532,17 +17523,18 @@ void (async () => {
|
|
|
17532
17523
|
}
|
|
17533
17524
|
}
|
|
17534
17525
|
} catch { /* peek failures non-fatal */ }
|
|
17526
|
+
// The watcher's `description` is its 'sub-agent' default (it
|
|
17527
|
+
// never reassigns it from the worker jsonl). The dispatch-time
|
|
17528
|
+
// task description lives in the registry row โ resolveWorkerFeedDispatch
|
|
17529
|
+
// prefers it so the header reads "๐ง Worker ยท <real task>" not
|
|
17530
|
+
// "ยท sub-agent" (worker-feed-dispatch.ts, pinned by its test).
|
|
17531
|
+
let dispatch: WorkerFeedDispatch = resolveWorkerFeedDispatch(null, description)
|
|
17535
17532
|
if (turnsDb != null) {
|
|
17536
17533
|
try {
|
|
17537
|
-
|
|
17538
|
-
.prepare('SELECT background, description FROM subagents WHERE jsonl_agent_id = ?')
|
|
17539
|
-
.get(agentId) as { background: number; description: string | null } | undefined
|
|
17540
|
-
if (row != null) {
|
|
17541
|
-
isBackground = row.background === 1
|
|
17542
|
-
dispatchDesc = row.description ?? ''
|
|
17543
|
-
}
|
|
17534
|
+
dispatch = resolveWorkerFeedDispatch(getSubagentByJsonlId(turnsDb, agentId), description)
|
|
17544
17535
|
} catch { /* best-effort */ }
|
|
17545
17536
|
}
|
|
17537
|
+
const isBackground = dispatch.isBackground
|
|
17546
17538
|
if (!isBackground) return // skip overhead for foreground
|
|
17547
17539
|
|
|
17548
17540
|
// #PR2 live worker-feed: when ON, the worker's live chat
|
|
@@ -17556,7 +17548,7 @@ void (async () => {
|
|
|
17556
17548
|
agentId,
|
|
17557
17549
|
fleetChatId || (loadAccess().allowFrom[0] ?? ''),
|
|
17558
17550
|
{
|
|
17559
|
-
description:
|
|
17551
|
+
description: dispatch.feedDescription,
|
|
17560
17552
|
lastTool,
|
|
17561
17553
|
toolCount,
|
|
17562
17554
|
latestSummary,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Subagent } from '../registry/subagents-schema.js'
|
|
2
|
+
|
|
3
|
+
export interface WorkerFeedDispatch {
|
|
4
|
+
/** True when the sub-agent was dispatched with `run_in_background: true`. */
|
|
5
|
+
isBackground: boolean
|
|
6
|
+
/**
|
|
7
|
+
* The human-readable task to render in the feed header
|
|
8
|
+
* ("๐ง Worker ยท <feedDescription>").
|
|
9
|
+
*/
|
|
10
|
+
feedDescription: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the two registry-derived inputs the worker-activity feed needs:
|
|
15
|
+
* whether the sub-agent was a background dispatch, and the task description
|
|
16
|
+
* to show in the feed header.
|
|
17
|
+
*
|
|
18
|
+
* The live watcher only carries a generic 'sub-agent' label โ it never
|
|
19
|
+
* reassigns `description` from the worker jsonl. The real dispatch-time
|
|
20
|
+
* description lives in the registry `subagents` row (written by the pretool
|
|
21
|
+
* hook from the `Agent(description:)` input). Prefer it; fall back to the
|
|
22
|
+
* watcher's label only when the row is missing or its description is empty.
|
|
23
|
+
*
|
|
24
|
+
* Pure + DB-free so it pins the #2002 behavior under both vitest and bun โ
|
|
25
|
+
* see worker-feed-dispatch.test.ts. The gateway must never inline this
|
|
26
|
+
* decision again: a regression here silently reverts the feed header to
|
|
27
|
+
* "ยท sub-agent".
|
|
28
|
+
*/
|
|
29
|
+
export function resolveWorkerFeedDispatch(
|
|
30
|
+
sub: Subagent | null,
|
|
31
|
+
watcherDescription: string,
|
|
32
|
+
): WorkerFeedDispatch {
|
|
33
|
+
return {
|
|
34
|
+
isBackground: sub?.background ?? false,
|
|
35
|
+
feedDescription: (sub?.description ?? '') || watcherDescription,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -73,7 +73,13 @@ export interface WorkerEntry {
|
|
|
73
73
|
readonly agentId: string
|
|
74
74
|
/** File path of the JSONL. */
|
|
75
75
|
readonly filePath: string
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Generic 'sub-agent' placeholder โ the watcher deliberately does NOT
|
|
78
|
+
* reassign this from the worker jsonl (see the init at construction and
|
|
79
|
+
* the "Do NOT overwrite" note in the line-handler). The real dispatch-time
|
|
80
|
+
* task description lives in the registry `subagents` row; the gateway reads
|
|
81
|
+
* it there via resolveWorkerFeedDispatch for the worker-feed header.
|
|
82
|
+
*/
|
|
77
83
|
description: string
|
|
78
84
|
/** Current lifecycle state. */
|
|
79
85
|
state: WorkerState
|
|
@@ -864,6 +870,9 @@ export function startSubagentWatcher(config: SubagentWatcherConfig): SubagentWat
|
|
|
864
870
|
const entry: WorkerEntry = {
|
|
865
871
|
agentId,
|
|
866
872
|
filePath,
|
|
873
|
+
// Generic placeholder only โ never overwritten from the jsonl. The
|
|
874
|
+
// gateway substitutes the real registry description for the worker
|
|
875
|
+
// feed (resolveWorkerFeedDispatch). See the WorkerEntry.description doc.
|
|
867
876
|
description: 'sub-agent',
|
|
868
877
|
state: 'running',
|
|
869
878
|
dispatchedAt: n,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { Subagent } from '../registry/subagents-schema.js'
|
|
3
|
+
import { resolveWorkerFeedDispatch } from '../gateway/worker-feed-dispatch.js'
|
|
4
|
+
|
|
5
|
+
function makeSub(over: Partial<Subagent>): Subagent {
|
|
6
|
+
return {
|
|
7
|
+
id: 'toolu_01ABC',
|
|
8
|
+
parent_session_id: null,
|
|
9
|
+
parent_turn_key: null,
|
|
10
|
+
agent_type: 'general-purpose',
|
|
11
|
+
description: null,
|
|
12
|
+
background: false,
|
|
13
|
+
started_at: 0,
|
|
14
|
+
last_activity_at: null,
|
|
15
|
+
ended_at: null,
|
|
16
|
+
status: 'running',
|
|
17
|
+
result_summary: null,
|
|
18
|
+
jsonl_agent_id: 'a37ad7639ae61476c',
|
|
19
|
+
...over,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('resolveWorkerFeedDispatch (#2002 regression pin)', () => {
|
|
24
|
+
it('uses the real registry description for the feed header, not the watcher label', () => {
|
|
25
|
+
const sub = makeSub({ background: true, description: 'Background ten-step worker' })
|
|
26
|
+
const out = resolveWorkerFeedDispatch(sub, 'sub-agent')
|
|
27
|
+
expect(out.isBackground).toBe(true)
|
|
28
|
+
expect(out.feedDescription).toBe('Background ten-step worker')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('falls back to the watcher label when the registry row is missing', () => {
|
|
32
|
+
const out = resolveWorkerFeedDispatch(null, 'sub-agent')
|
|
33
|
+
expect(out.isBackground).toBe(false)
|
|
34
|
+
expect(out.feedDescription).toBe('sub-agent')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('falls back to the watcher label when the registry description is null', () => {
|
|
38
|
+
const sub = makeSub({ background: true, description: null })
|
|
39
|
+
const out = resolveWorkerFeedDispatch(sub, 'sub-agent')
|
|
40
|
+
expect(out.isBackground).toBe(true)
|
|
41
|
+
expect(out.feedDescription).toBe('sub-agent')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('falls back to the watcher label when the registry description is empty', () => {
|
|
45
|
+
const sub = makeSub({ background: true, description: '' })
|
|
46
|
+
const out = resolveWorkerFeedDispatch(sub, 'sub-agent')
|
|
47
|
+
expect(out.feedDescription).toBe('sub-agent')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('reports a foreground sub-agent as not background', () => {
|
|
51
|
+
const sub = makeSub({ background: false, description: 'inline helper' })
|
|
52
|
+
const out = resolveWorkerFeedDispatch(sub, 'sub-agent')
|
|
53
|
+
expect(out.isBackground).toBe(false)
|
|
54
|
+
// description still resolves โ callers gate on isBackground separately.
|
|
55
|
+
expect(out.feedDescription).toBe('inline helper')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('a missing row defaults isBackground false so the feed never fires blind', () => {
|
|
59
|
+
// The gateway gates the feed on isBackground; a registry miss must not
|
|
60
|
+
// flip a foreground turn into a background one.
|
|
61
|
+
expect(resolveWorkerFeedDispatch(null, '').isBackground).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
})
|