@cluesmith/codev 3.2.3 → 3.3.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/dashboard-dist/assets/index-BZRTEMtH.css +32 -0
- package/dashboard-dist/assets/{index-BZ1x7x1X.js → index-JCbkpAcV.js} +33 -33
- package/dashboard-dist/assets/index-JCbkpAcV.js.map +1 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +173 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/attach.js +2 -2
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +16 -0
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/inbox.d.ts +42 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -0
- package/dist/agent-farm/commands/inbox.js +129 -0
- package/dist/agent-farm/commands/inbox.js.map +1 -0
- package/dist/agent-farm/commands/interrupt.d.ts +20 -0
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -0
- package/dist/agent-farm/commands/interrupt.js +65 -0
- package/dist/agent-farm/commands/interrupt.js.map +1 -0
- package/dist/agent-farm/commands/reset/constants.d.ts +52 -0
- package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/constants.js +52 -0
- package/dist/agent-farm/commands/reset/constants.js.map +1 -0
- package/dist/agent-farm/commands/reset/context.d.ts +230 -0
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/context.js +428 -0
- package/dist/agent-farm/commands/reset/context.js.map +1 -0
- package/dist/agent-farm/commands/reset/index.d.ts +192 -0
- package/dist/agent-farm/commands/reset/index.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/index.js +488 -0
- package/dist/agent-farm/commands/reset/index.js.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts +97 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.js +149 -0
- package/dist/agent-farm/commands/reset/receipt.js.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts +117 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.js +313 -0
- package/dist/agent-farm/commands/reset/reorient.js.map +1 -0
- package/dist/agent-farm/commands/reset.d.ts +19 -0
- package/dist/agent-farm/commands/reset.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset.js +292 -0
- package/dist/agent-farm/commands/reset.js.map +1 -0
- package/dist/agent-farm/commands/send.d.ts +2 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +52 -11
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/setup.d.ts +4 -1
- package/dist/agent-farm/commands/setup.d.ts.map +1 -1
- package/dist/agent-farm/commands/setup.js +8 -4
- package/dist/agent-farm/commands/setup.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +116 -6
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +345 -74
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +105 -9
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts +6 -0
- package/dist/agent-farm/commands/tower-sweep-husks.d.ts.map +1 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js +77 -0
- package/dist/agent-farm/commands/tower-sweep-husks.js.map +1 -0
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +34 -0
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/commands/whoami.d.ts +65 -0
- package/dist/agent-farm/commands/whoami.d.ts.map +1 -0
- package/dist/agent-farm/commands/whoami.js +196 -0
- package/dist/agent-farm/commands/whoami.js.map +1 -0
- package/dist/agent-farm/db/index.d.ts +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +80 -1
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +222 -0
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -0
- package/dist/agent-farm/db/mailbox.js +304 -0
- package/dist/agent-farm/db/mailbox.js.map +1 -0
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +35 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +45 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.d.ts +1 -1
- package/dist/agent-farm/lib/builder-lookup.js +2 -2
- package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
- package/dist/agent-farm/lib/reconnect-backoff.d.ts +54 -0
- package/dist/agent-farm/lib/reconnect-backoff.d.ts.map +1 -0
- package/dist/agent-farm/lib/reconnect-backoff.js +47 -0
- package/dist/agent-farm/lib/reconnect-backoff.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +16 -6
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +37 -6
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts +69 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +218 -27
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/architect-session-holder.d.ts +133 -0
- package/dist/agent-farm/servers/architect-session-holder.d.ts.map +1 -0
- package/dist/agent-farm/servers/architect-session-holder.js +268 -0
- package/dist/agent-farm/servers/architect-session-holder.js.map +1 -0
- package/dist/agent-farm/servers/cron-delivery.d.ts +72 -0
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/cron-delivery.js +80 -0
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -0
- package/dist/agent-farm/servers/delayed-send.d.ts +85 -0
- package/dist/agent-farm/servers/delayed-send.d.ts.map +1 -0
- package/dist/agent-farm/servers/delayed-send.js +165 -0
- package/dist/agent-farm/servers/delayed-send.js.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts +67 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.js +113 -0
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +370 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.js +617 -0
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +125 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.js +425 -0
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -0
- package/dist/agent-farm/servers/message-write.d.ts +53 -1
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +63 -0
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +23 -4
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/process-census.d.ts +41 -0
- package/dist/agent-farm/servers/process-census.d.ts.map +1 -0
- package/dist/agent-farm/servers/process-census.js +62 -0
- package/dist/agent-farm/servers/process-census.js.map +1 -0
- package/dist/agent-farm/servers/render-gate.d.ts +141 -0
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -0
- package/dist/agent-farm/servers/render-gate.js +251 -0
- package/dist/agent-farm/servers/render-gate.js.map +1 -0
- package/dist/agent-farm/servers/session-log-sweep.d.ts +93 -0
- package/dist/agent-farm/servers/session-log-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-log-sweep.js +165 -0
- package/dist/agent-farm/servers/session-log-sweep.js.map +1 -0
- package/dist/agent-farm/servers/session-submit.d.ts +90 -0
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-submit.js +127 -0
- package/dist/agent-farm/servers/session-submit.js.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts +87 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.d.ts.map +1 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js +129 -0
- package/dist/agent-farm/servers/shellper-husk-sweep.js.map +1 -0
- package/dist/agent-farm/servers/tower-cron.d.ts +10 -7
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +58 -53
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +14 -2
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +128 -19
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-messages.d.ts +38 -1
- package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-messages.js +106 -1
- package/dist/agent-farm/servers/tower-messages.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +16 -4
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +807 -99
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +241 -15
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +17 -2
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +139 -124
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +63 -4
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +2 -1
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +99 -1
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +242 -6
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +6 -21
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/servers/write-queue.d.ts +35 -0
- package/dist/agent-farm/servers/write-queue.d.ts.map +1 -0
- package/dist/agent-farm/servers/write-queue.js +56 -0
- package/dist/agent-farm/servers/write-queue.js.map +1 -0
- package/dist/agent-farm/state.d.ts +6 -1
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +5 -2
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +39 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.d.ts +2 -2
- package/dist/agent-farm/utils/agent-names.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.js +3 -3
- package/dist/agent-farm/utils/agent-names.js.map +1 -1
- package/dist/agent-farm/utils/architect-name.d.ts +2 -2
- package/dist/agent-farm/utils/architect-name.d.ts.map +1 -1
- package/dist/agent-farm/utils/architect-name.js +3 -3
- package/dist/agent-farm/utils/architect-name.js.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +13 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +33 -1
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/harness.d.ts +103 -7
- package/dist/agent-farm/utils/harness.d.ts.map +1 -1
- package/dist/agent-farm/utils/harness.js +126 -19
- package/dist/agent-farm/utils/harness.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -32
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -2
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +7 -6
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/agy-auth-cache.d.ts +97 -0
- package/dist/commands/consult/agy-auth-cache.d.ts.map +1 -0
- package/dist/commands/consult/agy-auth-cache.js +320 -0
- package/dist/commands/consult/agy-auth-cache.js.map +1 -0
- package/dist/commands/consult/cli-options.d.ts +34 -0
- package/dist/commands/consult/cli-options.d.ts.map +1 -0
- package/dist/commands/consult/cli-options.js +67 -0
- package/dist/commands/consult/cli-options.js.map +1 -0
- package/dist/commands/consult/index.d.ts +56 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +330 -27
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/metrics.d.ts +42 -0
- package/dist/commands/consult/metrics.d.ts.map +1 -1
- package/dist/commands/consult/metrics.js +129 -9
- package/dist/commands/consult/metrics.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +258 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +3 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +23 -0
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +60 -0
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/config.d.ts +20 -0
- package/dist/commands/porch/config.d.ts.map +1 -1
- package/dist/commands/porch/config.js +20 -0
- package/dist/commands/porch/config.js.map +1 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +32 -36
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +2 -41
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/update.d.ts +4 -4
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/config.d.ts +70 -3
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +67 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/consult-lanes.d.ts +149 -0
- package/dist/lib/consult-lanes.d.ts.map +1 -0
- package/dist/lib/consult-lanes.js +360 -0
- package/dist/lib/consult-lanes.js.map +1 -0
- package/dist/lib/default-branch.d.ts +1 -1
- package/dist/lib/default-branch.js +1 -1
- package/dist/lib/github.js +1 -1
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/gitignore.d.ts +25 -2
- package/dist/lib/gitignore.d.ts.map +1 -1
- package/dist/lib/gitignore.js +70 -0
- package/dist/lib/gitignore.js.map +1 -1
- package/dist/lib/migration-backup-audit.d.ts +64 -0
- package/dist/lib/migration-backup-audit.d.ts.map +1 -0
- package/dist/lib/migration-backup-audit.js +145 -0
- package/dist/lib/migration-backup-audit.js.map +1 -0
- package/dist/lib/prompt-behavior-metrics.d.ts +67 -0
- package/dist/lib/prompt-behavior-metrics.d.ts.map +1 -0
- package/dist/lib/prompt-behavior-metrics.js +290 -0
- package/dist/lib/prompt-behavior-metrics.js.map +1 -0
- package/dist/lib/protocol-drift-audit.d.ts +118 -0
- package/dist/lib/protocol-drift-audit.d.ts.map +1 -0
- package/dist/lib/protocol-drift-audit.js +217 -0
- package/dist/lib/protocol-drift-audit.js.map +1 -0
- package/dist/lib/scaffold.d.ts +10 -3
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +29 -26
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/lib/skeleton.d.ts +22 -1
- package/dist/lib/skeleton.d.ts.map +1 -1
- package/dist/lib/skeleton.js +120 -0
- package/dist/lib/skeleton.js.map +1 -1
- package/dist/lib/test-env.d.ts +74 -0
- package/dist/lib/test-env.d.ts.map +1 -0
- package/dist/lib/test-env.js +92 -0
- package/dist/lib/test-env.js.map +1 -0
- package/dist/terminal/pty-manager.d.ts +9 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +32 -6
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +156 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +307 -42
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/ring-buffer.d.ts +39 -6
- package/dist/terminal/ring-buffer.d.ts.map +1 -1
- package/dist/terminal/ring-buffer.js +74 -6
- package/dist/terminal/ring-buffer.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +129 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +337 -44
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +91 -0
- package/dist/terminal/session-screen.d.ts.map +1 -0
- package/dist/terminal/session-screen.js +135 -0
- package/dist/terminal/session-screen.js.map +1 -0
- package/dist/terminal/shellper-client.d.ts +19 -4
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +70 -8
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-main.js +2 -1
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +1 -1
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +33 -9
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +40 -2
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js +72 -5
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/dist/terminal/shellper-replay-buffer.d.ts +69 -7
- package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -1
- package/dist/terminal/shellper-replay-buffer.js +167 -31
- package/dist/terminal/shellper-replay-buffer.js.map +1 -1
- package/package.json +11 -9
- package/skeleton/.claude/skills/afx/SKILL.md +27 -1
- package/skeleton/.claude/skills/arch-init/SKILL.md +135 -0
- package/skeleton/.claude/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.claude/skills/codev/SKILL.md +33 -2
- package/skeleton/.claude/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/.codex/skills/afx/SKILL.md +170 -0
- package/skeleton/.codex/skills/arch-init/SKILL.md +135 -0
- package/skeleton/.codex/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.codex/skills/codev/SKILL.md +90 -0
- package/skeleton/.codex/skills/consult/SKILL.md +96 -0
- package/skeleton/.codex/skills/generate-image/SKILL.md +45 -0
- package/skeleton/.codex/skills/porch/SKILL.md +60 -0
- package/skeleton/.codex/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/.codex/skills/update-arch-docs/SKILL.md +132 -0
- package/skeleton/protocols/air/builder-prompt.md +28 -47
- package/skeleton/protocols/air/consult-types/impl-review.md +17 -34
- package/skeleton/protocols/air/consult-types/pr-review.md +14 -37
- package/skeleton/protocols/air/prompts/implement.md +20 -73
- package/skeleton/protocols/air/prompts/pr.md +22 -54
- package/skeleton/protocols/air/protocol.md +40 -71
- package/skeleton/protocols/aspir/builder-prompt.md +29 -66
- package/skeleton/protocols/aspir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/aspir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/aspir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/aspir/prompts/implement.md +27 -196
- package/skeleton/protocols/aspir/prompts/plan.md +21 -85
- package/skeleton/protocols/aspir/prompts/review.md +29 -228
- package/skeleton/protocols/aspir/prompts/specify.md +25 -111
- package/skeleton/protocols/aspir/protocol.json +10 -0
- package/skeleton/protocols/aspir/protocol.md +36 -82
- package/skeleton/protocols/bugfix/builder-prompt.md +23 -45
- package/skeleton/protocols/bugfix/consult-types/impl-review.md +21 -45
- package/skeleton/protocols/bugfix/consult-types/pr-review.md +18 -52
- package/skeleton/protocols/bugfix/prompts/fix.md +16 -61
- package/skeleton/protocols/bugfix/prompts/investigate.md +18 -62
- package/skeleton/protocols/bugfix/prompts/pr.md +22 -57
- package/skeleton/protocols/bugfix/protocol.md +44 -50
- package/skeleton/protocols/experiment/builder-prompt.md +25 -58
- package/skeleton/protocols/experiment/protocol.md +22 -188
- package/skeleton/protocols/experiment/templates/notes.md +11 -71
- package/skeleton/protocols/maintain/builder-prompt.md +24 -45
- package/skeleton/protocols/maintain/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/maintain/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/maintain/prompts/maintain.md +1 -1
- package/skeleton/protocols/maintain/protocol.md +32 -266
- package/skeleton/protocols/maintain/templates/maintenance-run.md +11 -0
- package/skeleton/protocols/pir/builder-prompt.md +30 -64
- package/skeleton/protocols/pir/consult-types/impl-review.md +17 -50
- package/skeleton/protocols/pir/consult-types/pr-review.md +11 -40
- package/skeleton/protocols/pir/prompts/implement.md +1 -2
- package/skeleton/protocols/pir/prompts/plan.md +0 -3
- package/skeleton/protocols/pir/prompts/review.md +1 -1
- package/skeleton/protocols/pir/protocol.md +54 -177
- package/skeleton/protocols/research/builder-prompt.md +27 -58
- package/skeleton/protocols/research/protocol.md +28 -153
- package/skeleton/protocols/spike/builder-prompt.md +30 -48
- package/skeleton/protocols/spike/protocol.md +24 -107
- package/skeleton/protocols/spike/templates/findings.md +9 -39
- package/skeleton/protocols/spir/builder-prompt.md +33 -62
- package/skeleton/protocols/spir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/spir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/spir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/spir/prompts/implement.md +26 -195
- package/skeleton/protocols/spir/prompts/plan.md +20 -84
- package/skeleton/protocols/spir/prompts/review.md +28 -227
- package/skeleton/protocols/spir/prompts/specify.md +24 -110
- package/skeleton/protocols/spir/protocol.json +10 -0
- package/skeleton/protocols/spir/protocol.md +84 -633
- package/skeleton/protocols/spir/templates/plan.md +28 -147
- package/skeleton/protocols/spir/templates/review.md +21 -80
- package/skeleton/protocols/spir/templates/spec.md +24 -118
- package/skeleton/resources/commands/agent-farm.md +348 -0
- package/skeleton/resources/commands/codev.md +5 -0
- package/skeleton/resources/commands/consult.md +262 -8
- package/skeleton/resources/commands/overview.md +1 -0
- package/skeleton/roles/architect.md +67 -306
- package/skeleton/roles/builder.md +77 -191
- package/skeleton/templates/AGENTS.md +2 -0
- package/skeleton/templates/CLAUDE.md +2 -0
- package/templates/tower.html +39 -2
- package/dashboard-dist/assets/index-BZ1x7x1X.js.map +0 -1
- package/dashboard-dist/assets/index-DB1KBSNE.css +0 -32
- package/dist/agent-farm/servers/send-buffer.d.ts +0 -59
- package/dist/agent-farm/servers/send-buffer.d.ts.map +0 -1
- package/dist/agent-farm/servers/send-buffer.js +0 -103
- package/dist/agent-farm/servers/send-buffer.js.map +0 -1
- package/skeleton/porch/prompts/defend.md +0 -108
- package/skeleton/porch/prompts/diagnose.md +0 -70
- package/skeleton/porch/prompts/evaluate.md +0 -132
- package/skeleton/porch/prompts/fix.md +0 -59
- package/skeleton/porch/prompts/implement.md +0 -88
- package/skeleton/porch/prompts/plan.md +0 -74
- package/skeleton/porch/prompts/pr.md +0 -84
- package/skeleton/porch/prompts/review.md +0 -249
- package/skeleton/porch/prompts/specify.md +0 -53
- package/skeleton/porch/prompts/test.md +0 -63
- package/skeleton/protocols/aspir/templates/plan.md +0 -184
- package/skeleton/protocols/aspir/templates/review.md +0 -120
- package/skeleton/protocols/aspir/templates/spec.md +0 -154
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-empty gate (Spec 1313, Phase 2) — the sole authority that answers
|
|
3
|
+
* "is this screen a clean, empty prompt?".
|
|
4
|
+
*
|
|
5
|
+
* A message body is only ever written to a prompt this gate proves empty, so
|
|
6
|
+
* corruption is eliminated by construction: a message can never fuse with a
|
|
7
|
+
* draft because it is never delivered while one exists. The classifier is a direct
|
|
8
|
+
* port of the G-lite classifier validated against the real claude/codex TUIs in
|
|
9
|
+
* spike 1265 (`codev/spikes/1265-poc/exp-g2-glite-prod-path.mjs`).
|
|
10
|
+
*
|
|
11
|
+
* WHAT it reads (Spec 1313 render-gate round 2 — capped-ring reconciliation): the gate
|
|
12
|
+
* classifies a rendered terminal SCREEN via the sync core {@link classifyBuffer}. In
|
|
13
|
+
* PRODUCTION that screen is the session's persistent bounded mirror (`SessionScreen`,
|
|
14
|
+
* terminal layer): one long-lived `@xterm/headless` Terminal fed the session's output
|
|
15
|
+
* incrementally — from birth on the live path — whose current viewport IS the live screen. The gate originally
|
|
16
|
+
* REBUILT the screen every check by replaying the whole output ring
|
|
17
|
+
* (`ringBuffer.getAll().join('\n')`) through a throwaway Terminal — but #1205 capped the ring's
|
|
18
|
+
* newline-free `partial` at 2 MiB (`trimPartial` halves to ~1 MiB), and a claude/codex
|
|
19
|
+
* alt-screen frame (`\x1b[?1049h`) is exactly one giant newline-free partial. So once a busy
|
|
20
|
+
* long-lived agent's frame crossed the cap, the gate was handed a TORN front — dropping the
|
|
21
|
+
* composer marker (→ false `no-composer-marker`) or the composer's lower rule (→ the region
|
|
22
|
+
* spills into status chrome → false `user-text`) — and held its mail PERMANENTLY. That is the
|
|
23
|
+
* over-ceiling delivery outage the whole-ring render was meant to eliminate, resurrected one
|
|
24
|
+
* layer down for exactly the busiest agents. A bounded terminal mirror needs only the live byte
|
|
25
|
+
* stream, not the whole ring, so the cap is irrelevant, the live-ring tear is gone, each classify is
|
|
26
|
+
* O(viewport) rather than O(ring size), and the whole-render era's unbounded-`partial` OOM risk
|
|
27
|
+
* (#1047: one classify allocating a multi-hundred-MB string) is closed. The whole-ring
|
|
28
|
+
* {@link classifyScreen} entry survives for the fixture suite and any transient one-shot
|
|
29
|
+
* classify; it shares the SAME classifier core, so the two paths can never diverge.
|
|
30
|
+
*
|
|
31
|
+
* Caveat — adopt/reconnect seed: after a Tower restart the mirror is seeded from a bounded replay
|
|
32
|
+
* tail (`capRingSeed`, 1 MiB), not the live-from-birth stream, so a long-lived alt-screen frame can
|
|
33
|
+
* be born torn on adopt. That classifies not-clean → the gate HOLDS (fail-safe, never a misdelivery)
|
|
34
|
+
* and self-heals on the next repaint/viewer nudge. Pre-existing (the pre-round-2 whole-ring gate saw
|
|
35
|
+
* the same capped seed), not a round-2 regression; tracked as #1361.
|
|
36
|
+
*
|
|
37
|
+
* Classifier (fail-toward-not-clean): CLEAN requires
|
|
38
|
+
* (a) a recognized composer marker on the screen, AND
|
|
39
|
+
* (b) a positively-bounded composer region (a rule/status line BELOW the
|
|
40
|
+
* marker — never a scan to the screen bottom), AND
|
|
41
|
+
* (c) zero normal-intensity (non-dim), non-whitespace, non-chrome cells in that
|
|
42
|
+
* region — with one measured exemption: claude's suggested-command *ghost* cursor
|
|
43
|
+
* cell (an inverse, non-dim char at the cursor followed by a non-empty dim run), which
|
|
44
|
+
* is composer chrome, not typed text (see `isGhostCursorCell`).
|
|
45
|
+
* The placeholder-vs-user-text distinction is an SGR attribute — both TUIs
|
|
46
|
+
* render rotating placeholder/hint text DIM while typed text is normal-intensity
|
|
47
|
+
* (measured, spike g2) — so no placeholder allowlist is needed. Anything
|
|
48
|
+
* unrecognized (no marker, no region boundary, a menu, a picker, a draft, a
|
|
49
|
+
* wrapper/boot screen, or a mirror that has not yet repainted a coherent frame) → NOT clean →
|
|
50
|
+
* the message stays held. There is no force path.
|
|
51
|
+
*
|
|
52
|
+
* Cost (spike g2, @xterm/headless 6.0.0): classifying a rendered viewport is sub-millisecond
|
|
53
|
+
* (bounded rows × cols, independent of history); the mirror pays a normal terminal-emulator
|
|
54
|
+
* parse per output chunk — the cost any emulator pays for the byte stream — amortised across
|
|
55
|
+
* the session instead of spent in a per-check whole-history burst.
|
|
56
|
+
*/
|
|
57
|
+
// `@xterm/headless` resolves to its CommonJS entry (no `exports` map, no
|
|
58
|
+
// `type: module`), and its named exports are not statically analyzable, so a
|
|
59
|
+
// native-node ESM `import { Terminal }` throws "Named export 'Terminal' not
|
|
60
|
+
// found" when the compiled dist runs under node (production; masked under vitest
|
|
61
|
+
// by vite's CJS interop). Default-import the module object — the codebase's
|
|
62
|
+
// convention for CJS deps (cf. `import Database from 'better-sqlite3'`).
|
|
63
|
+
import xtermHeadless from '@xterm/headless';
|
|
64
|
+
const { Terminal } = xtermHeadless;
|
|
65
|
+
/**
|
|
66
|
+
* Box-drawing / prompt chrome that is never "user text". The composer marker
|
|
67
|
+
* glyphs (❯ ›) live here too; the marker cell is additionally skipped by
|
|
68
|
+
* position so a profile whose marker is not listed still never self-trips.
|
|
69
|
+
*/
|
|
70
|
+
const IGNORE_CHARS = new Set(['❯', '›', '│', '▌', '─', '━', '╌', '┄', '╭', '╰', '┌', '└', '']);
|
|
71
|
+
/** All-whitespace (incl. NBSP and other Unicode spaces) → ignorable. */
|
|
72
|
+
const WHITESPACE = /^\s+$/u;
|
|
73
|
+
/** Rendered viewport lines, right-trimmed — the same extraction the spike asserts on. */
|
|
74
|
+
function screenLines(term, rows) {
|
|
75
|
+
const buf = term.buffer.active;
|
|
76
|
+
const top = buf.viewportY;
|
|
77
|
+
const lines = [];
|
|
78
|
+
for (let i = 0; i < rows; i++) {
|
|
79
|
+
const line = buf.getLine(top + i);
|
|
80
|
+
lines.push(line ? line.translateToString(true).trimEnd() : '');
|
|
81
|
+
}
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
/** Last row index whose text starts with the profile's composer marker, or -1. */
|
|
85
|
+
function findMarkerRow(lines, markerPattern) {
|
|
86
|
+
let markerRow = -1;
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
if (markerPattern.test(lines[i]))
|
|
89
|
+
markerRow = i;
|
|
90
|
+
}
|
|
91
|
+
return markerRow;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* First region-ending row after the marker (the rule/status line beneath the
|
|
95
|
+
* composer), or -1 when none is found. -1 means the composer has no proven lower
|
|
96
|
+
* bound (a partial/mid-repaint frame, or a torn replay) — the caller MUST hold, not
|
|
97
|
+
* scan to the screen bottom: scanning further counts status chrome below the
|
|
98
|
+
* composer as user text (the old bug) OR, if that chrome renders empty/dim, returns
|
|
99
|
+
* a false CLEAN. A missing boundary is indeterminate, and indeterminate is not-clean.
|
|
100
|
+
*/
|
|
101
|
+
function findRegionEnd(lines, markerRow, endPatterns) {
|
|
102
|
+
for (let i = markerRow + 1; i < lines.length; i++) {
|
|
103
|
+
if (endPatterns.some((p) => p.test(lines[i])))
|
|
104
|
+
return i;
|
|
105
|
+
}
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ghost-suggestion cursor cell (Spec 1313 render-gate hardening — false-`busy` on an idle
|
|
110
|
+
* claude composer). When claude's own last reply mentioned a runnable command it paints
|
|
111
|
+
* that command into the otherwise-empty composer as a *suggested-command ghost*, and the
|
|
112
|
+
* ghost's first character doubles as the software block cursor: it is rendered SGR-7
|
|
113
|
+
* INVERSE at normal intensity while the rest of the ghost is SGR-2 dim
|
|
114
|
+
* (`❯ ␛[7m a ␛[27m␛[2mfx cleanup …␛[22m`, measured live — captured as
|
|
115
|
+
* `claude-ghost-suggestion-empty.replay.bin`). The universal dim rule already skips the
|
|
116
|
+
* ghost body, but the lone inverse cursor cell was counted as user text → the composer
|
|
117
|
+
* classified `user-text`/`busy` FOREVER while genuinely empty, so mail to an idle
|
|
118
|
+
* (unattended) agent was never delivered (fail-safe becomes fail-forever for an idle
|
|
119
|
+
* recipient — the exact agent `afx send` exists to wake).
|
|
120
|
+
*
|
|
121
|
+
* This exempts exactly that cell: the cell at the headless buffer's cursor position,
|
|
122
|
+
* rendered inverse at normal intensity, whose following run on the same row is dim or
|
|
123
|
+
* empty (the measured ghost tail). It is deliberately NARROW — NOT the blanket inverse
|
|
124
|
+
* skip the finding warns against — and does not false-clean a real draft:
|
|
125
|
+
* - measured, claude renders the block cursor inverse only on the trailing WHITESPACE
|
|
126
|
+
* past a real draft (already skipped as whitespace) and never inverse-renders typed
|
|
127
|
+
* characters, so a real draft's typed cells are non-inverse and still counted;
|
|
128
|
+
* - an inverse *selection* over real multi-char text fails the dim-tail test (its
|
|
129
|
+
* following cells are non-dim) and, even if it passed, only this one cell is skipped
|
|
130
|
+
* while every other selected cell keeps the verdict `busy`.
|
|
131
|
+
* A lone inverse cursor cell with NO dim tail (a 1-char draft with the cursor sitting on its
|
|
132
|
+
* only char, empty composer otherwise) is NOT exempted — it stays `busy` — because the
|
|
133
|
+
* exemption requires positive ghost evidence (≥1 dim suggestion-body cell). That closes the
|
|
134
|
+
* false-clean an empty-tail exemption would have opened, honoring the no-new-corruption-vector
|
|
135
|
+
* / fail-toward-hold invariant (Codex CMAP, 2026-08-06). Real ghosts always carry a multi-char
|
|
136
|
+
* dim command body (the captured fixture's tail is 23 dim cells), so nothing real is lost.
|
|
137
|
+
*/
|
|
138
|
+
function isGhostCursorCell(line, row, col, cols, cursorRow, cursorCol, cell, probe) {
|
|
139
|
+
if (row !== cursorRow || col !== cursorCol)
|
|
140
|
+
return false;
|
|
141
|
+
if (!cell.isInverse())
|
|
142
|
+
return false; // typed text is never inverse-rendered; only the software cursor is
|
|
143
|
+
// Require POSITIVE ghost evidence: at least one dim, non-whitespace, non-chrome cell must
|
|
144
|
+
// follow on this row (the SGR-2 suggestion body), and EVERY following such cell must be dim.
|
|
145
|
+
// An empty / whitespace-only tail is NOT a ghost — it is a 1-char draft with the cursor on
|
|
146
|
+
// its only char, which must stay `busy` (fail-toward-hold; a lone inverse cell is not proof
|
|
147
|
+
// of a ghost). Any non-dim text to the right ⇒ real content, also not a ghost.
|
|
148
|
+
let sawDimTail = false;
|
|
149
|
+
for (let c = col + 1; c < cols; c++) {
|
|
150
|
+
line.getCell(c, probe);
|
|
151
|
+
const ch = probe.getChars();
|
|
152
|
+
if (!ch || WHITESPACE.test(ch) || IGNORE_CHARS.has(ch))
|
|
153
|
+
continue;
|
|
154
|
+
if (!probe.isDim())
|
|
155
|
+
return false;
|
|
156
|
+
sawDimTail = true;
|
|
157
|
+
}
|
|
158
|
+
return sawDimTail;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The classifier CORE (Spec 1313 render-gate round 2): classify an already-rendered
|
|
162
|
+
* headless buffer against a profile. Synchronous — it only READS the live buffer, it never
|
|
163
|
+
* parses — so it is shared, unchanged, by BOTH gate paths: the production persistent-mirror
|
|
164
|
+
* gate (`SessionScreen.read()` → this) and the transient {@link classifyScreen} (write a
|
|
165
|
+
* replay into a throwaway term → this). One classifier core means the two paths can never
|
|
166
|
+
* disagree about what "empty" means.
|
|
167
|
+
*
|
|
168
|
+
* Precondition: the caller has already parsed all input into `term` (the mirror flushes in
|
|
169
|
+
* `read()`; `classifyScreen` awaits its `write`). Having no `await`, a single call is atomic
|
|
170
|
+
* against concurrent feeds — nothing can mutate the buffer mid-scan.
|
|
171
|
+
*
|
|
172
|
+
* Returns `{ clean: true, detail: 'empty' }` only when a composer marker is present and the
|
|
173
|
+
* composer region carries zero normal-intensity user cells; otherwise
|
|
174
|
+
* `{ clean: false, reason: 'busy', … }`.
|
|
175
|
+
*/
|
|
176
|
+
export function classifyBuffer(term, cols, rows, profile) {
|
|
177
|
+
const buf = term.buffer.active;
|
|
178
|
+
const lines = screenLines(term, rows);
|
|
179
|
+
const markerRow = findMarkerRow(lines, profile.markerPattern);
|
|
180
|
+
if (markerRow === -1) {
|
|
181
|
+
// No composer marker: a wrapper/boot screen, a full-screen picker with no marker, a
|
|
182
|
+
// mirror that has not yet repainted a coherent frame, or an unrenderable snapshot.
|
|
183
|
+
// Never clean — the safe direction.
|
|
184
|
+
return { clean: false, reason: 'busy', detail: 'no-composer-marker' };
|
|
185
|
+
}
|
|
186
|
+
const endRow = findRegionEnd(lines, markerRow, profile.regionEndPatterns);
|
|
187
|
+
if (endRow === -1) {
|
|
188
|
+
// A marker with no rule/status line beneath it: a partial/mid-repaint frame. The
|
|
189
|
+
// composer has no proven lower bound, so hold rather than scan into the status chrome
|
|
190
|
+
// below it (which would either miscount chrome as user text or, if it renders
|
|
191
|
+
// empty/dim, return a false CLEAN).
|
|
192
|
+
return { clean: false, reason: 'busy', detail: 'no-region-end' };
|
|
193
|
+
}
|
|
194
|
+
const top = buf.viewportY;
|
|
195
|
+
const cell = buf.getNullCell();
|
|
196
|
+
const probe = buf.getNullCell(); // scratch cell for the ghost-tail look-ahead (never clobbers `cell`)
|
|
197
|
+
// Cursor position is viewport-relative (matching `row`, which indexes from `viewportY`).
|
|
198
|
+
const cursorRow = buf.cursorY;
|
|
199
|
+
const cursorCol = buf.cursorX;
|
|
200
|
+
let userCells = 0;
|
|
201
|
+
for (let row = markerRow; row < endRow; row++) {
|
|
202
|
+
const line = buf.getLine(top + row);
|
|
203
|
+
if (!line)
|
|
204
|
+
continue;
|
|
205
|
+
for (let col = 0; col < cols; col++) {
|
|
206
|
+
line.getCell(col, cell);
|
|
207
|
+
const ch = cell.getChars();
|
|
208
|
+
if (!ch || WHITESPACE.test(ch) || IGNORE_CHARS.has(ch))
|
|
209
|
+
continue;
|
|
210
|
+
if (row === markerRow && col === 0)
|
|
211
|
+
continue; // the marker glyph itself
|
|
212
|
+
if (cell.isDim())
|
|
213
|
+
continue; // placeholder / hint chrome renders dim (claude/codex)
|
|
214
|
+
if (profile.placeholderFgPalette !== undefined &&
|
|
215
|
+
cell.isFgPalette() &&
|
|
216
|
+
cell.getFgColor() === profile.placeholderFgPalette) {
|
|
217
|
+
continue; // per-app placeholder color: agy renders its idle hint in palette-8 (gray)
|
|
218
|
+
}
|
|
219
|
+
if (isGhostCursorCell(line, row, col, cols, cursorRow, cursorCol, cell, probe)) {
|
|
220
|
+
continue; // claude's suggested-command ghost cursor cell (see isGhostCursorCell)
|
|
221
|
+
}
|
|
222
|
+
userCells++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return userCells === 0
|
|
226
|
+
? { clean: true, detail: 'empty' }
|
|
227
|
+
: { clean: false, reason: 'busy', detail: 'user-text' };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Classify a one-shot replay snapshot by rendering it into a THROWAWAY headless terminal
|
|
231
|
+
* (Spec 1313). The transient path — the fixture suite and any caller holding a replay string
|
|
232
|
+
* rather than a live mirror. Production instead classifies the session's persistent
|
|
233
|
+
* {@link SessionScreen} directly via {@link classifyBuffer} (see the module header). Async
|
|
234
|
+
* because the headless terminal parses its input on a write callback; the shared
|
|
235
|
+
* {@link classifyBuffer} then does the actual classification.
|
|
236
|
+
*/
|
|
237
|
+
export async function classifyScreen(snapshot, profile) {
|
|
238
|
+
const { cols, rows } = snapshot;
|
|
239
|
+
// A throwaway terminal for this single classify. scrollback 2000 is ample for a
|
|
240
|
+
// whole-replay render; the gate reads only the viewport, so the value never changes the
|
|
241
|
+
// verdict (the persistent mirror uses a much smaller one for the same reason).
|
|
242
|
+
const term = new Terminal({ cols, rows, allowProposedApi: true, scrollback: 2000 });
|
|
243
|
+
try {
|
|
244
|
+
await new Promise((resolve) => term.write(snapshot.replay, resolve));
|
|
245
|
+
return classifyBuffer(term, cols, rows, profile);
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
term.dispose();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=render-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-gate.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/render-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1238: retention for PTY session logs.
|
|
3
|
+
*
|
|
4
|
+
* `PtySession` already bounds a *single* session's log (50 MB, one `.1`
|
|
5
|
+
* rotation — see `../../terminal/pty-session.ts`), but nothing has ever
|
|
6
|
+
* deleted the log of a session that ended. Every terminal Tower has ever
|
|
7
|
+
* opened leaves a file behind forever: an audited machine reached 29,728 files
|
|
8
|
+
* / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
|
|
9
|
+
*
|
|
10
|
+
* This module adds the missing cross-file policy: a log is deleted once its
|
|
11
|
+
* last write is older than the retention window. mtime is the retention key
|
|
12
|
+
* because the last byte written to a session log is, in effect, the moment
|
|
13
|
+
* that session stopped producing output — the "session ended" signal, without
|
|
14
|
+
* needing to join against DB session state (which is itself pruned, so dead
|
|
15
|
+
* sessions leave no row to join against).
|
|
16
|
+
*
|
|
17
|
+
* Live sessions are excluded by id regardless of mtime. A session log's fd is
|
|
18
|
+
* held open for the session's whole life, so unlinking it under a live writer
|
|
19
|
+
* would silently redirect every subsequent write into an unlinked inode —
|
|
20
|
+
* losing the log of the one session someone might actually want to read. A
|
|
21
|
+
* 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
|
|
22
|
+
*
|
|
23
|
+
* Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
|
|
24
|
+
* in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const DEFAULT_LOG_RETENTION_DAYS = 30;
|
|
27
|
+
export declare const DEFAULT_LOG_SWEEP_INTERVAL_MS: number;
|
|
28
|
+
export declare const MS_PER_DAY: number;
|
|
29
|
+
/**
|
|
30
|
+
* Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
|
|
31
|
+
* `0` disables the sweep entirely — an explicit "keep everything" opt-out for
|
|
32
|
+
* anyone who treats these logs as an audit trail.
|
|
33
|
+
*
|
|
34
|
+
* NaN-checked rather than `parsed || default` because `0` is a meaningful
|
|
35
|
+
* override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveLogRetentionDays(env?: NodeJS.ProcessEnv): number;
|
|
38
|
+
/**
|
|
39
|
+
* Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
|
|
40
|
+
* at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveLogSweepIntervalMs(env?: NodeJS.ProcessEnv): number;
|
|
43
|
+
export interface SessionLogFootprint {
|
|
44
|
+
/** Number of session log files (including `.log.1` rotations). */
|
|
45
|
+
files: number;
|
|
46
|
+
/** Total bytes on disk. */
|
|
47
|
+
bytes: number;
|
|
48
|
+
/** mtime of the oldest log, or null when there are none. */
|
|
49
|
+
oldestMtimeMs: number | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Total footprint of the session log directory. Read-only — used by
|
|
53
|
+
* `codev doctor` to surface the number the issue was filed about, and by the
|
|
54
|
+
* sweep's own logging. A missing directory is a healthy zero, not an error.
|
|
55
|
+
*/
|
|
56
|
+
export declare function measureSessionLogs(logDir: string): SessionLogFootprint;
|
|
57
|
+
export interface SweepSessionLogsOptions {
|
|
58
|
+
/** The session log directory, i.e. `~/.agent-farm/logs`. */
|
|
59
|
+
logDir: string;
|
|
60
|
+
/** Age at which a log becomes eligible for deletion. `0` disables the sweep. */
|
|
61
|
+
retentionMs: number;
|
|
62
|
+
/**
|
|
63
|
+
* Ids of sessions that are alive right now. Their logs are never deleted,
|
|
64
|
+
* however old the mtime looks, because their fd is still open.
|
|
65
|
+
*/
|
|
66
|
+
activeSessionIds?: Iterable<string>;
|
|
67
|
+
/** Seam, defaults to `Date.now()`. */
|
|
68
|
+
now?: number;
|
|
69
|
+
log?: (msg: string) => void;
|
|
70
|
+
}
|
|
71
|
+
export interface SweepSessionLogsResult {
|
|
72
|
+
/** Session log files examined. */
|
|
73
|
+
scanned: number;
|
|
74
|
+
/** Files unlinked. */
|
|
75
|
+
deleted: number;
|
|
76
|
+
/** Bytes reclaimed (sum of the deleted files' sizes). */
|
|
77
|
+
bytesFreed: number;
|
|
78
|
+
/** Aged-out files kept because their session is still live. */
|
|
79
|
+
skippedActive: number;
|
|
80
|
+
/** Files that were eligible but could not be unlinked. */
|
|
81
|
+
failed: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Delete session logs whose last write is older than `retentionMs`.
|
|
85
|
+
*
|
|
86
|
+
* Never throws: a log directory that can't be read, or an individual file that
|
|
87
|
+
* can't be stat'd or unlinked, is counted and skipped. Log retention is
|
|
88
|
+
* janitorial — it must not be able to take Tower's startup path down with it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function sweepSessionLogs(opts: SweepSessionLogsOptions): SweepSessionLogsResult;
|
|
91
|
+
/** Human-readable byte count for log lines and `codev doctor` output. */
|
|
92
|
+
export declare function formatBytes(bytes: number): string;
|
|
93
|
+
//# sourceMappingURL=session-log-sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-log-sweep.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,6BAA6B,QAAsB,CAAC;AAEjE,eAAO,MAAM,UAAU,QAAsB,CAAC;AAE9C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGpF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGtF;AAED,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAyBtE;AAED,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,sBAAsB,CA2DtF;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1238: retention for PTY session logs.
|
|
3
|
+
*
|
|
4
|
+
* `PtySession` already bounds a *single* session's log (50 MB, one `.1`
|
|
5
|
+
* rotation — see `../../terminal/pty-session.ts`), but nothing has ever
|
|
6
|
+
* deleted the log of a session that ended. Every terminal Tower has ever
|
|
7
|
+
* opened leaves a file behind forever: an audited machine reached 29,728 files
|
|
8
|
+
* / 19 GB in `~/.agent-farm/logs`, 97% of it untouched for over a month.
|
|
9
|
+
*
|
|
10
|
+
* This module adds the missing cross-file policy: a log is deleted once its
|
|
11
|
+
* last write is older than the retention window. mtime is the retention key
|
|
12
|
+
* because the last byte written to a session log is, in effect, the moment
|
|
13
|
+
* that session stopped producing output — the "session ended" signal, without
|
|
14
|
+
* needing to join against DB session state (which is itself pruned, so dead
|
|
15
|
+
* sessions leave no row to join against).
|
|
16
|
+
*
|
|
17
|
+
* Live sessions are excluded by id regardless of mtime. A session log's fd is
|
|
18
|
+
* held open for the session's whole life, so unlinking it under a live writer
|
|
19
|
+
* would silently redirect every subsequent write into an unlinked inode —
|
|
20
|
+
* losing the log of the one session someone might actually want to read. A
|
|
21
|
+
* 30-day-idle live session is vanishingly unlikely, but the guard is cheap.
|
|
22
|
+
*
|
|
23
|
+
* Wired in `./tower-server.ts` on two triggers (Tower startup, and a daily
|
|
24
|
+
* in-process timer), mirroring the husk sweep in `./shellper-husk-sweep.ts`.
|
|
25
|
+
*/
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
/** `<session-uuid>.log` and its single rotation `<session-uuid>.log.1`. */
|
|
29
|
+
const SESSION_LOG_RE = /^(.+)\.log(\.1)?$/;
|
|
30
|
+
export const DEFAULT_LOG_RETENTION_DAYS = 30;
|
|
31
|
+
export const DEFAULT_LOG_SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
32
|
+
export const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Retention window in days (env `AGENT_FARM_LOG_RETENTION_DAYS`, default 30).
|
|
35
|
+
* `0` disables the sweep entirely — an explicit "keep everything" opt-out for
|
|
36
|
+
* anyone who treats these logs as an audit trail.
|
|
37
|
+
*
|
|
38
|
+
* NaN-checked rather than `parsed || default` because `0` is a meaningful
|
|
39
|
+
* override and is falsy in JS (same reasoning as `resolveHuskGraceMs`).
|
|
40
|
+
*/
|
|
41
|
+
export function resolveLogRetentionDays(env = process.env) {
|
|
42
|
+
const parsed = parseInt(env.AGENT_FARM_LOG_RETENTION_DAYS ?? '', 10);
|
|
43
|
+
return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_RETENTION_DAYS : parsed, 0);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sweep cadence (env `AGENT_FARM_LOG_SWEEP_INTERVAL_MS`, default 24h). Floored
|
|
47
|
+
* at 1s so a bad value can't spin the event loop; tests shorten it deliberately.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveLogSweepIntervalMs(env = process.env) {
|
|
50
|
+
const parsed = parseInt(env.AGENT_FARM_LOG_SWEEP_INTERVAL_MS ?? '', 10);
|
|
51
|
+
return Math.max(Number.isNaN(parsed) ? DEFAULT_LOG_SWEEP_INTERVAL_MS : parsed, 1000);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Total footprint of the session log directory. Read-only — used by
|
|
55
|
+
* `codev doctor` to surface the number the issue was filed about, and by the
|
|
56
|
+
* sweep's own logging. A missing directory is a healthy zero, not an error.
|
|
57
|
+
*/
|
|
58
|
+
export function measureSessionLogs(logDir) {
|
|
59
|
+
const footprint = { files: 0, bytes: 0, oldestMtimeMs: null };
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = fs.readdirSync(logDir);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return footprint;
|
|
66
|
+
}
|
|
67
|
+
for (const name of entries) {
|
|
68
|
+
if (!SESSION_LOG_RE.test(name))
|
|
69
|
+
continue;
|
|
70
|
+
let stat;
|
|
71
|
+
try {
|
|
72
|
+
stat = fs.statSync(path.join(logDir, name));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue; // vanished mid-scan — nothing to account for
|
|
76
|
+
}
|
|
77
|
+
if (!stat.isFile())
|
|
78
|
+
continue;
|
|
79
|
+
footprint.files += 1;
|
|
80
|
+
footprint.bytes += stat.size;
|
|
81
|
+
if (footprint.oldestMtimeMs === null || stat.mtimeMs < footprint.oldestMtimeMs) {
|
|
82
|
+
footprint.oldestMtimeMs = stat.mtimeMs;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return footprint;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Delete session logs whose last write is older than `retentionMs`.
|
|
89
|
+
*
|
|
90
|
+
* Never throws: a log directory that can't be read, or an individual file that
|
|
91
|
+
* can't be stat'd or unlinked, is counted and skipped. Log retention is
|
|
92
|
+
* janitorial — it must not be able to take Tower's startup path down with it.
|
|
93
|
+
*/
|
|
94
|
+
export function sweepSessionLogs(opts) {
|
|
95
|
+
const result = {
|
|
96
|
+
scanned: 0,
|
|
97
|
+
deleted: 0,
|
|
98
|
+
bytesFreed: 0,
|
|
99
|
+
skippedActive: 0,
|
|
100
|
+
failed: 0,
|
|
101
|
+
};
|
|
102
|
+
if (opts.retentionMs <= 0)
|
|
103
|
+
return result;
|
|
104
|
+
const now = opts.now ?? Date.now();
|
|
105
|
+
const active = new Set(opts.activeSessionIds ?? []);
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = fs.readdirSync(opts.logDir);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return result; // no log dir yet (fresh install) — nothing to sweep
|
|
112
|
+
}
|
|
113
|
+
for (const name of entries) {
|
|
114
|
+
const match = SESSION_LOG_RE.exec(name);
|
|
115
|
+
if (!match)
|
|
116
|
+
continue;
|
|
117
|
+
const fullPath = path.join(opts.logDir, name);
|
|
118
|
+
let stat;
|
|
119
|
+
try {
|
|
120
|
+
stat = fs.statSync(fullPath);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!stat.isFile())
|
|
126
|
+
continue;
|
|
127
|
+
result.scanned += 1;
|
|
128
|
+
if (now - stat.mtimeMs < opts.retentionMs)
|
|
129
|
+
continue;
|
|
130
|
+
if (active.has(match[1])) {
|
|
131
|
+
result.skippedActive += 1;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(fullPath);
|
|
136
|
+
result.deleted += 1;
|
|
137
|
+
result.bytesFreed += stat.size;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
result.failed += 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (result.deleted > 0 || result.failed > 0) {
|
|
144
|
+
opts.log?.(`Session log sweep: deleted ${result.deleted} log(s), freed ${formatBytes(result.bytesFreed)}` +
|
|
145
|
+
` (${result.scanned} scanned` +
|
|
146
|
+
(result.skippedActive > 0 ? `, ${result.skippedActive} kept as live` : '') +
|
|
147
|
+
(result.failed > 0 ? `, ${result.failed} failed` : '') +
|
|
148
|
+
')');
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/** Human-readable byte count for log lines and `codev doctor` output. */
|
|
153
|
+
export function formatBytes(bytes) {
|
|
154
|
+
if (bytes < 1024)
|
|
155
|
+
return `${bytes} B`;
|
|
156
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
157
|
+
let value = bytes / 1024;
|
|
158
|
+
let unit = 0;
|
|
159
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
160
|
+
value /= 1024;
|
|
161
|
+
unit += 1;
|
|
162
|
+
}
|
|
163
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=session-log-sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-log-sweep.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-log-sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session submission lock (Spec 1273, verify phase).
|
|
3
|
+
*
|
|
4
|
+
* ## The bug this exists to make impossible
|
|
5
|
+
*
|
|
6
|
+
* `writeMessageToSession` writes text to the PTY and schedules the Enter that
|
|
7
|
+
* submits it **50–80ms later** (`message-write.ts`). The write functions return
|
|
8
|
+
* that completion offset but do not wait for it, and `/api/send` responded as
|
|
9
|
+
* soon as the write was *scheduled*. So an awaited send resolved before its own
|
|
10
|
+
* message had been submitted.
|
|
11
|
+
*
|
|
12
|
+
* In production (`afx reset`, 2026-07-31) that gap swallowed a `/clear`
|
|
13
|
+
* entirely. Reset awaited the raw write of `/clear`, then wrote the
|
|
14
|
+
* re-orientation — landing inside the 50ms window, into the same composer,
|
|
15
|
+
* ahead of the Enter. One Enter then submitted both as a single message
|
|
16
|
+
* beginning `/clear### [ARCHITECT INSTRUCTION...`. The slash command was never
|
|
17
|
+
* executed, the builder's context was never cleared, and every layer reported
|
|
18
|
+
* success.
|
|
19
|
+
*
|
|
20
|
+
* ## Ordering is not atomicity
|
|
21
|
+
*
|
|
22
|
+
* The paced writer already threads a delay offset between consecutive writes, and the
|
|
23
|
+
* mailbox delivery path serialises messages to one agent (`deliverAgentMailSerialized`
|
|
24
|
+
* chains each delivery on the prior one's paced completion). Both fix the *order* in
|
|
25
|
+
* which queued messages reach a session. Neither would have prevented this: the two
|
|
26
|
+
* writes were correctly ordered and still coalesced, because being second is not the
|
|
27
|
+
* same as being separate. What was missing is a guarantee that a submission completes —
|
|
28
|
+
* Enter included — before the next write to that session begins.
|
|
29
|
+
*
|
|
30
|
+
* ## What this provides
|
|
31
|
+
*
|
|
32
|
+
* A promise chain per session key. Each submission waits for the previous one to
|
|
33
|
+
* finish (including its Enter), so concurrent `/api/send` requests to the same
|
|
34
|
+
* terminal cannot interleave in the composer. `await submitToSession(...)` means
|
|
35
|
+
* *submitted*, not *scheduled* — which is what every caller already assumed it
|
|
36
|
+
* meant.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately keyed by session id rather than holding a session object: Tower
|
|
39
|
+
* re-fetches sessions by id, and a lock that outlived its session would pin a
|
|
40
|
+
* dead reference.
|
|
41
|
+
*
|
|
42
|
+
* ## Exactly what it covers — this is NOT blanket per-session atomicity
|
|
43
|
+
*
|
|
44
|
+
* A lock only serialises writers that take it. Currently that is the `escape`
|
|
45
|
+
* and `interrupt` paths of `/api/send`. Every other PTY writer still writes
|
|
46
|
+
* directly, and it is worth being precise about why:
|
|
47
|
+
*
|
|
48
|
+
* - The mailbox delivery path (`deliverAgentMailSerialized`, Spec 1313) — every
|
|
49
|
+
* normal `/api/send` AND every cron notification (Phase 6 rerouted cron here; the
|
|
50
|
+
* old blind `writeMessageToSession` is gone). NOT covered by this lock, and does not
|
|
51
|
+
* need it: it runs its OWN per-agent write serializer that completion-chains each
|
|
52
|
+
* delivery on the prior one's paced write (text + Enter), so two mailbox deliveries
|
|
53
|
+
* to one agent cannot interleave. That is a disjoint lock from this per-session one,
|
|
54
|
+
* so a mailbox delivery is not serialised against a concurrent `escape`/`interrupt`
|
|
55
|
+
* here — but a normal mailbox delivery only ever writes onto a render-gate-verified
|
|
56
|
+
* empty prompt, and `interrupt` is the explicit gate-bypassing human action (see
|
|
57
|
+
* tower-routes.ts), so that residual cross-path race is an accepted, documented
|
|
58
|
+
* boundary, not a regression this lock must close.
|
|
59
|
+
* - `POST /api/terminals/:id/write` — NOT covered. It is a raw passthrough
|
|
60
|
+
* with no Enter semantics of its own.
|
|
61
|
+
* - `tower-websocket.ts` keystrokes and the shellper frame relay — DELIBERATELY
|
|
62
|
+
* not covered. That is a human typing into their own terminal; serialising
|
|
63
|
+
* it behind an agent's message would make the UI feel stuck, and the human
|
|
64
|
+
* is the composer's owner.
|
|
65
|
+
*
|
|
66
|
+
* So the guarantee is: **two lock-taking `/api/send` submissions (escape/interrupt)
|
|
67
|
+
* to one session cannot interleave**, which is the failure that reached production.
|
|
68
|
+
* Anything stronger requires the remaining writers to take the lock too.
|
|
69
|
+
*/
|
|
70
|
+
/** Injectable for tests; real timers otherwise. */
|
|
71
|
+
export interface SubmitClock {
|
|
72
|
+
sleep(ms: number): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run a write against a session so that it completes before any other
|
|
76
|
+
* submission to the same session begins.
|
|
77
|
+
*
|
|
78
|
+
* @param sessionId terminal/session id — the lock's granularity
|
|
79
|
+
* @param write performs the write; returns ms from now until the final
|
|
80
|
+
* keystroke (the Enter) has been written. This is exactly what
|
|
81
|
+
* `writeMessageToSession` / `writeEscapeToSession` already
|
|
82
|
+
* return, so callers pass them through unchanged.
|
|
83
|
+
* @returns resolves once the submission is complete
|
|
84
|
+
*/
|
|
85
|
+
export declare function submitToSession(sessionId: string, write: () => number, clock?: SubmitClock): Promise<void>;
|
|
86
|
+
/** Number of sessions with a submission in flight. Test/observability only. */
|
|
87
|
+
export declare function pendingSubmissionSessions(): number;
|
|
88
|
+
/** Drop all chains. Test-only; a live Tower should let them drain. */
|
|
89
|
+
export declare function resetSubmissionChains(): void;
|
|
90
|
+
//# sourceMappingURL=session-submit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-submit.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/session-submit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAUH,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,MAAM,EACnB,KAAK,GAAE,WAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|