switchroom 0.14.90 → 0.14.91
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 +56 -4
- package/telegram-plugin/gateway/gateway.ts +64 -1
- package/telegram-plugin/gateway/subagent-status-surface.test.ts +118 -0
- package/telegram-plugin/gateway/subagent-status-surface.ts +69 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -49815,8 +49815,8 @@ var {
|
|
|
49815
49815
|
} = import__.default;
|
|
49816
49816
|
|
|
49817
49817
|
// src/build-info.ts
|
|
49818
|
-
var VERSION = "0.14.
|
|
49819
|
-
var COMMIT_SHA = "
|
|
49818
|
+
var VERSION = "0.14.91";
|
|
49819
|
+
var COMMIT_SHA = "e938daab";
|
|
49820
49820
|
|
|
49821
49821
|
// src/cli/agent.ts
|
|
49822
49822
|
init_source();
|
package/package.json
CHANGED
|
@@ -52900,10 +52900,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
52900
52900
|
}
|
|
52901
52901
|
|
|
52902
52902
|
// ../src/build-info.ts
|
|
52903
|
-
var VERSION = "0.14.
|
|
52904
|
-
var COMMIT_SHA = "
|
|
52905
|
-
var COMMIT_DATE = "2026-06-
|
|
52906
|
-
var LATEST_PR =
|
|
52903
|
+
var VERSION = "0.14.91";
|
|
52904
|
+
var COMMIT_SHA = "e938daab";
|
|
52905
|
+
var COMMIT_DATE = "2026-06-09T03:14:21Z";
|
|
52906
|
+
var LATEST_PR = 2241;
|
|
52907
52907
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
52908
52908
|
|
|
52909
52909
|
// gateway/boot-version.ts
|
|
@@ -53545,6 +53545,21 @@ function resolveWorkerFeedDispatch(sub, watcherDescription) {
|
|
|
53545
53545
|
};
|
|
53546
53546
|
}
|
|
53547
53547
|
|
|
53548
|
+
// gateway/subagent-status-surface.ts
|
|
53549
|
+
function resolveSubagentStatusSurface(input) {
|
|
53550
|
+
if (!input.isBackground) {
|
|
53551
|
+
if (input.liveTurnPresent)
|
|
53552
|
+
return "nest";
|
|
53553
|
+
if (!input.orphanStatusEnabled)
|
|
53554
|
+
return "skip";
|
|
53555
|
+
return input.workerFeedEnabled ? "worker-feed" : "skip";
|
|
53556
|
+
}
|
|
53557
|
+
return input.workerFeedEnabled ? "worker-feed" : "legacy-relay";
|
|
53558
|
+
}
|
|
53559
|
+
function isOrphanSubagentStatusEnabled(envVal) {
|
|
53560
|
+
return envVal !== "0";
|
|
53561
|
+
}
|
|
53562
|
+
|
|
53548
53563
|
// gateway/resolve-calling-subagent.ts
|
|
53549
53564
|
function resolveCallingSubagent(opts) {
|
|
53550
53565
|
if (opts.db == null)
|
|
@@ -64569,6 +64584,7 @@ var didOneTimeSetup = false;
|
|
|
64569
64584
|
if (watcherAgentDir != null) {
|
|
64570
64585
|
const workerFeedEnabled = isWorkerActivityFeedEnabled(process.env.SWITCHROOM_WORKER_ACTIVITY_FEED);
|
|
64571
64586
|
const foregroundNestingEnabled = process.env.SWITCHROOM_FOREGROUND_SUBAGENT_NESTING !== "0";
|
|
64587
|
+
const orphanStatusEnabled = isOrphanSubagentStatusEnabled(process.env.SWITCHROOM_ORPHAN_SUBAGENT_STATUS);
|
|
64572
64588
|
const workerActivityFeed = createWorkerActivityFeed({
|
|
64573
64589
|
bot: {
|
|
64574
64590
|
sendMessage: async (cid, text, sendOpts) => {
|
|
@@ -64650,6 +64666,22 @@ var didOneTimeSetup = false;
|
|
|
64650
64666
|
}
|
|
64651
64667
|
}
|
|
64652
64668
|
}
|
|
64669
|
+
return;
|
|
64670
|
+
}
|
|
64671
|
+
if (resolveSubagentStatusSurface({
|
|
64672
|
+
isBackground: false,
|
|
64673
|
+
liveTurnPresent: false,
|
|
64674
|
+
workerFeedEnabled,
|
|
64675
|
+
orphanStatusEnabled
|
|
64676
|
+
}) === "worker-feed") {
|
|
64677
|
+
workerActivityFeed.finish(agentId, {
|
|
64678
|
+
description: dispatch.feedDescription,
|
|
64679
|
+
lastTool: null,
|
|
64680
|
+
toolCount,
|
|
64681
|
+
latestSummary: resultText,
|
|
64682
|
+
elapsedMs: durationMs,
|
|
64683
|
+
state: outcome === "failed" ? "failed" : "done"
|
|
64684
|
+
});
|
|
64653
64685
|
}
|
|
64654
64686
|
return;
|
|
64655
64687
|
}
|
|
@@ -64716,6 +64748,26 @@ var didOneTimeSetup = false;
|
|
|
64716
64748
|
}
|
|
64717
64749
|
const isBackground = dispatch.isBackground;
|
|
64718
64750
|
if (!isBackground) {
|
|
64751
|
+
const surface = resolveSubagentStatusSurface({
|
|
64752
|
+
isBackground: false,
|
|
64753
|
+
liveTurnPresent: currentTurn != null,
|
|
64754
|
+
workerFeedEnabled,
|
|
64755
|
+
orphanStatusEnabled
|
|
64756
|
+
});
|
|
64757
|
+
if (surface === "worker-feed") {
|
|
64758
|
+
const origin = resolveSubagentOriginChat(agentId);
|
|
64759
|
+
workerActivityFeed.update(agentId, origin?.chatId || fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
|
|
64760
|
+
description: dispatch.feedDescription,
|
|
64761
|
+
lastTool,
|
|
64762
|
+
toolCount,
|
|
64763
|
+
latestSummary,
|
|
64764
|
+
elapsedMs,
|
|
64765
|
+
state: "running"
|
|
64766
|
+
}, origin?.threadId);
|
|
64767
|
+
return;
|
|
64768
|
+
}
|
|
64769
|
+
if (surface !== "nest")
|
|
64770
|
+
return;
|
|
64719
64771
|
const turn = currentTurn;
|
|
64720
64772
|
if (turn == null)
|
|
64721
64773
|
return;
|
|
@@ -471,6 +471,10 @@ import {
|
|
|
471
471
|
} from './resume-inbound-builder.js'
|
|
472
472
|
import { applySubagentsSchema, getSubagentByJsonlId } from '../registry/subagents-schema.js'
|
|
473
473
|
import { resolveWorkerFeedDispatch, type WorkerFeedDispatch } from './worker-feed-dispatch.js'
|
|
474
|
+
import {
|
|
475
|
+
resolveSubagentStatusSurface,
|
|
476
|
+
isOrphanSubagentStatusEnabled,
|
|
477
|
+
} from './subagent-status-surface.js'
|
|
474
478
|
import { formatIdleFooter } from '../idle-footer.js'
|
|
475
479
|
import { resolveCallingSubagent } from './resolve-calling-subagent.js'
|
|
476
480
|
|
|
@@ -20410,6 +20414,11 @@ void (async () => {
|
|
|
20410
20414
|
// compose draft, so no answer-stream contention). The kill-switch
|
|
20411
20415
|
// disables only the nesting; the parent's own feed is unaffected.
|
|
20412
20416
|
const foregroundNestingEnabled = process.env.SWITCHROOM_FOREGROUND_SUBAGENT_NESTING !== '0'
|
|
20417
|
+
// Orphaned-foreground status (2026-06-09): a FOREGROUND sub-agent
|
|
20418
|
+
// with no live parent turn to nest into (dispatched outside a turn,
|
|
20419
|
+
// or the turn ended while it kept running — extended autonomous
|
|
20420
|
+
// work) is surfaced via the worker feed instead of vanishing.
|
|
20421
|
+
const orphanStatusEnabled = isOrphanSubagentStatusEnabled(process.env.SWITCHROOM_ORPHAN_SUBAGENT_STATUS)
|
|
20413
20422
|
const workerActivityFeed = createWorkerActivityFeed({
|
|
20414
20423
|
bot: {
|
|
20415
20424
|
sendMessage: async (cid, text, sendOpts) => {
|
|
@@ -20609,6 +20618,29 @@ void (async () => {
|
|
|
20609
20618
|
}
|
|
20610
20619
|
}
|
|
20611
20620
|
}
|
|
20621
|
+
return
|
|
20622
|
+
}
|
|
20623
|
+
// Not nested → an orphaned foreground sub-agent that was
|
|
20624
|
+
// surfaced via the worker feed (no live turn to nest into):
|
|
20625
|
+
// finalize its message (no-op if none was posted). A
|
|
20626
|
+
// foreground result returns inline as the Task tool result, so
|
|
20627
|
+
// there is no handback to deliver — return after.
|
|
20628
|
+
if (
|
|
20629
|
+
resolveSubagentStatusSurface({
|
|
20630
|
+
isBackground: false,
|
|
20631
|
+
liveTurnPresent: false,
|
|
20632
|
+
workerFeedEnabled,
|
|
20633
|
+
orphanStatusEnabled,
|
|
20634
|
+
}) === 'worker-feed'
|
|
20635
|
+
) {
|
|
20636
|
+
void workerActivityFeed.finish(agentId, {
|
|
20637
|
+
description: dispatch.feedDescription,
|
|
20638
|
+
lastTool: null,
|
|
20639
|
+
toolCount,
|
|
20640
|
+
latestSummary: resultText,
|
|
20641
|
+
elapsedMs: durationMs,
|
|
20642
|
+
state: outcome === 'failed' ? 'failed' : 'done',
|
|
20643
|
+
})
|
|
20612
20644
|
}
|
|
20613
20645
|
return
|
|
20614
20646
|
}
|
|
@@ -20738,8 +20770,39 @@ void (async () => {
|
|
|
20738
20770
|
// activity draft rather than a separate worker message. Pure
|
|
20739
20771
|
// jsonl-tail → render (no model call), inside the
|
|
20740
20772
|
// subscription-honest boundary.
|
|
20773
|
+
//
|
|
20774
|
+
// But a foreground sub-agent with NO live turn to nest into
|
|
20775
|
+
// (dispatched outside a turn, or the turn ended while it kept
|
|
20776
|
+
// running — extended autonomous work) has nowhere to nest, and
|
|
20777
|
+
// pre-fix it silently returned here → invisible. Route through
|
|
20778
|
+
// the proven decision: an orphaned foreground sub-agent goes to
|
|
20779
|
+
// the worker feed (owner-DM fallback), not into the void.
|
|
20780
|
+
const surface = resolveSubagentStatusSurface({
|
|
20781
|
+
isBackground: false,
|
|
20782
|
+
liveTurnPresent: currentTurn != null,
|
|
20783
|
+
workerFeedEnabled,
|
|
20784
|
+
orphanStatusEnabled,
|
|
20785
|
+
})
|
|
20786
|
+
if (surface === 'worker-feed') {
|
|
20787
|
+
const origin = resolveSubagentOriginChat(agentId)
|
|
20788
|
+
void workerActivityFeed.update(
|
|
20789
|
+
agentId,
|
|
20790
|
+
origin?.chatId || fleetChatId || (loadAccess().allowFrom[0] ?? ''),
|
|
20791
|
+
{
|
|
20792
|
+
description: dispatch.feedDescription,
|
|
20793
|
+
lastTool,
|
|
20794
|
+
toolCount,
|
|
20795
|
+
latestSummary,
|
|
20796
|
+
elapsedMs,
|
|
20797
|
+
state: 'running',
|
|
20798
|
+
},
|
|
20799
|
+
origin?.threadId,
|
|
20800
|
+
)
|
|
20801
|
+
return
|
|
20802
|
+
}
|
|
20803
|
+
if (surface !== 'nest') return // 'skip' — orphan-status off
|
|
20741
20804
|
const turn = currentTurn
|
|
20742
|
-
if (turn == null) return
|
|
20805
|
+
if (turn == null) return // defensive: 'nest' implies a live turn
|
|
20743
20806
|
// Render regardless of `replyCalled` — a foreground Task
|
|
20744
20807
|
// blocks the parent, so any reply seen while it runs is an
|
|
20745
20808
|
// interim ack, never the final answer. Gating on replyCalled
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
resolveSubagentStatusSurface,
|
|
4
|
+
isOrphanSubagentStatusEnabled,
|
|
5
|
+
type SubagentStatusSurface,
|
|
6
|
+
type SubagentStatusSurfaceInput,
|
|
7
|
+
} from './subagent-status-surface.js'
|
|
8
|
+
|
|
9
|
+
// ── Human-readable map ──────────────────────────────────────────────────────
|
|
10
|
+
describe('resolveSubagentStatusSurface', () => {
|
|
11
|
+
const base: SubagentStatusSurfaceInput = {
|
|
12
|
+
isBackground: false,
|
|
13
|
+
liveTurnPresent: true,
|
|
14
|
+
workerFeedEnabled: true,
|
|
15
|
+
orphanStatusEnabled: true,
|
|
16
|
+
}
|
|
17
|
+
it('foreground + live turn → nest (unchanged default)', () => {
|
|
18
|
+
expect(resolveSubagentStatusSurface(base)).toBe('nest')
|
|
19
|
+
})
|
|
20
|
+
it('THE fix: orphaned foreground (no live turn) → worker-feed', () => {
|
|
21
|
+
expect(resolveSubagentStatusSurface({ ...base, liveTurnPresent: false })).toBe('worker-feed')
|
|
22
|
+
})
|
|
23
|
+
it('kill switch: orphaned foreground with orphanStatus OFF → skip (pre-fix invisible)', () => {
|
|
24
|
+
expect(
|
|
25
|
+
resolveSubagentStatusSurface({ ...base, liveTurnPresent: false, orphanStatusEnabled: false }),
|
|
26
|
+
).toBe('skip')
|
|
27
|
+
})
|
|
28
|
+
it('orphaned foreground but feed OFF → skip (nothing to surface through)', () => {
|
|
29
|
+
expect(
|
|
30
|
+
resolveSubagentStatusSurface({ ...base, liveTurnPresent: false, workerFeedEnabled: false }),
|
|
31
|
+
).toBe('skip')
|
|
32
|
+
})
|
|
33
|
+
it('background + feed on → worker-feed', () => {
|
|
34
|
+
expect(resolveSubagentStatusSurface({ ...base, isBackground: true, liveTurnPresent: false })).toBe('worker-feed')
|
|
35
|
+
})
|
|
36
|
+
it('background + feed off → legacy-relay', () => {
|
|
37
|
+
expect(
|
|
38
|
+
resolveSubagentStatusSurface({ ...base, isBackground: true, workerFeedEnabled: false }),
|
|
39
|
+
).toBe('legacy-relay')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('isOrphanSubagentStatusEnabled — default ON, =0 kill switch', () => {
|
|
44
|
+
it('undefined / "1" / "" → on; "0" → off', () => {
|
|
45
|
+
expect(isOrphanSubagentStatusEnabled(undefined)).toBe(true)
|
|
46
|
+
expect(isOrphanSubagentStatusEnabled('1')).toBe(true)
|
|
47
|
+
expect(isOrphanSubagentStatusEnabled('')).toBe(true)
|
|
48
|
+
expect(isOrphanSubagentStatusEnabled('0')).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// ── TOTAL-ENUMERATION DETERMINISM PROOF ─────────────────────────────────────
|
|
53
|
+
// 4 booleans = 16 reachable inputs. Enumerate all, assert totality, determinism,
|
|
54
|
+
// the documented table (independent spec), and the load-bearing invariants.
|
|
55
|
+
// (operator standard feedback_prove_finite_fsm_not_sample.)
|
|
56
|
+
function allInputs(): SubagentStatusSurfaceInput[] {
|
|
57
|
+
const rows: SubagentStatusSurfaceInput[] = []
|
|
58
|
+
for (const isBackground of [false, true])
|
|
59
|
+
for (const liveTurnPresent of [false, true])
|
|
60
|
+
for (const workerFeedEnabled of [false, true])
|
|
61
|
+
for (const orphanStatusEnabled of [false, true])
|
|
62
|
+
rows.push({ isBackground, liveTurnPresent, workerFeedEnabled, orphanStatusEnabled })
|
|
63
|
+
return rows
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Independent spec encoding (kept separate from the impl).
|
|
67
|
+
function spec(i: SubagentStatusSurfaceInput): SubagentStatusSurface {
|
|
68
|
+
if (i.isBackground) return i.workerFeedEnabled ? 'worker-feed' : 'legacy-relay'
|
|
69
|
+
if (i.liveTurnPresent) return 'nest'
|
|
70
|
+
if (!i.orphanStatusEnabled) return 'skip'
|
|
71
|
+
return i.workerFeedEnabled ? 'worker-feed' : 'skip'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('resolveSubagentStatusSurface — total enumeration (16 inputs)', () => {
|
|
75
|
+
const ROWS = allInputs()
|
|
76
|
+
|
|
77
|
+
it('exactly 16 reachable inputs (2^4)', () => {
|
|
78
|
+
expect(ROWS.length).toBe(16)
|
|
79
|
+
})
|
|
80
|
+
it('TOTAL + DETERMINISTIC: every input returns one of the four surfaces, idempotently', () => {
|
|
81
|
+
const surfaces = new Set<SubagentStatusSurface>(['nest', 'worker-feed', 'legacy-relay', 'skip'])
|
|
82
|
+
for (const i of ROWS) {
|
|
83
|
+
const a = resolveSubagentStatusSurface(i)
|
|
84
|
+
expect(surfaces.has(a)).toBe(true)
|
|
85
|
+
expect(resolveSubagentStatusSurface({ ...i })).toBe(a)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
it('PRECEDENCE: matches the documented spec on all 16 inputs', () => {
|
|
89
|
+
for (const i of ROWS) expect(resolveSubagentStatusSurface(i)).toBe(spec(i))
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('INV-ORPHAN-VISIBLE: an orphaned foreground sub-agent is NEVER skip when orphanStatus + feed are on', () => {
|
|
93
|
+
for (const i of ROWS) {
|
|
94
|
+
if (!i.isBackground && !i.liveTurnPresent && i.orphanStatusEnabled && i.workerFeedEnabled) {
|
|
95
|
+
expect(resolveSubagentStatusSurface(i)).toBe('worker-feed')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
it('INV-KILL-SWITCH: orphanStatus OFF ⇒ an orphaned foreground sub-agent is exactly the pre-fix behaviour (skip)', () => {
|
|
100
|
+
for (const i of ROWS) {
|
|
101
|
+
if (!i.isBackground && !i.liveTurnPresent && !i.orphanStatusEnabled) {
|
|
102
|
+
expect(resolveSubagentStatusSurface(i)).toBe('skip')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
it('INV-NEST-UNCHANGED: a foreground sub-agent with a live turn is ALWAYS nest, independent of the other flags', () => {
|
|
107
|
+
for (const i of ROWS) {
|
|
108
|
+
if (!i.isBackground && i.liveTurnPresent) expect(resolveSubagentStatusSurface(i)).toBe('nest')
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
it('INV-BACKGROUND-UNCHANGED: background routing depends ONLY on the feed flag, never on liveTurn/orphanStatus', () => {
|
|
112
|
+
for (const i of ROWS) {
|
|
113
|
+
if (i.isBackground) {
|
|
114
|
+
expect(resolveSubagentStatusSurface(i)).toBe(i.workerFeedEnabled ? 'worker-feed' : 'legacy-relay')
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where does a sub-agent's live status go?
|
|
3
|
+
*
|
|
4
|
+
* A sub-agent's progress is surfaced on one of four "surfaces". This pure
|
|
5
|
+
* decision picks which, so the routing is provable by total enumeration rather
|
|
6
|
+
* than buried in the gateway's imperative branches.
|
|
7
|
+
*
|
|
8
|
+
* - `nest` — a FOREGROUND sub-agent running inside a LIVE parent turn:
|
|
9
|
+
* its narrative nests under the parent's activity draft
|
|
10
|
+
* (the progress card). The default, unchanged.
|
|
11
|
+
* - `worker-feed` — a BACKGROUND worker (the `🛠 Worker` edit-in-place
|
|
12
|
+
* message), OR a FOREGROUND sub-agent that has NO live
|
|
13
|
+
* parent turn to nest into (dispatched outside a turn, or
|
|
14
|
+
* the turn ended while it kept running). The latter is the
|
|
15
|
+
* 2026-06-09 fix: extended autonomous work was invisible
|
|
16
|
+
* because foreground status was turn-scoped and a sub-agent
|
|
17
|
+
* with no turn silently returned. It now reuses the worker
|
|
18
|
+
* feed (with the same owner-DM fallback background workers
|
|
19
|
+
* already use), so post-turn work is always visible.
|
|
20
|
+
* - `legacy-relay` — a BACKGROUND worker when the worker feed is OFF: fall
|
|
21
|
+
* back to the legacy "still working" injected-inbound relay.
|
|
22
|
+
* - `skip` — nothing to surface (kill-switch off for an orphaned
|
|
23
|
+
* foreground, or no feed to surface it through).
|
|
24
|
+
*
|
|
25
|
+
* Determinism: the input space is 4 booleans = 16 rows, enumerated and proven
|
|
26
|
+
* in subagent-status-surface.test.ts (operator standard
|
|
27
|
+
* feedback_prove_finite_fsm_not_sample). The load-bearing invariant: an
|
|
28
|
+
* orphaned foreground sub-agent (no live turn) is `worker-feed`, never `skip`,
|
|
29
|
+
* whenever the orphan-status flag and the feed are both on.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export type SubagentStatusSurface = 'nest' | 'worker-feed' | 'legacy-relay' | 'skip'
|
|
33
|
+
|
|
34
|
+
export interface SubagentStatusSurfaceInput {
|
|
35
|
+
/** run_in_background dispatch (registry `subagents.background`). */
|
|
36
|
+
isBackground: boolean
|
|
37
|
+
/**
|
|
38
|
+
* A LIVE parent turn exists to nest this (foreground) sub-agent into.
|
|
39
|
+
* onProgress: `currentTurn != null`. onFinish: `currentTurn != null && it was
|
|
40
|
+
* actually nested` (so a foreground sub-agent that was surfaced via the worker
|
|
41
|
+
* feed — never nested — finalizes through the feed, not a turn collapse).
|
|
42
|
+
* Ignored for background sub-agents.
|
|
43
|
+
*/
|
|
44
|
+
liveTurnPresent: boolean
|
|
45
|
+
/** SWITCHROOM_WORKER_ACTIVITY_FEED on. */
|
|
46
|
+
workerFeedEnabled: boolean
|
|
47
|
+
/** SWITCHROOM_ORPHAN_SUBAGENT_STATUS on — surface no-parent-turn foreground
|
|
48
|
+
* sub-agents via the worker feed. Off = pre-fix behaviour (invisible). */
|
|
49
|
+
orphanStatusEnabled: boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveSubagentStatusSurface(
|
|
53
|
+
input: SubagentStatusSurfaceInput,
|
|
54
|
+
): SubagentStatusSurface {
|
|
55
|
+
if (!input.isBackground) {
|
|
56
|
+
// Foreground sub-agent.
|
|
57
|
+
if (input.liveTurnPresent) return 'nest'
|
|
58
|
+
// Orphaned foreground: no live turn to nest into — the invisible case.
|
|
59
|
+
if (!input.orphanStatusEnabled) return 'skip' // kill switch: pre-fix behaviour
|
|
60
|
+
return input.workerFeedEnabled ? 'worker-feed' : 'skip' // surfacing needs the feed
|
|
61
|
+
}
|
|
62
|
+
// Background worker.
|
|
63
|
+
return input.workerFeedEnabled ? 'worker-feed' : 'legacy-relay'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** SWITCHROOM_ORPHAN_SUBAGENT_STATUS — default ON; `=0` restores pre-fix (invisible) behaviour. */
|
|
67
|
+
export function isOrphanSubagentStatusEnabled(envVal: string | undefined): boolean {
|
|
68
|
+
return envVal !== '0'
|
|
69
|
+
}
|