switchroom 0.7.15 → 0.10.0
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 +51 -59
- package/bin/run-hook.sh +27 -11
- package/bin/timezone-hook.sh +9 -7
- package/dist/agent-scheduler/index.js +410 -133
- package/dist/auth-broker/index.js +13932 -0
- package/dist/cli/switchroom.js +26937 -5601
- package/dist/host-control/main.js +12702 -0
- package/dist/vault/approvals/kernel-server.js +467 -184
- package/dist/vault/broker/server.js +1430 -724
- package/examples/minimal.yaml +63 -0
- package/examples/personal-google-workspace-mcp/.env.example +34 -0
- package/examples/personal-google-workspace-mcp/README.md +194 -0
- package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
- package/examples/switchroom.yaml +220 -0
- package/package.json +7 -4
- package/profiles/_base/settings.json.hbs +20 -5
- package/profiles/_base/start.sh.hbs +16 -3
- package/profiles/_shared/agent-self-service.md.hbs +126 -0
- package/profiles/_shared/telegram-style.md.hbs +20 -90
- package/profiles/_shared/vault-protocol.md.hbs +68 -0
- package/profiles/default/CLAUDE.md +50 -96
- package/profiles/default/CLAUDE.md.hbs +36 -6
- package/profiles/default/workspace/SOUL.md.hbs +12 -5
- package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
- package/skills/buildkite-agent-runtime/SKILL.md +44 -11
- package/skills/buildkite-api/SKILL.md +31 -8
- package/skills/buildkite-cli/SKILL.md +27 -9
- package/skills/buildkite-migration/SKILL.md +22 -9
- package/skills/buildkite-pipelines/SKILL.md +26 -9
- package/skills/buildkite-secure-delivery/SKILL.md +23 -9
- package/skills/buildkite-test-engine/SKILL.md +25 -8
- package/skills/docx/SKILL.md +1 -1
- 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/skills/file-bug/SKILL.md +34 -6
- package/skills/humanizer/SKILL.md +15 -0
- package/skills/humanizer-calibrate/SKILL.md +7 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/pdf/SKILL.md +1 -1
- package/skills/pptx/SKILL.md +1 -1
- package/skills/skill-creator/SKILL.md +21 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/skills/switchroom-cli/SKILL.md +63 -64
- package/skills/switchroom-health/SKILL.md +23 -10
- package/skills/switchroom-install/SKILL.md +3 -3
- package/skills/switchroom-manage/SKILL.md +26 -19
- package/skills/switchroom-runtime/SKILL.md +191 -0
- package/skills/switchroom-status/SKILL.md +27 -2
- package/skills/telegram-test-harness/SKILL.md +3 -0
- package/skills/token-helpers/SKILL.md +24 -1
- package/skills/webapp-testing/SKILL.md +31 -1
- package/skills/xlsx/SKILL.md +1 -1
- package/telegram-plugin/admin-commands/index.ts +7 -5
- package/telegram-plugin/analytics-posthog.ts +191 -0
- package/telegram-plugin/bridge/bridge.ts +69 -0
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/dist/bridge/bridge.js +194 -119
- package/telegram-plugin/dist/gateway/gateway.js +23611 -19671
- package/telegram-plugin/dist/server.js +245 -189
- package/telegram-plugin/first-paint.ts +3 -24
- package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
- package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
- package/telegram-plugin/gateway/auth-command.ts +794 -0
- package/telegram-plugin/gateway/auth-line.ts +123 -0
- package/telegram-plugin/gateway/boot-card.ts +169 -40
- package/telegram-plugin/gateway/boot-issue-cache.ts +308 -0
- package/telegram-plugin/gateway/boot-probes.ts +166 -123
- package/telegram-plugin/gateway/boot-reason.ts +41 -7
- package/telegram-plugin/gateway/boot-version.ts +66 -0
- package/telegram-plugin/gateway/gateway.ts +3499 -1885
- package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +18 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +106 -0
- package/telegram-plugin/gateway/quarantine.ts +69 -0
- package/telegram-plugin/gateway/quota-cache.ts +9 -4
- package/telegram-plugin/gateway/reaction-trigger.ts +401 -0
- package/telegram-plugin/gateway/recent-denials.test.ts +103 -0
- package/telegram-plugin/gateway/recent-denials.ts +77 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +109 -31
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +125 -0
- package/telegram-plugin/history.ts +91 -0
- package/telegram-plugin/hooks/hooks.json +10 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +130 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +19 -2
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +22 -2
- package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
- package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
- package/telegram-plugin/inbound-classifier.ts +50 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +136 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/package.json +4 -2
- package/telegram-plugin/permission-rule.ts +51 -0
- package/telegram-plugin/permission-title.ts +56 -0
- package/telegram-plugin/quota-check.ts +19 -41
- package/telegram-plugin/registry/reaper.ts +223 -0
- package/telegram-plugin/retry-api-call.ts +80 -0
- package/telegram-plugin/runtime-metrics.ts +177 -0
- package/telegram-plugin/scripts/build.mjs +0 -1
- package/telegram-plugin/secret-detect/index.ts +24 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +64 -12
- package/telegram-plugin/secret-detect/vault-error.ts +78 -11
- package/telegram-plugin/secret-detect/vault-write.ts +14 -2
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/session-tail.ts +6 -1
- package/telegram-plugin/shared/bot-runtime.ts +5 -4
- package/telegram-plugin/silence-poke.ts +420 -0
- package/telegram-plugin/silent-end.ts +174 -0
- package/telegram-plugin/stream-controller.ts +13 -0
- package/telegram-plugin/stream-reply-handler.ts +7 -0
- package/telegram-plugin/subagent-watcher.ts +213 -4
- package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
- package/telegram-plugin/tests/boot-card-issue-dedup.test.ts +247 -0
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +182 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +65 -2
- package/telegram-plugin/tests/boot-card-render.test.ts +146 -0
- package/telegram-plugin/tests/boot-card-silent-on-operator.test.ts +103 -0
- package/telegram-plugin/tests/boot-probes.test.ts +216 -10
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/finalize-callback.test.ts +190 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +26 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +12 -3
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +104 -0
- package/telegram-plugin/tests/history-reaper.test.ts +378 -0
- package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
- package/telegram-plugin/tests/inbound-classifier.test.ts +76 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +267 -0
- package/telegram-plugin/tests/issues-card.test.ts +49 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +132 -0
- package/telegram-plugin/tests/permission-rule.test.ts +80 -1
- package/telegram-plugin/tests/permission-title.test.ts +31 -0
- package/telegram-plugin/tests/quota-check.test.ts +5 -35
- package/telegram-plugin/tests/races.test.ts +179 -0
- package/telegram-plugin/tests/reaction-trigger-flow.test.ts +353 -0
- package/telegram-plugin/tests/reaction-trigger.test.ts +397 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +152 -1
- package/telegram-plugin/tests/runtime-metrics.test.ts +145 -0
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +155 -0
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +133 -0
- package/telegram-plugin/tests/secret-detect-false-positives.test.ts +137 -0
- package/telegram-plugin/tests/silence-poke.test.ts +493 -0
- package/telegram-plugin/tests/silent-end.test.ts +206 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +107 -0
- package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +224 -0
- package/telegram-plugin/tests/subagent-watcher-stall-terminal.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +263 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +81 -0
- package/telegram-plugin/tests/vault-approval-posture.test.ts +256 -0
- package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +73 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +226 -0
- package/telegram-plugin/tests/vault-grant-union.test.ts +130 -0
- package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +140 -0
- package/telegram-plugin/tests/vault-posture-quarantine.test.ts +104 -0
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +114 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +106 -0
- package/telegram-plugin/turn-signal-tracker.ts +100 -24
- package/telegram-plugin/uat/SETUP.md +210 -35
- package/telegram-plugin/uat/assertions.ts +264 -37
- package/telegram-plugin/uat/driver-info.ts +57 -0
- package/telegram-plugin/uat/driver.ts +590 -51
- package/telegram-plugin/uat/harness.ts +140 -94
- package/telegram-plugin/uat/load-env.test.ts +72 -0
- package/telegram-plugin/uat/load-env.ts +48 -0
- package/telegram-plugin/uat/login.ts +96 -53
- package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
- package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
- package/telegram-plugin/uat/runners/report.ts +150 -0
- package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
- package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
- package/telegram-plugin/uat/runners/scorer.ts +106 -0
- package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
- package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
- package/telegram-plugin/uat/scenarios/ask-user-button-tap-dm.test.ts +141 -0
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +255 -0
- package/telegram-plugin/uat/scenarios/fuzz-human-style-dm.test.ts +275 -0
- package/telegram-plugin/uat/scenarios/fuzz-random-prompts-dm.test.ts +146 -0
- package/telegram-plugin/uat/scenarios/fuzz-status-ask-dm.test.ts +486 -0
- package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +100 -0
- package/telegram-plugin/uat/scenarios/jtbd-soft-commit-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-status-query-dm.test.ts +49 -0
- package/telegram-plugin/uat/scenarios/location-inbound-dm.test.ts +65 -0
- package/telegram-plugin/uat/scenarios/midturn-silent-dm.test.ts +175 -0
- package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +142 -0
- package/telegram-plugin/uat/scenarios/reactions-trigger-turn-dm.test.ts +96 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-deletes-original-dm.test.ts +123 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-no-false-positive-dm.test.ts +87 -0
- package/telegram-plugin/uat/scenarios/silence-poke-soft-dm.test.ts +155 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +95 -0
- package/telegram-plugin/uat/scenarios/smoke-dm-reply.test.ts +57 -0
- package/telegram-plugin/uat/scenarios/subagent-watcher-no-rerun-dm.test.ts +135 -0
- package/telegram-plugin/uat/scenarios/vault-approval-posture-telegram-id-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/vault-audit-allow-dm.test.ts +108 -0
- package/telegram-plugin/uat/scenarios/vault-grant-auto-resume-dm.test.ts +121 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-concurrent-dm.test.ts +161 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-end-to-end-dm.test.ts +158 -0
- package/telegram-plugin/uat/scenarios/voice-inbound-dm.test.ts +65 -0
- package/telegram-plugin/vault-approval-posture.ts +42 -0
- package/telegram-plugin/welcome-text.ts +1 -0
- package/telegram-plugin/active-pins-sweep.ts +0 -204
- package/telegram-plugin/active-pins.ts +0 -146
- package/telegram-plugin/auth-dashboard.ts +0 -1104
- package/telegram-plugin/auth-slot-parser.ts +0 -497
- package/telegram-plugin/card-event-log.ts +0 -138
- package/telegram-plugin/dist/foreman/foreman.js +0 -31106
- package/telegram-plugin/docs/multi-agent-card-design.md +0 -847
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +0 -144
- package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
- package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
- package/telegram-plugin/foreman/foreman.ts +0 -1165
- package/telegram-plugin/foreman/setup-flow.ts +0 -345
- package/telegram-plugin/foreman/setup-state.ts +0 -239
- package/telegram-plugin/foreman/state.ts +0 -203
- package/telegram-plugin/pin-event-log.ts +0 -76
- package/telegram-plugin/progress-card-driver.ts +0 -2886
- package/telegram-plugin/progress-card-pin-manager.ts +0 -589
- package/telegram-plugin/progress-card-pin-watchdog.ts +0 -98
- package/telegram-plugin/progress-card.ts +0 -1409
- package/telegram-plugin/tests/HARNESS.md +0 -340
- package/telegram-plugin/tests/_progress-card-harness.ts +0 -109
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +0 -211
- package/telegram-plugin/tests/active-pins-sweep.test.ts +0 -309
- package/telegram-plugin/tests/active-pins.test.ts +0 -187
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
- package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
- package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
- package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +0 -201
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
- package/telegram-plugin/tests/card-event-log.test.ts +0 -145
- package/telegram-plugin/tests/first-paint.test.ts +0 -257
- package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
- package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
- package/telegram-plugin/tests/foreman-state.test.ts +0 -164
- package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +0 -243
- package/telegram-plugin/tests/pin-event-log.test.ts +0 -124
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +0 -73
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +0 -272
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +0 -258
- package/telegram-plugin/tests/progress-card-delay-842.test.ts +0 -160
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +0 -81
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +0 -80
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +0 -215
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +0 -123
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +0 -76
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +0 -62
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +0 -84
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +0 -139
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +0 -773
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +0 -66
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +0 -64
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +0 -190
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +0 -146
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +0 -123
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +0 -82
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +0 -114
- package/telegram-plugin/tests/real-gateway-harness.ts +0 -699
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +0 -313
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +0 -299
- package/telegram-plugin/tests/real-gateway-spec.test.ts +0 -487
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +0 -101
- package/telegram-plugin/tests/setup-flow.test.ts +0 -510
- package/telegram-plugin/tests/setup-state.test.ts +0 -146
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +0 -116
- package/telegram-plugin/tests/turn-end-regressions.test.ts +0 -489
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +0 -218
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +0 -131
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +0 -120
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +0 -116
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +0 -87
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +0 -211
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +0 -62
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +0 -110
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +0 -128
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +0 -58
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +0 -133
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +0 -155
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +0 -117
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +0 -187
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +0 -149
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +0 -114
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +0 -105
- package/telegram-plugin/tests/waiting-ux-harness.ts +0 -381
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +0 -233
- package/telegram-plugin/turn-flush-prose-recovery.ts +0 -40
- package/telegram-plugin/two-zone-card.ts +0 -269
- package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +0 -61
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* P1 of #662 — `renderTwoZoneCard` pure renderer.
|
|
3
|
-
*
|
|
4
|
-
* Two-zone status card: PARENT bullets + FLEET rows. Replaces the v1
|
|
5
|
-
* expandable-blockquote-per-sub-agent layout (still live behind the
|
|
6
|
-
* default codepath; gated on `TWO_ZONE_CARD=1` for now).
|
|
7
|
-
*
|
|
8
|
-
* Pure: no IO, no globals, no clock except the caller-supplied `now`.
|
|
9
|
-
* All sanitisation happens upstream in `fleet-state.ts`; this module
|
|
10
|
-
* only formats already-sanitised values.
|
|
11
|
-
*
|
|
12
|
-
* Spec: `reference/status-card-design.md` (PR #661).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { FleetMember, FleetStatus } from './fleet-state.js'
|
|
16
|
-
import { cap } from './fleet-state.js'
|
|
17
|
-
import type { ProgressCardState, RenderOptions, TaskNum } from './progress-card.js'
|
|
18
|
-
import { escapeHtml, formatDuration } from './card-format.js'
|
|
19
|
-
import { mcpDisplayName, toolFallbackLabel } from './tool-labels.js'
|
|
20
|
-
|
|
21
|
-
const PARENT_BULLET_CAP = 8
|
|
22
|
-
const FLEET_ROW_CAP = 5
|
|
23
|
-
const STUCK_IDLE_MS = 60_000
|
|
24
|
-
|
|
25
|
-
export interface TwoZoneCardInput {
|
|
26
|
-
state: ProgressCardState
|
|
27
|
-
fleet: ReadonlyMap<string, FleetMember>
|
|
28
|
-
now: number
|
|
29
|
-
taskNum?: TaskNum
|
|
30
|
-
opts?: RenderOptions
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface PhaseResolution {
|
|
34
|
-
icon: string
|
|
35
|
-
label: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ─── Public entry point ─────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
export function renderTwoZoneCard(input: TwoZoneCardInput): string {
|
|
41
|
-
const { state, fleet, now, opts } = input
|
|
42
|
-
if (state.turnStartedAt === 0) {
|
|
43
|
-
return '⏳ Waiting…'
|
|
44
|
-
}
|
|
45
|
-
const phase = phaseFor(state, fleet, now, opts as Record<string, unknown> | undefined)
|
|
46
|
-
const elapsedMs = Math.max(0, now - state.turnStartedAt)
|
|
47
|
-
const totalTools = countTotalTools(state, fleet)
|
|
48
|
-
const subCount = fleet.size
|
|
49
|
-
const lines: string[] = []
|
|
50
|
-
lines.push(renderHeader(phase, elapsedMs, totalTools, subCount, input.taskNum))
|
|
51
|
-
const parentZone = renderParentZone(state)
|
|
52
|
-
if (parentZone) {
|
|
53
|
-
lines.push('')
|
|
54
|
-
lines.push(parentZone)
|
|
55
|
-
}
|
|
56
|
-
const fleetZone = renderFleetZone(fleet, now)
|
|
57
|
-
if (fleetZone) {
|
|
58
|
-
lines.push('')
|
|
59
|
-
lines.push(fleetZone)
|
|
60
|
-
}
|
|
61
|
-
return lines.join('\n')
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ─── Phase resolver ─────────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Maps (parent state, fleet, opts) to a single header phase. Truth
|
|
68
|
-
* table from `reference/status-card-design.md` §Header. Precedence:
|
|
69
|
-
* forced-close > silent-end (gated on parent terminal) > stalled >
|
|
70
|
-
* background > done > working.
|
|
71
|
-
*
|
|
72
|
-
* SilentEnd is lifted ABOVE the background/done checks so that a fleet
|
|
73
|
-
* still running can't suppress the silent-end label once the parent has
|
|
74
|
-
* terminated without a reply. It IS gated on `parentDone || stage===done`
|
|
75
|
-
* to prevent firing prematurely while the parent is still in flight.
|
|
76
|
-
*/
|
|
77
|
-
export function phaseFor(
|
|
78
|
-
state: ProgressCardState,
|
|
79
|
-
fleet: ReadonlyMap<string, FleetMember>,
|
|
80
|
-
now: number,
|
|
81
|
-
opts?: Record<string, unknown>,
|
|
82
|
-
): PhaseResolution {
|
|
83
|
-
const stalledClose = opts?.stalledClose === true
|
|
84
|
-
const silentEnd = opts?.silentEnd === true
|
|
85
|
-
const parentDone = opts?.parentDone === true || state.stage === 'done'
|
|
86
|
-
|
|
87
|
-
if (stalledClose) return { icon: '⚠', label: 'Forced close' }
|
|
88
|
-
|
|
89
|
-
const fleetRunning = anyFleetActive(fleet)
|
|
90
|
-
// Stalled = "the only fleet members that could still make progress have
|
|
91
|
-
// gone idle past the threshold". An empty filtered list (every fleet
|
|
92
|
-
// member is already terminal) MUST NOT count as stalled — Array#every
|
|
93
|
-
// returns true vacuously on `[]`, which previously caused the card to
|
|
94
|
-
// freeze at ⚠ Stalled the moment the last sub-agent finished while the
|
|
95
|
-
// parent was still running. We require at least one running-or-stuck
|
|
96
|
-
// member before claiming the fleet is stalled.
|
|
97
|
-
const runningOrStuck = [...fleet.values()].filter((m) => m.status === 'running' || m.status === 'stuck')
|
|
98
|
-
const fleetAllStuck = runningOrStuck.length > 0 && runningOrStuck.every((m) => isStuck(m, now))
|
|
99
|
-
|
|
100
|
-
// SilentEnd: parent terminated without a reply. Lifted above the
|
|
101
|
-
// background/done branches so a still-running fleet can't mask it,
|
|
102
|
-
// but gated on parentDone so we don't fire while parent is in flight.
|
|
103
|
-
if (silentEnd && parentDone) {
|
|
104
|
-
return { icon: '🙊', label: 'Ended without reply' }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Stalled: every running-or-stuck member is past the idle threshold.
|
|
108
|
-
// Members already terminal (done/failed) are excluded from this check —
|
|
109
|
-
// a fleet of [done, stuck] still surfaces as Stalled because the only
|
|
110
|
-
// member that could still make progress is no longer doing so. The
|
|
111
|
-
// `fleetAllStuck` calc itself guards against an empty filtered list
|
|
112
|
-
// (otherwise `[].every()` would lock the card at Stalled the moment
|
|
113
|
-
// the last sub-agent finished).
|
|
114
|
-
if (fleetAllStuck && !parentDone) {
|
|
115
|
-
return { icon: '⚠', label: 'Stalled' }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Background: parentDone but at least one fleet member still running
|
|
119
|
-
if (parentDone && fleetRunning) {
|
|
120
|
-
return { icon: '⏸', label: 'Background' }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Done: parent terminal + no fleet still running
|
|
124
|
-
if (parentDone && !fleetRunning) {
|
|
125
|
-
return { icon: '✅', label: 'Done' }
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Default: active work
|
|
129
|
-
return { icon: '⚙️', label: 'Working…' }
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function anyFleetActive(fleet: ReadonlyMap<string, FleetMember>): boolean {
|
|
133
|
-
for (const m of fleet.values()) {
|
|
134
|
-
if (m.status === 'running' || m.status === 'background' || m.status === 'stuck') return true
|
|
135
|
-
}
|
|
136
|
-
return false
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function isStuck(m: FleetMember, now: number): boolean {
|
|
140
|
-
if (m.status === 'stuck') return true
|
|
141
|
-
return m.status === 'running' && now - m.lastActivityAt > STUCK_IDLE_MS
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ─── Header ─────────────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
function renderHeader(
|
|
147
|
-
phase: PhaseResolution,
|
|
148
|
-
elapsedMs: number,
|
|
149
|
-
totalTools: number,
|
|
150
|
-
subCount: number,
|
|
151
|
-
taskNum?: TaskNum,
|
|
152
|
-
): string {
|
|
153
|
-
const tools = totalTools >= 100 ? '99+' : String(totalTools)
|
|
154
|
-
const elapsed = formatDuration(elapsedMs)
|
|
155
|
-
const parts = [`${phase.icon} <b>${escapeHtml(phase.label)}</b>`, `⏱ ${elapsed}`, `🔧 ${tools}`]
|
|
156
|
-
if (subCount > 0) parts.push(`🤖 ${subCount}`)
|
|
157
|
-
if (taskNum && taskNum.total > 1) parts.push(`#${taskNum.index}/${taskNum.total}`)
|
|
158
|
-
return parts.join(' · ')
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ─── Parent zone ────────────────────────────────────────────────────────────
|
|
162
|
-
|
|
163
|
-
function renderParentZone(state: ProgressCardState): string {
|
|
164
|
-
const items = state.items
|
|
165
|
-
if (items.length === 0) return ''
|
|
166
|
-
const lines: string[] = ['<b>PARENT</b>']
|
|
167
|
-
const visible = items.slice(-PARENT_BULLET_CAP)
|
|
168
|
-
const earlier = items.length - visible.length
|
|
169
|
-
if (earlier > 0) lines.push(`(+${earlier} earlier)`)
|
|
170
|
-
const inFlight = state.stage !== 'done'
|
|
171
|
-
const lastIdx = visible.length - 1
|
|
172
|
-
for (let i = 0; i < visible.length; i++) {
|
|
173
|
-
const it = visible[i]
|
|
174
|
-
let text: string
|
|
175
|
-
if (it.label) {
|
|
176
|
-
text = escapeHtml(truncate(it.label, 80))
|
|
177
|
-
} else {
|
|
178
|
-
const tool = it.tool || ''
|
|
179
|
-
const fallback = tool.startsWith('mcp__')
|
|
180
|
-
? mcpDisplayName(tool) || toolFallbackLabel(tool)
|
|
181
|
-
: toolFallbackLabel(tool)
|
|
182
|
-
text = escapeHtml(fallback)
|
|
183
|
-
}
|
|
184
|
-
if (inFlight && i === lastIdx) {
|
|
185
|
-
lines.push(`◉ ${text}`)
|
|
186
|
-
} else {
|
|
187
|
-
lines.push(`● ${text}`)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return lines.join('\n')
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ─── Fleet zone ─────────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
export function renderFleetZone(fleet: ReadonlyMap<string, FleetMember>, now: number): string {
|
|
196
|
-
if (fleet.size === 0) return ''
|
|
197
|
-
const all = [...fleet.values()]
|
|
198
|
-
const { visible, hidden } = cap(all, FLEET_ROW_CAP)
|
|
199
|
-
const lines: string[] = [`<b>FLEET (${fleet.size})</b>`]
|
|
200
|
-
for (const m of visible) lines.push(renderFleetRow(m, now))
|
|
201
|
-
if (hidden > 0) lines.push(`+ ${hidden} more`)
|
|
202
|
-
return lines.join('\n')
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function renderFleetRow(m: FleetMember, now: number): string {
|
|
206
|
-
const glyph = glyphForFleetStatus(m.status)
|
|
207
|
-
const role = escapeHtml(truncate(m.role || 'agent', 30))
|
|
208
|
-
const id6 = escapeHtml(m.agentId.slice(0, 6))
|
|
209
|
-
const tools = `${m.toolCount}t`
|
|
210
|
-
const activity = formatLastActivity(m, now)
|
|
211
|
-
return `${glyph} ${role} <code>${id6}</code> · ${tools} · ${activity}`
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function formatLastActivity(m: FleetMember, now: number): string {
|
|
215
|
-
// Terminal states show "<status> <relative-time>"
|
|
216
|
-
if (m.terminalAt != null) {
|
|
217
|
-
const age = formatRelativeTime(Math.max(0, now - m.terminalAt))
|
|
218
|
-
return `${escapeHtml(m.status)} ${age}`
|
|
219
|
-
}
|
|
220
|
-
// Stuck — show idle duration
|
|
221
|
-
if (m.status === 'stuck') {
|
|
222
|
-
const idle = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
|
|
223
|
-
return `idle ${idle}`
|
|
224
|
-
}
|
|
225
|
-
// Running — show last tool + age
|
|
226
|
-
if (m.lastTool == null) {
|
|
227
|
-
const age = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
|
|
228
|
-
return `started ${age}`
|
|
229
|
-
}
|
|
230
|
-
const age = formatRelativeTime(Math.max(0, now - m.lastActivityAt))
|
|
231
|
-
const arg = m.lastTool.sanitisedArg
|
|
232
|
-
? ` <code>${escapeHtml(truncate(m.lastTool.sanitisedArg, 60))}</code>`
|
|
233
|
-
: ''
|
|
234
|
-
return `${escapeHtml(m.lastTool.name)}${arg} (${age})`
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function glyphForFleetStatus(status: FleetStatus): string {
|
|
238
|
-
switch (status) {
|
|
239
|
-
case 'running': return '↻'
|
|
240
|
-
case 'background': return '⏸'
|
|
241
|
-
case 'done': return '✓'
|
|
242
|
-
case 'failed': return '✗'
|
|
243
|
-
case 'stuck': return '⚠'
|
|
244
|
-
case 'killed': return '✗'
|
|
245
|
-
case 'capped': return '⚠'
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
250
|
-
|
|
251
|
-
export function formatRelativeTime(ms: number): string {
|
|
252
|
-
const s = Math.floor(ms / 1000)
|
|
253
|
-
if (s < 60) return `${s}s ago`
|
|
254
|
-
const m = Math.floor(s / 60)
|
|
255
|
-
const r = s % 60
|
|
256
|
-
if (r === 0) return `${m}m ago`
|
|
257
|
-
return `${m}m${r}s ago`
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function truncate(s: string, n: number): string {
|
|
261
|
-
if (s.length <= n) return s
|
|
262
|
-
return s.slice(0, n - 1) + '…'
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function countTotalTools(state: ProgressCardState, fleet: ReadonlyMap<string, FleetMember>): number {
|
|
266
|
-
let n = state.items.length
|
|
267
|
-
for (const m of fleet.values()) n += m.toolCount
|
|
268
|
-
return n
|
|
269
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smoke scenario — clerk replies to a simple text message.
|
|
3
|
-
*
|
|
4
|
-
* Part of: https://github.com/switchroom/switchroom/issues/866
|
|
5
|
-
*
|
|
6
|
-
* Phase 1 status: this file exercises the harness shape (typecheck-
|
|
7
|
-
* clean, reads as the canonical example) but will FAIL at runtime
|
|
8
|
-
* because `harness.spinUp` is stubbed. Phase 2 wires the real
|
|
9
|
-
* lifecycle and this becomes the first green UAT scenario.
|
|
10
|
-
*
|
|
11
|
-
* The shape mirrors the canonical scenario in epic #863 so reviewers
|
|
12
|
-
* can map directly between the design doc and the code.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, it, expect } from "vitest";
|
|
16
|
-
import { spinUp } from "../harness.js";
|
|
17
|
-
|
|
18
|
-
describe("uat: clerk smoke", () => {
|
|
19
|
-
it("replies to a text message with the right reaction sequence + HTML reply", async () => {
|
|
20
|
-
const sc = await spinUp({ agent: "clerk", topic: "smoke-clerk-reply" });
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const sent = await sc.driver.sendText(
|
|
24
|
-
sc.chatId,
|
|
25
|
-
"summarize this short note",
|
|
26
|
-
{ messageThreadId: sc.threadId },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// Status reactions should walk 👀 → 🤔 → 🔥 → 👍 on the inbound
|
|
30
|
-
// message within 30s on a healthy run.
|
|
31
|
-
await sc.expectReaction(
|
|
32
|
-
sent.messageId,
|
|
33
|
-
["👀", "🤔", "🔥", "👍"],
|
|
34
|
-
{ timeout: 30_000 },
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// The progress card should be pinned within a few seconds.
|
|
38
|
-
const card = await sc.expectPinnedCard({ timeout: 5_000 });
|
|
39
|
-
|
|
40
|
-
// And ride to `done` within the model's normal turn budget.
|
|
41
|
-
const finalCard = await sc.waitForCardPhase(card, "done", {
|
|
42
|
-
timeout: 60_000,
|
|
43
|
-
});
|
|
44
|
-
expect(finalCard.text).toMatch(/Done|✅/);
|
|
45
|
-
|
|
46
|
-
// The actual reply prose lands as a fresh bot message, in HTML.
|
|
47
|
-
const reply = await sc.expectMessage(/./, {
|
|
48
|
-
from: "bot",
|
|
49
|
-
timeout: 60_000,
|
|
50
|
-
});
|
|
51
|
-
// Reviewer note: parse_mode isn't on the wire-level update;
|
|
52
|
-
// we proxy via "did the rendered HTML contain a `<` we
|
|
53
|
-
// recognize, or did markdown leak as plain `*`?". Phase 2
|
|
54
|
-
// will pick the right shape — left as a TODO so this file
|
|
55
|
-
// typechecks today.
|
|
56
|
-
expect(reply.text.length).toBeGreaterThan(0);
|
|
57
|
-
} finally {
|
|
58
|
-
await sc.tearDown();
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
});
|