@cluesmith/codev 3.3.2 → 3.3.4
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-D3XaKpZW.js → index-DZf6pNPE.js} +41 -41
- package/dashboard-dist/assets/index-DZf6pNPE.js.map +1 -0
- package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +48 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/ide.d.ts +20 -0
- package/dist/agent-farm/commands/ide.d.ts.map +1 -0
- package/dist/agent-farm/commands/ide.js +138 -0
- package/dist/agent-farm/commands/ide.js.map +1 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
- package/dist/agent-farm/commands/inbox.js +79 -10
- package/dist/agent-farm/commands/inbox.js.map +1 -1
- package/dist/agent-farm/commands/interrupt.d.ts +4 -1
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
- package/dist/agent-farm/commands/interrupt.js +6 -3
- package/dist/agent-farm/commands/interrupt.js.map +1 -1
- package/dist/agent-farm/commands/reset.d.ts +4 -1
- package/dist/agent-farm/commands/reset.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset.js +6 -3
- package/dist/agent-farm/commands/reset.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +28 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +79 -6
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +16 -7
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/tower.d.ts +21 -9
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +90 -30
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +3 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +8 -443
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +47 -7
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
- package/dist/agent-farm/db/mailbox.js +56 -12
- package/dist/agent-farm/db/mailbox.js.map +1 -1
- package/dist/agent-farm/db/migrations.d.ts +57 -0
- package/dist/agent-farm/db/migrations.d.ts.map +1 -0
- package/dist/agent-farm/db/migrations.js +530 -0
- package/dist/agent-farm/db/migrations.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 +1 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/tower-owner.d.ts +168 -0
- package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
- package/dist/agent-farm/db/tower-owner.js +369 -0
- package/dist/agent-farm/db/tower-owner.js.map +1 -0
- package/dist/agent-farm/db/types.d.ts +47 -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/ide-record.d.ts +65 -0
- package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-record.js +118 -0
- package/dist/agent-farm/lib/ide-record.js.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +96 -19
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.js +2 -1
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.d.ts +44 -2
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.js +79 -6
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
- package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
- package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-forward.js +274 -0
- package/dist/agent-farm/servers/ide-forward.js.map +1 -0
- package/dist/agent-farm/servers/ide-server.d.ts +68 -0
- package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-server.js +281 -0
- package/dist/agent-farm/servers/ide-server.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +296 -12
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.js +566 -69
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +66 -19
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.js +204 -37
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +138 -11
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +144 -35
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +28 -12
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +130 -79
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +77 -1
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
- package/dist/agent-farm/servers/render-gate.js +174 -25
- package/dist/agent-farm/servers/render-gate.js.map +1 -1
- package/dist/agent-farm/servers/session-submit.d.ts +7 -4
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
- package/dist/agent-farm/servers/session-submit.js +7 -4
- package/dist/agent-farm/servers/session-submit.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +7 -3
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +17 -10
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +186 -26
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +79 -9
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +106 -13
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +39 -0
- 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 +26 -2
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +78 -2
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +107 -11
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/agent-farm/utils/port.d.ts +22 -0
- package/dist/agent-farm/utils/port.d.ts.map +1 -0
- package/dist/agent-farm/utils/port.js +35 -0
- package/dist/agent-farm/utils/port.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +26 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +36 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +61 -1
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/tree-tripwire.d.ts +177 -0
- package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
- package/dist/commands/consult/tree-tripwire.js +356 -0
- package/dist/commands/consult/tree-tripwire.js.map +1 -0
- package/dist/commands/doctor.d.ts +69 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +165 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-image.d.ts +25 -1
- package/dist/commands/generate-image.d.ts.map +1 -1
- package/dist/commands/generate-image.js +410 -2
- package/dist/commands/generate-image.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +18 -2
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/lib/agent-env.d.ts +86 -0
- package/dist/lib/agent-env.d.ts.map +1 -0
- package/dist/lib/agent-env.js +126 -0
- package/dist/lib/agent-env.js.map +1 -0
- package/dist/lib/forge-contracts.d.ts +76 -5
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge-rate-limit.d.ts +45 -0
- package/dist/lib/forge-rate-limit.d.ts.map +1 -0
- package/dist/lib/forge-rate-limit.js +89 -0
- package/dist/lib/forge-rate-limit.js.map +1 -0
- package/dist/lib/forge.d.ts +29 -1
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +96 -21
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/github.d.ts +13 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +58 -15
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/test-env.d.ts +17 -0
- package/dist/lib/test-env.d.ts.map +1 -1
- package/dist/lib/test-env.js +20 -0
- package/dist/lib/test-env.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +10 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +38 -9
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +174 -8
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +299 -25
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +23 -2
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +115 -36
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +34 -5
- package/dist/terminal/session-screen.d.ts.map +1 -1
- package/dist/terminal/session-screen.js +52 -6
- package/dist/terminal/session-screen.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +73 -0
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +151 -0
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-main.js +20 -3
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +2 -0
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +17 -1
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +28 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/dist/terminal/terminal-replies.d.ts +102 -0
- package/dist/terminal/terminal-replies.d.ts.map +1 -0
- package/dist/terminal/terminal-replies.js +181 -0
- package/dist/terminal/terminal-replies.js.map +1 -0
- package/package.json +4 -4
- package/scripts/forge/gitea/_lib.sh +214 -0
- package/scripts/forge/gitea/issue-comment.sh +8 -1
- package/scripts/forge/gitea/issue-view.sh +95 -4
- package/scripts/forge/gitea/pr-create.sh +155 -0
- package/scripts/forge/gitea/pr-exists.sh +32 -4
- package/scripts/forge/gitea/pr-list.sh +40 -31
- package/scripts/forge/gitea/pr-view.sh +65 -4
- package/scripts/forge/gitea/recently-merged.sh +91 -21
- package/scripts/forge/gitea/user-identity.sh +30 -1
- package/scripts/forge/github/issue-view.sh +5 -2
- package/scripts/forge/github/pr-create.sh +47 -0
- package/scripts/forge/github/pr-list.sh +6 -2
- package/scripts/forge/github/pr-search.sh +6 -2
- package/scripts/forge/gitlab/issue-search.sh +1 -0
- package/scripts/forge/gitlab/issue-view.sh +24 -4
- package/scripts/forge/gitlab/pr-create.sh +53 -0
- package/scripts/forge/gitlab/pr-search.sh +37 -2
- package/scripts/forge/linear/auth-status.sh +1 -0
- package/scripts/forge/linear/issue-comment.sh +1 -0
- package/scripts/forge/linear/issue-list.sh +1 -0
- package/scripts/forge/linear/issue-search.sh +1 -0
- package/scripts/forge/linear/issue-view.sh +14 -2
- package/scripts/forge/linear/recently-closed.sh +1 -0
- package/scripts/forge/linear/user-identity.sh +1 -0
- package/skeleton/.claude/skills/generate-image/SKILL.md +39 -1
- package/skeleton/.codex/skills/generate-image/SKILL.md +39 -1
- package/skeleton/protocols/air/prompts/pr.md +6 -1
- package/skeleton/protocols/aspir/prompts/review.md +6 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
- package/skeleton/protocols/bugfix/protocol.md +2 -1
- package/skeleton/protocols/maintain/prompts/review.md +6 -1
- package/skeleton/protocols/pir/prompts/review.md +10 -7
- package/skeleton/protocols/spir/prompts/review.md +6 -1
- package/skeleton/resources/commands/agent-farm.md +28 -3
- package/skeleton/roles/consultant.md +32 -0
- package/skeleton/templates/AGENTS.md +1 -1
- package/skeleton/templates/CLAUDE.md +1 -1
- package/templates/tower.html +16 -1
- package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
- package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
|
@@ -17,9 +17,30 @@
|
|
|
17
17
|
* correctness bug, since agy's screens classify by an entirely different rule.
|
|
18
18
|
*/
|
|
19
19
|
import type { GateProfile } from './render-gate.js';
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* claude composer profile (marker ❯, dim placeholder — measured, spike g2).
|
|
22
|
+
*
|
|
23
|
+
* `queuesInputMidTurn` measured for Issue #1664 against claude 2.1.266: a message delivered while
|
|
24
|
+
* a turn was streaming landed intact and was submitted as one message at the turn's end in 100 of
|
|
25
|
+
* 100 trials (0 head-loss), and the TUI shows it as `Press up to edit queued messages` in the
|
|
26
|
+
* meantime. Evidence: `codev/evidence/1664-streaming-delivery/`.
|
|
27
|
+
*/
|
|
21
28
|
export declare const CLAUDE_PROFILE: GateProfile;
|
|
22
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* codex composer profile (marker ›, dim placeholder — measured, spike g2).
|
|
31
|
+
*
|
|
32
|
+
* `queuesInputMidTurn` measured for Issue #1664 against codex-cli 0.153.4, by the same harness
|
|
33
|
+
* and the same production delivery path as claude's: 20 of 20 mid-turn deliveries landed intact
|
|
34
|
+
* and were submitted, 0 head-loss, every one written at an instant the retired whole-screen
|
|
35
|
+
* settle would have refused. Evidence: `codev/evidence/1664-streaming-delivery/`.
|
|
36
|
+
*
|
|
37
|
+
* One measured quirk worth knowing, though it needs no code: codex's composer SLIDES DOWN the
|
|
38
|
+
* screen as its transcript grows (measured rows 13 → 37 over ~11 s) until it reaches the bottom
|
|
39
|
+
* and pins. Its row span is part of the fingerprint, so during that phase the region legitimately
|
|
40
|
+
* counts as moving and mail holds — a fresh codex session's first delivery waits for the screen
|
|
41
|
+
* to fill. Transient, self-correcting, and not worth special-casing: a long-lived builder's
|
|
42
|
+
* screen is always full.
|
|
43
|
+
*/
|
|
23
44
|
export declare const CODEX_PROFILE: GateProfile;
|
|
24
45
|
/**
|
|
25
46
|
* agy composer profile (Spec 1313 Phase 3 — net-new measurement). agy breaks the
|
|
@@ -32,6 +53,27 @@ export declare const CODEX_PROFILE: GateProfile;
|
|
|
32
53
|
* option is palette-12, counted) — so a blind Enter never confirms filesystem
|
|
33
54
|
* trust. Region bounds reuse the shared rule-line/status patterns (agy brackets
|
|
34
55
|
* its composer with `─────` rules, like claude).
|
|
56
|
+
*
|
|
57
|
+
* The two marker ANCHORS (#1474) exist because `> ` alone does not identify agy's
|
|
58
|
+
* composer. Measured on real agy 1.1.13 captures (`fixtures/gate/agy-*.txt`), a live
|
|
59
|
+
* screen routinely carries OTHER `> ` rows, and last-match-wins picked them over the
|
|
60
|
+
* composer:
|
|
61
|
+
* - the slash-menu selection cursor (`> /add-dir …`) — palette-12 like the composer,
|
|
62
|
+
* and rendered BELOW it, so it won the marker scan outright;
|
|
63
|
+
* - the transcript echo of every submitted turn (`> <the message you sent>`), palette-4,
|
|
64
|
+
* one per conversation turn;
|
|
65
|
+
* - the trust dialog's selected option — palette-12, with no composer on screen at all.
|
|
66
|
+
* Hence `markerRequiresCursorRow: true` (only the live input row holds the cursor — menu
|
|
67
|
+
* and dialog rows never do) and `markerFgPalette: 12` (the marker glyph's own color, which
|
|
68
|
+
* separates the composer from the palette-4 transcript echo). Both were measured stable
|
|
69
|
+
* across agy's modes, including the bare-`>` no-hint mode.
|
|
70
|
+
*
|
|
71
|
+
* Direction of failure, deliberately: an agy re-theme, or a cursor parked off the composer
|
|
72
|
+
* (e.g. a single mid-repaint frame), yields NO marker ⇒ `no-composer-marker` ⇒ the message
|
|
73
|
+
* is held and re-checked, never delivered onto a screen we failed to understand. A
|
|
74
|
+
* sustained hold escalates through the mailbox-delivery liveness telemetry
|
|
75
|
+
* (`recordStreak`) — the same drift path {@link REGION_END_PATTERNS} documents — so widen
|
|
76
|
+
* these anchors ONLY from a real capture.
|
|
35
77
|
*/
|
|
36
78
|
export declare const AGY_PROFILE: GateProfile;
|
|
37
79
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-profiles.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+BpD
|
|
1
|
+
{"version":3,"file":"gate-profiles.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA+BpD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAE,WAK5B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,EAAE,WAK3B,CAAC;AAkBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,WAAW,EAAE,WAyBzB,CAAC;AAQF;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAMxE"}
|
|
@@ -45,24 +45,56 @@ const COMPOSER_MARKER = /^[❯›]/;
|
|
|
45
45
|
* from a real capture — a too-loose pattern that matches draft content is a false-clean.
|
|
46
46
|
*/
|
|
47
47
|
const REGION_END_PATTERNS = [/^[─━╌┄]{5,}/, /^\s{2,}(gpt|high:|~\/)/];
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* claude composer profile (marker ❯, dim placeholder — measured, spike g2).
|
|
50
|
+
*
|
|
51
|
+
* `queuesInputMidTurn` measured for Issue #1664 against claude 2.1.266: a message delivered while
|
|
52
|
+
* a turn was streaming landed intact and was submitted as one message at the turn's end in 100 of
|
|
53
|
+
* 100 trials (0 head-loss), and the TUI shows it as `Press up to edit queued messages` in the
|
|
54
|
+
* meantime. Evidence: `codev/evidence/1664-streaming-delivery/`.
|
|
55
|
+
*/
|
|
49
56
|
export const CLAUDE_PROFILE = {
|
|
50
57
|
app: 'claude',
|
|
51
58
|
markerPattern: COMPOSER_MARKER,
|
|
52
59
|
regionEndPatterns: REGION_END_PATTERNS,
|
|
60
|
+
queuesInputMidTurn: true,
|
|
53
61
|
};
|
|
54
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* codex composer profile (marker ›, dim placeholder — measured, spike g2).
|
|
64
|
+
*
|
|
65
|
+
* `queuesInputMidTurn` measured for Issue #1664 against codex-cli 0.153.4, by the same harness
|
|
66
|
+
* and the same production delivery path as claude's: 20 of 20 mid-turn deliveries landed intact
|
|
67
|
+
* and were submitted, 0 head-loss, every one written at an instant the retired whole-screen
|
|
68
|
+
* settle would have refused. Evidence: `codev/evidence/1664-streaming-delivery/`.
|
|
69
|
+
*
|
|
70
|
+
* One measured quirk worth knowing, though it needs no code: codex's composer SLIDES DOWN the
|
|
71
|
+
* screen as its transcript grows (measured rows 13 → 37 over ~11 s) until it reaches the bottom
|
|
72
|
+
* and pins. Its row span is part of the fingerprint, so during that phase the region legitimately
|
|
73
|
+
* counts as moving and mail holds — a fresh codex session's first delivery waits for the screen
|
|
74
|
+
* to fill. Transient, self-correcting, and not worth special-casing: a long-lived builder's
|
|
75
|
+
* screen is always full.
|
|
76
|
+
*/
|
|
55
77
|
export const CODEX_PROFILE = {
|
|
56
78
|
app: 'codex',
|
|
57
79
|
markerPattern: COMPOSER_MARKER,
|
|
58
80
|
regionEndPatterns: REGION_END_PATTERNS,
|
|
81
|
+
queuesInputMidTurn: true,
|
|
59
82
|
};
|
|
60
83
|
/**
|
|
61
|
-
* agy (Antigravity CLI
|
|
62
|
-
*
|
|
63
|
-
*
|
|
84
|
+
* agy (Antigravity CLI) composer marker: a `>` prompt glyph at the input row start — a
|
|
85
|
+
* different glyph from claude/codex's `❯`/`›`, so its own pattern. The trailing separator
|
|
86
|
+
* is `\s|$` rather than a literal space because agy's no-hint mode renders the empty
|
|
87
|
+
* composer as a BARE `>`, which right-trims to `">"` (measured on 1.1.13, #1474) — the
|
|
88
|
+
* old `/^> /` never matched it, so the gate held every message to an agy in that mode
|
|
89
|
+
* forever.
|
|
90
|
+
*
|
|
91
|
+
* This text pattern is deliberately NOT the whole marker test. `> ` is ordinary output —
|
|
92
|
+
* and, measured on real agy screens, it is also agy's own slash-menu selection cursor and
|
|
93
|
+
* the per-turn transcript echo of each submitted message. The composer is identified by
|
|
94
|
+
* this pattern PLUS the two anchors on {@link AGY_PROFILE} below; broaden or narrow the
|
|
95
|
+
* three together, never this line alone.
|
|
64
96
|
*/
|
|
65
|
-
const AGY_MARKER = /^>
|
|
97
|
+
const AGY_MARKER = /^>(\s|$)/;
|
|
66
98
|
/**
|
|
67
99
|
* agy composer profile (Spec 1313 Phase 3 — net-new measurement). agy breaks the
|
|
68
100
|
* dim-placeholder assumption: its idle mode-hint (`Accept-edits mode: …`) renders
|
|
@@ -74,12 +106,53 @@ const AGY_MARKER = /^> /;
|
|
|
74
106
|
* option is palette-12, counted) — so a blind Enter never confirms filesystem
|
|
75
107
|
* trust. Region bounds reuse the shared rule-line/status patterns (agy brackets
|
|
76
108
|
* its composer with `─────` rules, like claude).
|
|
109
|
+
*
|
|
110
|
+
* The two marker ANCHORS (#1474) exist because `> ` alone does not identify agy's
|
|
111
|
+
* composer. Measured on real agy 1.1.13 captures (`fixtures/gate/agy-*.txt`), a live
|
|
112
|
+
* screen routinely carries OTHER `> ` rows, and last-match-wins picked them over the
|
|
113
|
+
* composer:
|
|
114
|
+
* - the slash-menu selection cursor (`> /add-dir …`) — palette-12 like the composer,
|
|
115
|
+
* and rendered BELOW it, so it won the marker scan outright;
|
|
116
|
+
* - the transcript echo of every submitted turn (`> <the message you sent>`), palette-4,
|
|
117
|
+
* one per conversation turn;
|
|
118
|
+
* - the trust dialog's selected option — palette-12, with no composer on screen at all.
|
|
119
|
+
* Hence `markerRequiresCursorRow: true` (only the live input row holds the cursor — menu
|
|
120
|
+
* and dialog rows never do) and `markerFgPalette: 12` (the marker glyph's own color, which
|
|
121
|
+
* separates the composer from the palette-4 transcript echo). Both were measured stable
|
|
122
|
+
* across agy's modes, including the bare-`>` no-hint mode.
|
|
123
|
+
*
|
|
124
|
+
* Direction of failure, deliberately: an agy re-theme, or a cursor parked off the composer
|
|
125
|
+
* (e.g. a single mid-repaint frame), yields NO marker ⇒ `no-composer-marker` ⇒ the message
|
|
126
|
+
* is held and re-checked, never delivered onto a screen we failed to understand. A
|
|
127
|
+
* sustained hold escalates through the mailbox-delivery liveness telemetry
|
|
128
|
+
* (`recordStreak`) — the same drift path {@link REGION_END_PATTERNS} documents — so widen
|
|
129
|
+
* these anchors ONLY from a real capture.
|
|
77
130
|
*/
|
|
78
131
|
export const AGY_PROFILE = {
|
|
79
132
|
app: 'agy',
|
|
80
133
|
markerPattern: AGY_MARKER,
|
|
81
134
|
regionEndPatterns: REGION_END_PATTERNS,
|
|
82
135
|
placeholderFgPalette: 8,
|
|
136
|
+
markerRequiresCursorRow: true,
|
|
137
|
+
markerFgPalette: 12,
|
|
138
|
+
// Issue #1664, set by OWNER RULING (2026-09-09) — **UNMEASURED**. Every other value of this
|
|
139
|
+
// field on this page came from driving the real TUI through the acceptance harness; this one
|
|
140
|
+
// did not. The owner's call is that mid-turn delivery is universal and only human input holds
|
|
141
|
+
// mail, so agy is granted the licence rather than waiting on a measurement.
|
|
142
|
+
//
|
|
143
|
+
// What that trades away, stated plainly so a future reader is not misled by the company this
|
|
144
|
+
// line keeps: if agy DROPS or mishandles input arriving mid-turn, a delivery lands as text in
|
|
145
|
+
// its composer that is never submitted — the body strands on the line, the classifier then
|
|
146
|
+
// reads it as `user-text`, and every later message to that agent queues behind it until a
|
|
147
|
+
// human clears the line. That is a stuck agent with a visible cause, not silent data loss, but
|
|
148
|
+
// it is a real failure mode and it is not ruled out by anything in this repository.
|
|
149
|
+
//
|
|
150
|
+
// Settling it is a harness run, not an argument:
|
|
151
|
+
// node --experimental-strip-types scripts/bugfix-1664-streaming-delivery-harness.mts \
|
|
152
|
+
// --harness agy --scenario streaming --trials 20 --assume-queues-input
|
|
153
|
+
// If that reports 20/20 delivered-intact and submitted, replace this comment with the measured
|
|
154
|
+
// one. If it does not, this line is what needs changing.
|
|
155
|
+
queuesInputMidTurn: true,
|
|
83
156
|
};
|
|
84
157
|
/** Registry keyed by the harness name `detectHarnessFromCommand` returns. */
|
|
85
158
|
const PROFILES_BY_HARNESS = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-profiles.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAC;AAEtE
|
|
1
|
+
{"version":3,"file":"gate-profiles.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/gate-profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,wBAAwB,CAAC,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,GAAG,EAAE,QAAQ;IACb,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,GAAG,EAAE,OAAO;IACZ,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,GAAG,EAAE,KAAK;IACV,aAAa,EAAE,UAAU;IACzB,iBAAiB,EAAE,mBAAmB;IACtC,oBAAoB,EAAE,CAAC;IACvB,uBAAuB,EAAE,IAAI;IAC7B,eAAe,EAAE,EAAE;IACnB,4FAA4F;IAC5F,6FAA6F;IAC7F,8FAA8F;IAC9F,4EAA4E;IAC5E,EAAE;IACF,6FAA6F;IAC7F,8FAA8F;IAC9F,2FAA2F;IAC3F,0FAA0F;IAC1F,+FAA+F;IAC/F,oFAAoF;IACpF,EAAE;IACF,iDAAiD;IACjD,yFAAyF;IACzF,2EAA2E;IAC3E,+FAA+F;IAC/F,yDAAyD;IACzD,kBAAkB,EAAE,IAAI;CACzB,CAAC;AAEF,6EAA6E;AAC7E,MAAM,mBAAmB,GAAgC;IACvD,MAAM,EAAE,cAAc;IACtB,KAAK,EAAE,aAAa;CACrB,CAAC;AAiBF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,WAAW,CAAC;IAC7E,MAAM,OAAO,GAAG,wBAAwB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,OAAO,IAAI,OAAO,IAAI,mBAAmB;QAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACnF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE prefix forward (Issue #1668).
|
|
3
|
+
*
|
|
4
|
+
* Tower forwards a fixed `/ide/` prefix to a single local VS Code server-web
|
|
5
|
+
* process — plain HTTP here and, for the workbench's own remote-protocol
|
|
6
|
+
* sockets, a raw bidirectional WebSocket pipe (Tower never parses those frames;
|
|
7
|
+
* they are VS Code's protocol, not Tower's — see `tower-websocket.ts` for the
|
|
8
|
+
* contrast with the PTY terminal bridge).
|
|
9
|
+
*
|
|
10
|
+
* Authorization is decided upstream by `isForwardAuthorized` (the HTTP path also
|
|
11
|
+
* passes the general `isRequestAllowed` choke point first); this module only
|
|
12
|
+
* proxies, after that decision. Header hygiene and `x-forwarded-*` stamping
|
|
13
|
+
* happen here, strictly after auth (Issue #1668 §D2).
|
|
14
|
+
*/
|
|
15
|
+
import http from 'node:http';
|
|
16
|
+
import type net from 'node:net';
|
|
17
|
+
/** The tunnel's public authority, for stamping `x-forwarded-*` on tunnel-borne requests. */
|
|
18
|
+
export interface PublicAuthority {
|
|
19
|
+
host: string;
|
|
20
|
+
proto: string;
|
|
21
|
+
prefix: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Merge the IDE connection token into a Cookie header value as `vscode-tkn=…`,
|
|
25
|
+
* preserving every existing cookie and never clobbering an already-present
|
|
26
|
+
* `vscode-tkn` (Issue #1668). Exported for testing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeConnectionTokenCookie(existing: string | string[] | undefined, token: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Derive the tunnel's public authority from the cloud config, matching the
|
|
31
|
+
* `accessUrl` Tower advertises (`tower-tunnel.ts`: `${server_url}/t/${tower_name}/`).
|
|
32
|
+
* Returns null when the tower is not cloud-registered.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getPublicAuthority(): PublicAuthority | null;
|
|
35
|
+
/**
|
|
36
|
+
* Build the header set forwarded to the IDE server: drop hop-by-hop and
|
|
37
|
+
* Tower-internal headers, set the loopback `Host`, and — only when tunnel-borne
|
|
38
|
+
* — stamp `x-forwarded-host` / `x-forwarded-proto` / `x-forwarded-prefix` from
|
|
39
|
+
* the public authority so the workbench builds correct `remoteAuthority` and
|
|
40
|
+
* base-path URLs and never sees a stale upstream port. Response caching headers
|
|
41
|
+
* (`Cache-Control` / `ETag`) are a RESPONSE concern and preserved separately.
|
|
42
|
+
*
|
|
43
|
+
* Pure and exported for unit testing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildForwardHeaders(incoming: http.IncomingHttpHeaders, port: number, publicAuthority: PublicAuthority | null, connectionToken: string | null): Record<string, string | string[]>;
|
|
46
|
+
/** True if `pathname` targets the IDE forward prefix (`/ide` or `/ide/…`). */
|
|
47
|
+
export declare function matchIdePrefix(pathname: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Forward a plain HTTP request to the local IDE server. Assumes authorization
|
|
50
|
+
* has already passed. Path and query are forwarded unchanged (the workbench
|
|
51
|
+
* runs with `--server-base-path /ide` and selects the workspace from `?folder=`).
|
|
52
|
+
* A dead / unreachable server yields 502, never a crash.
|
|
53
|
+
*/
|
|
54
|
+
export declare function forwardIdeHttp(req: http.IncomingMessage, res: http.ServerResponse, log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Forward a WebSocket upgrade to the local IDE server as a raw bidirectional
|
|
57
|
+
* byte pipe. Tower authenticates the upgrade (caller) and then gets out of the
|
|
58
|
+
* way — the workbench speaks VS Code's own remote protocol, which Tower must not
|
|
59
|
+
* parse. Mirrors `tunnel-client.ts handleWebSocketConnect`'s pipe.
|
|
60
|
+
*/
|
|
61
|
+
export declare function forwardIdeWebSocket(req: http.IncomingMessage, socket: net.Socket, head: Buffer, log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void): Promise<void>;
|
|
62
|
+
//# sourceMappingURL=ide-forward.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ide-forward.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-forward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAuChC,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAa3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,eAAe,GAAG,IAAI,EACvC,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CA0BnC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGxD;AAaD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/D,OAAO,CAAC,IAAI,CAAC,CAmCf;AAaD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,MAAM,EAAE,GAAG,CAAC,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/D,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE prefix forward (Issue #1668).
|
|
3
|
+
*
|
|
4
|
+
* Tower forwards a fixed `/ide/` prefix to a single local VS Code server-web
|
|
5
|
+
* process — plain HTTP here and, for the workbench's own remote-protocol
|
|
6
|
+
* sockets, a raw bidirectional WebSocket pipe (Tower never parses those frames;
|
|
7
|
+
* they are VS Code's protocol, not Tower's — see `tower-websocket.ts` for the
|
|
8
|
+
* contrast with the PTY terminal bridge).
|
|
9
|
+
*
|
|
10
|
+
* Authorization is decided upstream by `isForwardAuthorized` (the HTTP path also
|
|
11
|
+
* passes the general `isRequestAllowed` choke point first); this module only
|
|
12
|
+
* proxies, after that decision. Header hygiene and `x-forwarded-*` stamping
|
|
13
|
+
* happen here, strictly after auth (Issue #1668 §D2).
|
|
14
|
+
*/
|
|
15
|
+
import http from 'node:http';
|
|
16
|
+
import { URL } from 'node:url';
|
|
17
|
+
import { readCloudConfig } from '../lib/cloud-config.js';
|
|
18
|
+
import { TUNNEL_PROXY_HEADER } from '../lib/tunnel-client.js';
|
|
19
|
+
import { TOWER_KEY_HEADER, LEGACY_WEB_KEY_HEADER } from '@cluesmith/codev-types';
|
|
20
|
+
import { IDE_DEFAULT_PREFIX, readIdeConnectionToken } from '../lib/ide-record.js';
|
|
21
|
+
import { getRecordedIdePort, ensureIdeServerLive } from './ide-server.js';
|
|
22
|
+
/** Hop-by-hop headers not forwarded between connections (RFC 7230 §6.1). */
|
|
23
|
+
const HOP_BY_HOP = new Set([
|
|
24
|
+
'connection',
|
|
25
|
+
'keep-alive',
|
|
26
|
+
'proxy-authenticate',
|
|
27
|
+
'proxy-authorization',
|
|
28
|
+
'te',
|
|
29
|
+
'trailers',
|
|
30
|
+
'transfer-encoding',
|
|
31
|
+
'upgrade',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Headers stripped before forwarding to the IDE server:
|
|
35
|
+
* - `x-forwarded-port` / `x-original-host`: a leaked upstream port makes the
|
|
36
|
+
* browser dial the wrong WebSocket port, breaking the live session.
|
|
37
|
+
* - `codev-tower-key` / `codev-web-key`: Tower's auth credential — the IDE
|
|
38
|
+
* server must never see it.
|
|
39
|
+
* - `x-codev-tunnel-proxy`: Tower's tunnel marker — never forwarded, so it
|
|
40
|
+
* cannot leak or be trusted downstream (marker integrity, #1674).
|
|
41
|
+
* - `host`: replaced with the loopback target authority below.
|
|
42
|
+
*/
|
|
43
|
+
const STRIP_BEFORE_FORWARD = new Set([
|
|
44
|
+
'x-forwarded-port',
|
|
45
|
+
'x-original-host',
|
|
46
|
+
TOWER_KEY_HEADER,
|
|
47
|
+
LEGACY_WEB_KEY_HEADER,
|
|
48
|
+
TUNNEL_PROXY_HEADER,
|
|
49
|
+
'host',
|
|
50
|
+
]);
|
|
51
|
+
/** True if this request arrived over the tunnel (stamped by `TunnelClient`, #1588). */
|
|
52
|
+
function isTunnelBorne(headers) {
|
|
53
|
+
return headers[TUNNEL_PROXY_HEADER] === '1';
|
|
54
|
+
}
|
|
55
|
+
/** The connection-token cookie name the VS Code server reads. */
|
|
56
|
+
const IDE_TOKEN_COOKIE = 'vscode-tkn';
|
|
57
|
+
/**
|
|
58
|
+
* Merge the IDE connection token into a Cookie header value as `vscode-tkn=…`,
|
|
59
|
+
* preserving every existing cookie and never clobbering an already-present
|
|
60
|
+
* `vscode-tkn` (Issue #1668). Exported for testing.
|
|
61
|
+
*/
|
|
62
|
+
export function mergeConnectionTokenCookie(existing, token) {
|
|
63
|
+
const current = Array.isArray(existing) ? existing.join('; ') : existing ?? '';
|
|
64
|
+
// Never clobber: if a vscode-tkn is already present, leave the header as-is.
|
|
65
|
+
if (new RegExp(`(^|;\\s*)${IDE_TOKEN_COOKIE}=`).test(current))
|
|
66
|
+
return current;
|
|
67
|
+
const pair = `${IDE_TOKEN_COOKIE}=${token}`;
|
|
68
|
+
return current ? `${current}; ${pair}` : pair;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Derive the tunnel's public authority from the cloud config, matching the
|
|
72
|
+
* `accessUrl` Tower advertises (`tower-tunnel.ts`: `${server_url}/t/${tower_name}/`).
|
|
73
|
+
* Returns null when the tower is not cloud-registered.
|
|
74
|
+
*/
|
|
75
|
+
export function getPublicAuthority() {
|
|
76
|
+
const cfg = readCloudConfig();
|
|
77
|
+
if (!cfg)
|
|
78
|
+
return null;
|
|
79
|
+
let host;
|
|
80
|
+
let proto;
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(cfg.server_url);
|
|
83
|
+
host = url.host;
|
|
84
|
+
proto = url.protocol === 'http:' ? 'http' : 'https';
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return { host, proto, prefix: `/t/${cfg.tower_name}${IDE_DEFAULT_PREFIX.replace(/\/$/, '')}` };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the header set forwarded to the IDE server: drop hop-by-hop and
|
|
93
|
+
* Tower-internal headers, set the loopback `Host`, and — only when tunnel-borne
|
|
94
|
+
* — stamp `x-forwarded-host` / `x-forwarded-proto` / `x-forwarded-prefix` from
|
|
95
|
+
* the public authority so the workbench builds correct `remoteAuthority` and
|
|
96
|
+
* base-path URLs and never sees a stale upstream port. Response caching headers
|
|
97
|
+
* (`Cache-Control` / `ETag`) are a RESPONSE concern and preserved separately.
|
|
98
|
+
*
|
|
99
|
+
* Pure and exported for unit testing.
|
|
100
|
+
*/
|
|
101
|
+
export function buildForwardHeaders(incoming, port, publicAuthority, connectionToken) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const lower = key.toLowerCase();
|
|
107
|
+
if (HOP_BY_HOP.has(lower))
|
|
108
|
+
continue;
|
|
109
|
+
if (STRIP_BEFORE_FORWARD.has(lower))
|
|
110
|
+
continue;
|
|
111
|
+
out[key] = value;
|
|
112
|
+
}
|
|
113
|
+
out['host'] = `127.0.0.1:${port}`;
|
|
114
|
+
if (isTunnelBorne(incoming) && publicAuthority) {
|
|
115
|
+
out['x-forwarded-host'] = publicAuthority.host;
|
|
116
|
+
out['x-forwarded-proto'] = publicAuthority.proto;
|
|
117
|
+
out['x-forwarded-prefix'] = publicAuthority.prefix;
|
|
118
|
+
}
|
|
119
|
+
// Present the IDE server's connection token as a cookie. The server is
|
|
120
|
+
// spawned with `--connection-token-file` and 403s without it, and it cannot
|
|
121
|
+
// ride the browser's cookie jar through the relay (Set-Cookie Path=/ide never
|
|
122
|
+
// matches /t/<tower>/ide/), so Tower injects it here — merging with, never
|
|
123
|
+
// clobbering, any existing Cookie header (Issue #1668).
|
|
124
|
+
if (connectionToken) {
|
|
125
|
+
out['cookie'] = mergeConnectionTokenCookie(incoming.cookie, connectionToken);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/** True if `pathname` targets the IDE forward prefix (`/ide` or `/ide/…`). */
|
|
130
|
+
export function matchIdePrefix(pathname) {
|
|
131
|
+
const bare = IDE_DEFAULT_PREFIX.replace(/\/$/, ''); // "/ide"
|
|
132
|
+
return pathname === bare || pathname.startsWith(IDE_DEFAULT_PREFIX);
|
|
133
|
+
}
|
|
134
|
+
/** Copy response headers, dropping hop-by-hop; preserves `Cache-Control` / `ETag`. */
|
|
135
|
+
function copyResponseHeaders(headers) {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
138
|
+
if (value === undefined)
|
|
139
|
+
continue;
|
|
140
|
+
if (HOP_BY_HOP.has(key.toLowerCase()))
|
|
141
|
+
continue;
|
|
142
|
+
out[key] = value;
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Forward a plain HTTP request to the local IDE server. Assumes authorization
|
|
148
|
+
* has already passed. Path and query are forwarded unchanged (the workbench
|
|
149
|
+
* runs with `--server-base-path /ide` and selects the workspace from `?folder=`).
|
|
150
|
+
* A dead / unreachable server yields 502, never a crash.
|
|
151
|
+
*/
|
|
152
|
+
export async function forwardIdeHttp(req, res, log) {
|
|
153
|
+
// Hot path: resolve the target port from the record — no port scan. A dead
|
|
154
|
+
// server fails the connect below, which triggers a respawn (Issue #1668).
|
|
155
|
+
const port = getRecordedIdePort();
|
|
156
|
+
if (port === null) {
|
|
157
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
158
|
+
res.end(JSON.stringify({ error: 'IDE server unavailable' }));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const headers = buildForwardHeaders(req.headers, port, getPublicAuthority(), readIdeConnectionToken());
|
|
162
|
+
const proxyReq = http.request({ hostname: '127.0.0.1', port, path: req.url || '/', method: req.method, headers }, (proxyRes) => {
|
|
163
|
+
res.writeHead(proxyRes.statusCode ?? 502, copyResponseHeaders(proxyRes.headers));
|
|
164
|
+
proxyRes.pipe(res);
|
|
165
|
+
proxyRes.on('error', () => {
|
|
166
|
+
if (!res.writableEnded)
|
|
167
|
+
res.end();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
proxyReq.on('error', (err) => {
|
|
171
|
+
log('WARN', `IDE HTTP forward error: ${err.message}`);
|
|
172
|
+
onUpstreamConnectError(err, log);
|
|
173
|
+
if (!res.headersSent) {
|
|
174
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
175
|
+
res.end(JSON.stringify({ error: 'Bad Gateway: IDE server unavailable' }));
|
|
176
|
+
}
|
|
177
|
+
else if (!res.writableEnded) {
|
|
178
|
+
res.end();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
req.pipe(proxyReq);
|
|
182
|
+
req.on('error', () => proxyReq.destroy());
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* On a refused/reset upstream connection the recorded server is down. Kick a
|
|
186
|
+
* respawn off the hot path (fire-and-forget) so the next request lands; this
|
|
187
|
+
* request still 502s. The respawn (cold path) is where the `lsof` probe lives.
|
|
188
|
+
*/
|
|
189
|
+
function onUpstreamConnectError(err, log) {
|
|
190
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') {
|
|
191
|
+
ensureIdeServerLive(log).catch(() => { });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Forward a WebSocket upgrade to the local IDE server as a raw bidirectional
|
|
196
|
+
* byte pipe. Tower authenticates the upgrade (caller) and then gets out of the
|
|
197
|
+
* way — the workbench speaks VS Code's own remote protocol, which Tower must not
|
|
198
|
+
* parse. Mirrors `tunnel-client.ts handleWebSocketConnect`'s pipe.
|
|
199
|
+
*/
|
|
200
|
+
export async function forwardIdeWebSocket(req, socket, head, log) {
|
|
201
|
+
const port = getRecordedIdePort();
|
|
202
|
+
if (port === null) {
|
|
203
|
+
socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
|
|
204
|
+
socket.destroy();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Header hygiene drops the hop-by-hop `Connection`/`Upgrade` pair, so re-add
|
|
208
|
+
// them for the upstream upgrade (as `tunnel-client.ts` does). The client's
|
|
209
|
+
// `Sec-WebSocket-Key` is preserved by `buildForwardHeaders` and forwarded as-is
|
|
210
|
+
// so the upstream's `Sec-WebSocket-Accept` — relayed back verbatim — validates
|
|
211
|
+
// against the client's own key.
|
|
212
|
+
const headers = buildForwardHeaders(req.headers, port, getPublicAuthority(), readIdeConnectionToken());
|
|
213
|
+
headers['Connection'] = 'Upgrade';
|
|
214
|
+
headers['Upgrade'] = 'websocket';
|
|
215
|
+
const upstream = http.request({
|
|
216
|
+
hostname: '127.0.0.1',
|
|
217
|
+
port,
|
|
218
|
+
path: req.url || '/',
|
|
219
|
+
method: 'GET',
|
|
220
|
+
headers,
|
|
221
|
+
});
|
|
222
|
+
upstream.on('upgrade', (_upstreamRes, upstreamSocket, upstreamHead) => {
|
|
223
|
+
// Replay the handshake response and any buffered bytes, then pipe both ways.
|
|
224
|
+
socket.write(rebuildUpgradeResponse(_upstreamRes));
|
|
225
|
+
if (upstreamHead.length > 0)
|
|
226
|
+
socket.write(upstreamHead);
|
|
227
|
+
if (head.length > 0)
|
|
228
|
+
upstreamSocket.write(head);
|
|
229
|
+
upstreamSocket.pipe(socket);
|
|
230
|
+
socket.pipe(upstreamSocket);
|
|
231
|
+
const closeBoth = () => {
|
|
232
|
+
if (!socket.destroyed)
|
|
233
|
+
socket.destroy();
|
|
234
|
+
if (!upstreamSocket.destroyed)
|
|
235
|
+
upstreamSocket.destroy();
|
|
236
|
+
};
|
|
237
|
+
socket.on('error', closeBoth);
|
|
238
|
+
upstreamSocket.on('error', closeBoth);
|
|
239
|
+
socket.on('close', closeBoth);
|
|
240
|
+
upstreamSocket.on('close', closeBoth);
|
|
241
|
+
});
|
|
242
|
+
upstream.on('response', (upstreamRes) => {
|
|
243
|
+
// Upstream declined the upgrade (e.g. 404) — relay the status and close.
|
|
244
|
+
socket.write(`HTTP/1.1 ${upstreamRes.statusCode ?? 502} ${upstreamRes.statusMessage ?? ''}\r\n\r\n`);
|
|
245
|
+
socket.destroy();
|
|
246
|
+
});
|
|
247
|
+
upstream.on('error', (err) => {
|
|
248
|
+
log('WARN', `IDE WS forward error: ${err.message}`);
|
|
249
|
+
onUpstreamConnectError(err, log);
|
|
250
|
+
if (!socket.destroyed) {
|
|
251
|
+
socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
|
|
252
|
+
socket.destroy();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
upstream.end();
|
|
256
|
+
}
|
|
257
|
+
/** Reconstruct the HTTP/1.1 101 switching-protocols response line + headers. */
|
|
258
|
+
function rebuildUpgradeResponse(res) {
|
|
259
|
+
const statusLine = `HTTP/1.1 ${res.statusCode ?? 101} ${res.statusMessage ?? 'Switching Protocols'}`;
|
|
260
|
+
const lines = [statusLine];
|
|
261
|
+
for (const [key, value] of Object.entries(res.headers)) {
|
|
262
|
+
if (value === undefined)
|
|
263
|
+
continue;
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
for (const v of value)
|
|
266
|
+
lines.push(`${key}: ${v}`);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
lines.push(`${key}: ${value}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return lines.join('\r\n') + '\r\n\r\n';
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=ide-forward.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ide-forward.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-forward.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE server lifecycle, Tower-side (Issue #1668).
|
|
3
|
+
*
|
|
4
|
+
* Tower is the spawner: `afx ide start` drives this through the local-only
|
|
5
|
+
* `/api/ide` endpoint, and the same spawn path is reused by boot reconcile and
|
|
6
|
+
* by on-demand respawn when a `/ide/` request arrives and the server is down.
|
|
7
|
+
* Runtime state lives in the `~/.agent-farm/ide-server.json` record (Issue
|
|
8
|
+
* #1668 §D3) — no config file, no `global.db` change. Liveness is discovered by
|
|
9
|
+
* port (`getProcessesOnPort`), the same way Tower finds its own process.
|
|
10
|
+
*/
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import { type IdeServerRecord } from '../lib/ide-record.js';
|
|
13
|
+
type LogFn = (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* The recorded IDE server port, or null if none — the forward's hot-path target
|
|
16
|
+
* resolution. A plain record read, no port scan (Issue #1668): a dead server
|
|
17
|
+
* fails the proxy connect immediately, which is the only liveness check the
|
|
18
|
+
* forward needs.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getRecordedIdePort(): number | null;
|
|
21
|
+
export interface SpawnIdeOptions {
|
|
22
|
+
serverPath: string;
|
|
23
|
+
port?: number;
|
|
24
|
+
defaultFolder?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Spawn the IDE server detached (so it survives a Tower restart, like a
|
|
28
|
+
* shellper) and write the runtime record. Resolves to the record once the
|
|
29
|
+
* server is listening, or throws if the binary is missing or never binds.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spawnIdeServer(opts: SpawnIdeOptions, log: LogFn): Promise<IdeServerRecord>;
|
|
32
|
+
/**
|
|
33
|
+
* Stop the IDE server on the recorded port: SIGTERM, wait for exit, escalate to
|
|
34
|
+
* SIGKILL for survivors (mirrors `towerStop`, #1668 plan §4), then delete the
|
|
35
|
+
* record. Cold path — `getProcessesOnPort` is fine here.
|
|
36
|
+
*/
|
|
37
|
+
export declare function stopIdeServer(log: LogFn): Promise<{
|
|
38
|
+
stopped: boolean;
|
|
39
|
+
port: number | null;
|
|
40
|
+
}>;
|
|
41
|
+
export interface IdeStatus {
|
|
42
|
+
record: IdeServerRecord | null;
|
|
43
|
+
alive: boolean;
|
|
44
|
+
pid: number | null;
|
|
45
|
+
}
|
|
46
|
+
/** Report the IDE server's status from the record plus a live port probe. */
|
|
47
|
+
export declare function ideServerStatus(): IdeStatus;
|
|
48
|
+
/**
|
|
49
|
+
* Ensure the IDE server is live and return its port, or null if it cannot be
|
|
50
|
+
* made live (no record, or a respawn that never bound). A live server is the
|
|
51
|
+
* fast path (no spawn). A recorded-but-dead server is respawned from the
|
|
52
|
+
* recorded `serverPath`; concurrent callers share one in-flight respawn.
|
|
53
|
+
*/
|
|
54
|
+
export declare function ensureIdeServerLive(log?: LogFn): Promise<number | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Boot-time reconcile: adopt a live server (no orphan) or respawn a recorded
|
|
57
|
+
* but dead one. Best-effort — never throws into Tower boot.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reconcileIdeServer(log: LogFn): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Local-only `/api/ide` management endpoint driving the lifecycle above:
|
|
62
|
+
* `POST` starts (body `{ serverPath, port?, defaultFolder? }`), `DELETE` stops,
|
|
63
|
+
* `GET` reports status. Reached only post-`isRequestAllowed` (key required) and
|
|
64
|
+
* blocked from the tunnel like `/api/tunnel/*` — it spawns/kills a process.
|
|
65
|
+
*/
|
|
66
|
+
export declare function handleIdeApi(req: http.IncomingMessage, res: http.ServerResponse, log: LogFn): Promise<void>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=ide-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ide-server.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/ide-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,OAAO,EAGL,KAAK,eAAe,EAMrB,MAAM,sBAAsB,CAAC;AAE9B,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AA4BzE;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAElD;AAYD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CA6EhG;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAkClG;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,SAAS,CAK3C;AAKD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,GAAE,KAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBvF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,GAAG,EAAE,KAAK,GACT,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
|