@bastani/atomic 0.5.24-0 → 0.5.25-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/dist/sdk/components/attached-statusline.d.ts +17 -4
- package/dist/sdk/components/attached-statusline.d.ts.map +1 -1
- package/dist/sdk/components/graph-theme.d.ts +1 -0
- package/dist/sdk/components/graph-theme.d.ts.map +1 -1
- package/dist/sdk/components/statusline.d.ts.map +1 -1
- package/dist/sdk/providers/claude.d.ts.map +1 -1
- package/dist/sdk/runtime/attached-footer.d.ts +17 -0
- package/dist/sdk/runtime/attached-footer.d.ts.map +1 -0
- package/dist/sdk/runtime/cc-debounce.d.ts +29 -0
- package/dist/sdk/runtime/cc-debounce.d.ts.map +1 -0
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/runtime/theme.d.ts +1 -0
- package/dist/sdk/runtime/theme.d.ts.map +1 -1
- package/dist/sdk/runtime/tmux.d.ts +10 -45
- package/dist/sdk/runtime/tmux.d.ts.map +1 -1
- package/dist/sdk/workflows/index.d.ts +1 -1
- package/dist/sdk/workflows/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +5 -2
- package/src/commands/cli/chat/index.ts +8 -1
- package/src/commands/cli/footer.tsx +18 -4
- package/src/sdk/components/attached-statusline.tsx +58 -5
- package/src/sdk/components/connectors.test.ts +1 -0
- package/src/sdk/components/graph-theme.ts +2 -0
- package/src/sdk/components/statusline.tsx +3 -0
- package/src/sdk/providers/claude.ts +7 -10
- package/src/sdk/runtime/attached-footer.ts +66 -0
- package/src/sdk/runtime/cc-debounce.ts +104 -0
- package/src/sdk/runtime/executor.ts +1 -28
- package/src/sdk/runtime/theme.ts +3 -0
- package/src/sdk/runtime/tmux.conf +21 -16
- package/src/sdk/runtime/tmux.ts +26 -179
- package/src/sdk/workflows/index.ts +0 -7
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/react */
|
|
2
2
|
/**
|
|
3
3
|
* Footer rendered inside each agent tmux window. Lives in a 1-row bottom
|
|
4
|
-
* pane created by the executor
|
|
5
|
-
* the orchestrator Statusline style
|
|
6
|
-
*
|
|
4
|
+
* pane created by the executor (workflow) or the chat command, spawned via
|
|
5
|
+
* `atomic _footer`. Mirrors the orchestrator Statusline style: a colored
|
|
6
|
+
* pill on the left and right-aligned context on the right.
|
|
7
|
+
*
|
|
8
|
+
* Two variants:
|
|
9
|
+
* - Workflow: the window name is the left badge; right side shows the
|
|
10
|
+
* navigation hints (ctrl+g graph · ctrl+\ next). The `ctrl+b d
|
|
11
|
+
* detach` hint is surfaced in the orchestrator-window Statusline
|
|
12
|
+
* only, not duplicated into every agent pane footer.
|
|
13
|
+
* - Chat (agentType set): the provider name becomes the left pill
|
|
14
|
+
* (CLAUDE / COPILOT / OPENCODE, colored to match the workflow
|
|
15
|
+
* picker); right side shows pane name · ctrl+b d detach
|
|
16
|
+
* (tmux's default detach binding — spelled out because many Atomic
|
|
17
|
+
* users have never used tmux directly).
|
|
7
18
|
*/
|
|
19
|
+
import type { AgentType } from "../types.ts";
|
|
8
20
|
import type { GraphTheme } from "./graph-theme.ts";
|
|
9
|
-
export declare function AttachedStatusline({ name, theme }: {
|
|
21
|
+
export declare function AttachedStatusline({ name, theme, agentType, }: {
|
|
10
22
|
name: string;
|
|
11
23
|
theme: GraphTheme;
|
|
24
|
+
agentType?: AgentType;
|
|
12
25
|
}): import("react").ReactNode;
|
|
13
26
|
//# sourceMappingURL=attached-statusline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attached-statusline.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/attached-statusline.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC
|
|
1
|
+
{"version":3,"file":"attached-statusline.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/attached-statusline.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EACL,SAAS,GACV,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB,6BA8CA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-theme.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/graph-theme.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"graph-theme.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/graph-theme.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,GAAG,UAAU,CAgB7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/statusline.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AAItC,wBAAgB,UAAU,CAAC,EACzB,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;CACnB,
|
|
1
|
+
{"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../../../src/sdk/components/statusline.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AAItC,wBAAgB,UAAU,CAAC,EACzB,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;CACnB,6BAuDA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/sdk/providers/claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,OAAO,IAAI,UAAU,EAC3B,MAAM,gCAAgC,CAAC;AAgCxC;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtE;AA+GD,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,sIAAsI;IACtI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAexF;
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/sdk/providers/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper for spawning the attached `atomic _footer` pane inside an agent
|
|
3
|
+
* tmux window.
|
|
4
|
+
*
|
|
5
|
+
* Shared between the workflow executor (per-agent windows) and the chat
|
|
6
|
+
* command (single-agent window). Splits the target pane vertically so the
|
|
7
|
+
* top pane keeps running the agent CLI and the bottom pane hosts the
|
|
8
|
+
* React footer.
|
|
9
|
+
*
|
|
10
|
+
* Resolves the CLI entrypoint relative to this module (runtime/ lives at
|
|
11
|
+
* src/sdk/runtime/, so ../../cli.ts is the CLI). `process.argv[1]` points
|
|
12
|
+
* at the orchestrator's executor-entry.ts when called from the executor,
|
|
13
|
+
* so it can't be used here.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentType } from "../types.ts";
|
|
16
|
+
export declare function spawnAttachedFooter(windowName: string, paneId: string, agentType?: AgentType): void;
|
|
17
|
+
//# sourceMappingURL=attached-footer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attached-footer.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/attached-footer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAiB7C,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,SAAS,GACpB,IAAI,CA4BN"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Ctrl+C debounce helper for Atomic-managed tmux panes.
|
|
4
|
+
*
|
|
5
|
+
* Invoked from `tmux.conf` on every root-table Ctrl+C:
|
|
6
|
+
*
|
|
7
|
+
* bind -n C-c run-shell -b '"#{@atomic-bun}" "#{@atomic-cc-debounce}" "#{pane_id}"'
|
|
8
|
+
*
|
|
9
|
+
* The binding sits on the shared atomic tmux server, so the debounce
|
|
10
|
+
* applies uniformly to every pane — Claude Code, OpenCode, and Copilot
|
|
11
|
+
* CLI, in both chat and workflow sessions.
|
|
12
|
+
*
|
|
13
|
+
* Rule: forward Ctrl+C only if the previous press is more than QUIET_MS
|
|
14
|
+
* ago. The state file is touched on *every* press (forwarded or
|
|
15
|
+
* swallowed) so sustained spam keeps extending the cooldown instead of
|
|
16
|
+
* letting a press leak through every QUIET_MS interval — which is what
|
|
17
|
+
* would otherwise still trigger an agent CLI's "double-tap to exit"
|
|
18
|
+
* confirmation.
|
|
19
|
+
*/
|
|
20
|
+
/** Quiet period (ms) the user must leave between presses for the next
|
|
21
|
+
* one to be forwarded. Must exceed every integrated agent's exit-confirm
|
|
22
|
+
* window — Claude Code's is the widest (~1.5 s), so 1200 ms is a safe
|
|
23
|
+
* margin that still feels responsive for legitimate double-interrupts. */
|
|
24
|
+
export declare const QUIET_MS = 1200;
|
|
25
|
+
/** Pure decision helper — exported so tests can exercise it without
|
|
26
|
+
* touching the filesystem or spawning tmux. Returns `true` when the
|
|
27
|
+
* press should be forwarded, `false` when it should be swallowed. */
|
|
28
|
+
export declare function shouldForward(nowMs: number, lastMs: number, quietMs?: number): boolean;
|
|
29
|
+
//# sourceMappingURL=cc-debounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cc-debounce.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/cc-debounce.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAMH;;;2EAG2E;AAC3E,eAAO,MAAM,QAAQ,OAAO,CAAC;AAE7B;;sEAEsE;AACtE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,MAAiB,GACzB,OAAO,CAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EACV,kBAAkB,EAMlB,SAAS,EAET,YAAY,EAMb,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM/C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM/C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AA4CD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,aAAa,CAElE"}
|
|
@@ -82,6 +82,16 @@ export declare function createWindow(sessionName: string, windowName: string, co
|
|
|
82
82
|
* @returns The pane ID of the new pane
|
|
83
83
|
*/
|
|
84
84
|
export declare function createPane(sessionName: string, command: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Replace the running command in an existing pane with a new one.
|
|
87
|
+
*
|
|
88
|
+
* `-k` kills whatever is currently running in the pane (e.g. a still-initializing
|
|
89
|
+
* shell) before tmux spawns the new command. Because tmux execs the command
|
|
90
|
+
* itself rather than forwarding keystrokes through a shell line editor, there
|
|
91
|
+
* is no shell-ready race and no ZLE TCSAFLUSH drop — callers can invoke this
|
|
92
|
+
* immediately after pane creation without waiting for a prompt to appear.
|
|
93
|
+
*/
|
|
94
|
+
export declare function respawnPane(paneId: string, command: string): void;
|
|
85
95
|
/**
|
|
86
96
|
* Send literal text to a tmux pane using `-l` flag (no special key interpretation).
|
|
87
97
|
* Uses `--` to prevent text starting with `-` from being parsed as flags.
|
|
@@ -106,14 +116,6 @@ export declare function sendViaPasteBuffer(paneId: string, text: string): void;
|
|
|
106
116
|
* Send a special key (C-m, C-c, C-u, Tab, etc.) to a tmux pane.
|
|
107
117
|
*/
|
|
108
118
|
export declare function sendSpecialKey(paneId: string, key: string): void;
|
|
109
|
-
/**
|
|
110
|
-
* Send literal text and submit with C-m (carriage return).
|
|
111
|
-
* Uses C-m instead of Enter for raw-mode TUI compatibility.
|
|
112
|
-
*
|
|
113
|
-
* @param presses - Number of C-m presses (default: 1)
|
|
114
|
-
* @param delayMs - Delay between presses in ms (default: 100)
|
|
115
|
-
*/
|
|
116
|
-
export declare function sendKeysAndSubmit(paneId: string, text: string, presses?: number, delayMs?: number): Promise<void>;
|
|
117
119
|
/**
|
|
118
120
|
* Capture the visible content of a tmux pane.
|
|
119
121
|
*
|
|
@@ -248,41 +250,4 @@ export declare function normalizeTmuxCapture(text: string): string;
|
|
|
248
250
|
* Normalize captured text preserving line structure (for display output).
|
|
249
251
|
*/
|
|
250
252
|
export declare function normalizeTmuxLines(text: string): string;
|
|
251
|
-
/**
|
|
252
|
-
* Returns true when the pane shows an agent prompt ready for input.
|
|
253
|
-
* Detects Claude Code (❯), Codex (›), and generic (>) prompts.
|
|
254
|
-
*/
|
|
255
|
-
export declare function paneLooksReady(captured: string): boolean;
|
|
256
|
-
/**
|
|
257
|
-
* Returns true when the agent has an active task in progress.
|
|
258
|
-
* Checks last 40 lines for known busy indicators.
|
|
259
|
-
*/
|
|
260
|
-
export declare function paneHasActiveTask(captured: string): boolean;
|
|
261
|
-
/**
|
|
262
|
-
* Returns true when the pane is idle — showing a prompt and not processing.
|
|
263
|
-
* Uses visible-only capture to avoid stale scrollback matches.
|
|
264
|
-
*/
|
|
265
|
-
export declare function paneIsIdle(paneId: string): boolean;
|
|
266
|
-
/**
|
|
267
|
-
* Wait for the pane to be idle (prompt visible, no active task) with
|
|
268
|
-
* exponential backoff. Returns the time spent waiting (ms).
|
|
269
|
-
*/
|
|
270
|
-
export declare function waitForPaneReady(paneId: string, timeoutMs?: number): Promise<number>;
|
|
271
|
-
/**
|
|
272
|
-
* Attempt to submit by pressing C-m, verifying after each round.
|
|
273
|
-
* Returns true as soon as the trigger text disappears from the visible
|
|
274
|
-
* capture or an active task is detected.
|
|
275
|
-
*/
|
|
276
|
-
export declare function attemptSubmitRounds(paneId: string, normalizedPrompt: string, rounds: number, pressesPerRound?: number): Promise<boolean>;
|
|
277
|
-
/**
|
|
278
|
-
* Wait for a pattern to appear in a tmux pane's output.
|
|
279
|
-
* Polls the pane content at the given interval until the pattern matches
|
|
280
|
-
* or the timeout is reached.
|
|
281
|
-
*
|
|
282
|
-
* @returns The full pane content when the pattern was found
|
|
283
|
-
*/
|
|
284
|
-
export declare function waitForOutput(paneId: string, pattern: RegExp, options?: {
|
|
285
|
-
timeoutMs?: number;
|
|
286
|
-
pollIntervalMs?: number;
|
|
287
|
-
}): Promise<string>;
|
|
288
253
|
//# sourceMappingURL=tmux.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/tmux.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAMtC,4FAA4F;AAC5F,eAAO,MAAM,WAAW,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"tmux.d.ts","sourceRoot":"","sources":["../../../src/sdk/runtime/tmux.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ export type { ClaudeSessionOptions, ClaudeQueryOptions } from "../providers/clau
|
|
|
15
15
|
export { validateCopilotWorkflow } from "../providers/copilot.ts";
|
|
16
16
|
export { validateOpenCodeWorkflow } from "../providers/opencode.ts";
|
|
17
17
|
export type { TmuxResult, TmuxSession, SessionType } from "../runtime/tmux.ts";
|
|
18
|
-
export { SOCKET_NAME, isTmuxInstalled, getMuxBinary, resetMuxBinaryCache, isInsideTmux, isInsideAtomicSocket, createSession, createWindow, createPane, sendLiteralText, sendSpecialKey,
|
|
18
|
+
export { SOCKET_NAME, isTmuxInstalled, getMuxBinary, resetMuxBinaryCache, isInsideTmux, isInsideAtomicSocket, createSession, createWindow, createPane, sendLiteralText, sendSpecialKey, capturePane, capturePaneVisible, capturePaneScrollback, killSession, killWindow, sessionExists, listSessions, attachSession, spawnMuxAttach, switchClient, getCurrentSession, attachOrSwitch, detachAndAttachAtomic, selectWindow, setSessionEnv, getSessionEnv, parseSessionName, tmuxRun, normalizeTmuxCapture, normalizeTmuxLines, } from "../runtime/tmux.ts";
|
|
19
19
|
export { AGENTS, discoverWorkflows, findWorkflow, loadWorkflowsMetadata, WORKFLOWS_GITIGNORE, } from "../runtime/discovery.ts";
|
|
20
20
|
export type { DiscoveredWorkflow, WorkflowWithMetadata, WorkflowMetadataStatus, } from "../runtime/discovery.ts";
|
|
21
21
|
export { WorkflowLoader } from "../runtime/loader.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sdk/workflows/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExE,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,qBAAqB,IAAI,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC3F,YAAY,EAAE,cAAc,IAAI,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG7F,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAC5I,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,eAAe,EACf,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sdk/workflows/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExE,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,YAAY,IAAI,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,YAAY,EAAE,qBAAqB,IAAI,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC3F,YAAY,EAAE,cAAc,IAAI,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG7F,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAC5I,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,eAAe,EACf,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,OAAO,EACP,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -331,9 +331,12 @@ Examples:
|
|
|
331
331
|
.command("_footer", { hidden: true })
|
|
332
332
|
.description("Internal: render the attached-mode footer for an agent window")
|
|
333
333
|
.requiredOption("--name <name>", "Agent window name")
|
|
334
|
-
.
|
|
334
|
+
.option("--agent <agent>", "Agent type — renders provider pill in the footer")
|
|
335
|
+
.action(async (opts: { name: string; agent?: string }) => {
|
|
335
336
|
const { footerCommand } = await import("./commands/cli/footer.tsx");
|
|
336
|
-
const
|
|
337
|
+
const { isValidAgent } = await import("./services/config/definitions.ts");
|
|
338
|
+
const agentType = opts.agent && isValidAgent(opts.agent) ? opts.agent : undefined;
|
|
339
|
+
const exitCode = await footerCommand(opts.name, agentType);
|
|
337
340
|
process.exit(exitCode);
|
|
338
341
|
});
|
|
339
342
|
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
spawnMuxAttach,
|
|
40
40
|
switchClient,
|
|
41
41
|
} from "../../../sdk/workflows/index.ts";
|
|
42
|
+
import { spawnAttachedFooter } from "../../../sdk/runtime/attached-footer.ts";
|
|
42
43
|
import { ensureTmuxInstalled } from "../../../lib/spawn.ts";
|
|
43
44
|
|
|
44
45
|
// ============================================================================
|
|
@@ -241,9 +242,15 @@ export async function chatCommand(options: ChatCommandOptions = {}): Promise<num
|
|
|
241
242
|
|
|
242
243
|
// ── Create session on the atomic socket and attach ──
|
|
243
244
|
try {
|
|
244
|
-
createSession(windowName, shellCmd, undefined, projectRoot);
|
|
245
|
+
const paneId = createSession(windowName, shellCmd, undefined, projectRoot);
|
|
245
246
|
setSessionEnv(windowName, "ATOMIC_AGENT", agentType);
|
|
246
247
|
|
|
248
|
+
// Split the pane so the agent CLI runs on top and the React footer
|
|
249
|
+
// (provider pill + window name) runs in a 5% bottom pane. Matches
|
|
250
|
+
// the workflow-window layout, minus the keyboard hints (chat sessions
|
|
251
|
+
// only host a single agent, so ctrl+g / ctrl+\ navigation is moot).
|
|
252
|
+
spawnAttachedFooter(windowName, paneId, agentType);
|
|
253
|
+
|
|
247
254
|
if (isInsideAtomicSocket()) {
|
|
248
255
|
// Already on the atomic server — just switch to the new session.
|
|
249
256
|
switchClient(windowName);
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type GraphTheme,
|
|
21
21
|
} from "../../sdk/components/graph-theme.ts";
|
|
22
22
|
import { AttachedStatusline } from "../../sdk/components/attached-statusline.tsx";
|
|
23
|
+
import type { AgentType } from "../../sdk/types.ts";
|
|
23
24
|
|
|
24
25
|
const PARENT_WATCHDOG_MS = 2000;
|
|
25
26
|
|
|
@@ -58,7 +59,15 @@ const EXIT_SIGNALS: NodeJS.Signals[] =
|
|
|
58
59
|
? ["SIGTERM", "SIGINT", "SIGBREAK", "SIGHUP"]
|
|
59
60
|
: ["SIGHUP", "SIGTERM", "SIGINT", "SIGPIPE"];
|
|
60
61
|
|
|
61
|
-
function FooterShell({
|
|
62
|
+
function FooterShell({
|
|
63
|
+
name,
|
|
64
|
+
theme,
|
|
65
|
+
agentType,
|
|
66
|
+
}: {
|
|
67
|
+
name: string;
|
|
68
|
+
theme: GraphTheme;
|
|
69
|
+
agentType?: AgentType;
|
|
70
|
+
}) {
|
|
62
71
|
const renderer = useRenderer();
|
|
63
72
|
|
|
64
73
|
useEffect(() => {
|
|
@@ -101,17 +110,22 @@ function FooterShell({ name, theme }: { name: string; theme: GraphTheme }) {
|
|
|
101
110
|
justifyContent="flex-end"
|
|
102
111
|
backgroundColor={theme.background}
|
|
103
112
|
>
|
|
104
|
-
<AttachedStatusline name={name} theme={theme} />
|
|
113
|
+
<AttachedStatusline name={name} theme={theme} agentType={agentType} />
|
|
105
114
|
</box>
|
|
106
115
|
);
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
export async function footerCommand(
|
|
118
|
+
export async function footerCommand(
|
|
119
|
+
name: string,
|
|
120
|
+
agentType?: AgentType,
|
|
121
|
+
): Promise<number> {
|
|
110
122
|
const renderer = await createCliRenderer({
|
|
111
123
|
exitOnCtrlC: false,
|
|
112
124
|
});
|
|
113
125
|
const theme = deriveGraphTheme(resolveTheme(renderer.themeMode));
|
|
114
|
-
createRoot(renderer).render(
|
|
126
|
+
createRoot(renderer).render(
|
|
127
|
+
<FooterShell name={name} theme={theme} agentType={agentType} />,
|
|
128
|
+
);
|
|
115
129
|
|
|
116
130
|
await new Promise<void>(() => {});
|
|
117
131
|
return 0;
|
|
@@ -1,14 +1,67 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/react */
|
|
2
2
|
/**
|
|
3
3
|
* Footer rendered inside each agent tmux window. Lives in a 1-row bottom
|
|
4
|
-
* pane created by the executor
|
|
5
|
-
* the orchestrator Statusline style
|
|
6
|
-
*
|
|
4
|
+
* pane created by the executor (workflow) or the chat command, spawned via
|
|
5
|
+
* `atomic _footer`. Mirrors the orchestrator Statusline style: a colored
|
|
6
|
+
* pill on the left and right-aligned context on the right.
|
|
7
|
+
*
|
|
8
|
+
* Two variants:
|
|
9
|
+
* - Workflow: the window name is the left badge; right side shows the
|
|
10
|
+
* navigation hints (ctrl+g graph · ctrl+\ next). The `ctrl+b d
|
|
11
|
+
* detach` hint is surfaced in the orchestrator-window Statusline
|
|
12
|
+
* only, not duplicated into every agent pane footer.
|
|
13
|
+
* - Chat (agentType set): the provider name becomes the left pill
|
|
14
|
+
* (CLAUDE / COPILOT / OPENCODE, colored to match the workflow
|
|
15
|
+
* picker); right side shows pane name · ctrl+b d detach
|
|
16
|
+
* (tmux's default detach binding — spelled out because many Atomic
|
|
17
|
+
* users have never used tmux directly).
|
|
7
18
|
*/
|
|
8
19
|
|
|
20
|
+
import type { AgentType } from "../types.ts";
|
|
9
21
|
import type { GraphTheme } from "./graph-theme.ts";
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
/** Per-agent brand color, matching the workflow picker pill hues. */
|
|
24
|
+
const AGENT_PILL_COLOR: Record<AgentType, keyof GraphTheme> = {
|
|
25
|
+
claude: "warning",
|
|
26
|
+
copilot: "success",
|
|
27
|
+
opencode: "mauve",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DOT = "\u00B7";
|
|
31
|
+
|
|
32
|
+
export function AttachedStatusline({
|
|
33
|
+
name,
|
|
34
|
+
theme,
|
|
35
|
+
agentType,
|
|
36
|
+
}: {
|
|
37
|
+
name: string;
|
|
38
|
+
theme: GraphTheme;
|
|
39
|
+
agentType?: AgentType;
|
|
40
|
+
}) {
|
|
41
|
+
if (agentType) {
|
|
42
|
+
const pillBg = theme[AGENT_PILL_COLOR[agentType]];
|
|
43
|
+
return (
|
|
44
|
+
<box height={1} flexDirection="row" backgroundColor={theme.backgroundElement}>
|
|
45
|
+
<box backgroundColor={pillBg} paddingLeft={1} paddingRight={1} alignItems="center">
|
|
46
|
+
<text fg={theme.backgroundElement}>
|
|
47
|
+
<strong>{agentType.toUpperCase()}</strong>
|
|
48
|
+
</text>
|
|
49
|
+
</box>
|
|
50
|
+
|
|
51
|
+
<box flexGrow={1} />
|
|
52
|
+
|
|
53
|
+
<box paddingRight={2} alignItems="center">
|
|
54
|
+
<text>
|
|
55
|
+
<span fg={theme.textMuted}>{name}</span>
|
|
56
|
+
<span fg={theme.textDim}>{" " + DOT + " "}</span>
|
|
57
|
+
<span fg={theme.text}>ctrl+b d</span>
|
|
58
|
+
<span fg={theme.textMuted}> detach</span>
|
|
59
|
+
</text>
|
|
60
|
+
</box>
|
|
61
|
+
</box>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
12
65
|
return (
|
|
13
66
|
<box height={1} flexDirection="row" backgroundColor={theme.backgroundElement}>
|
|
14
67
|
<box backgroundColor={theme.primary} paddingLeft={1} paddingRight={1} alignItems="center">
|
|
@@ -23,7 +76,7 @@ export function AttachedStatusline({ name, theme }: { name: string; theme: Graph
|
|
|
23
76
|
<text>
|
|
24
77
|
<span fg={theme.text}>ctrl+g</span>
|
|
25
78
|
<span fg={theme.textMuted}> graph</span>
|
|
26
|
-
<span fg={theme.textDim}>
|
|
79
|
+
<span fg={theme.textDim}>{" " + DOT + " "}</span>
|
|
27
80
|
<span fg={theme.text}>{"ctrl+\\"}</span>
|
|
28
81
|
<span fg={theme.textMuted}> next</span>
|
|
29
82
|
</text>
|
|
@@ -14,6 +14,7 @@ export interface GraphTheme {
|
|
|
14
14
|
error: string;
|
|
15
15
|
warning: string;
|
|
16
16
|
info: string;
|
|
17
|
+
mauve: string;
|
|
17
18
|
border: string;
|
|
18
19
|
borderActive: string;
|
|
19
20
|
}
|
|
@@ -30,6 +31,7 @@ export function deriveGraphTheme(t: TerminalTheme): GraphTheme {
|
|
|
30
31
|
error: t.error,
|
|
31
32
|
warning: t.warning,
|
|
32
33
|
info: t.accent,
|
|
34
|
+
mauve: t.mauve,
|
|
33
35
|
border: t.borderDim,
|
|
34
36
|
borderActive: t.border,
|
|
35
37
|
};
|
|
@@ -51,6 +51,9 @@ export function Statusline({
|
|
|
51
51
|
<span fg={theme.text}>/</span>
|
|
52
52
|
<span fg={theme.textMuted}> stages</span>
|
|
53
53
|
<span fg={theme.textDim}> {"\u00B7"} </span>
|
|
54
|
+
<span fg={theme.text}>ctrl+b d</span>
|
|
55
|
+
<span fg={theme.textMuted}> detach</span>
|
|
56
|
+
<span fg={theme.textDim}> {"\u00B7"} </span>
|
|
54
57
|
<span fg={theme.text}>q</span>
|
|
55
58
|
<span fg={theme.textMuted}> quit</span>
|
|
56
59
|
</text>
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type SDKUserMessage,
|
|
25
25
|
type Options as SDKOptions,
|
|
26
26
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
27
|
-
import {
|
|
27
|
+
import { respawnPane } from "../runtime/tmux.ts";
|
|
28
28
|
import { escBash } from "../runtime/executor.ts";
|
|
29
29
|
import { watch, unlink, mkdir, writeFile } from "node:fs/promises";
|
|
30
30
|
import { existsSync, writeFileSync } from "node:fs";
|
|
@@ -283,15 +283,12 @@ async function spawnClaudeWithPrompt(
|
|
|
283
283
|
argvPrompt,
|
|
284
284
|
].join(" ");
|
|
285
285
|
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
await waitForPaneReady(paneId, readyTimeoutMs);
|
|
293
|
-
|
|
294
|
-
await sendKeysAndSubmit(paneId, cmd);
|
|
286
|
+
// Replace the pane's shell with `claude` directly. tmux execs the command
|
|
287
|
+
// itself, so there's no shell line editor to race with — the previous
|
|
288
|
+
// approach keystroked into a zsh that hadn't finished ZLE init yet, and
|
|
289
|
+
// zsh's TCSAFLUSH during startup would discard the buffered `\r`, leaving
|
|
290
|
+
// the command typed at the prompt but never submitted.
|
|
291
|
+
respawnPane(paneId, cmd);
|
|
295
292
|
|
|
296
293
|
// SDK-native readiness signal: wait for Claude to create its JSONL file
|
|
297
294
|
// at the known UUID path.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper for spawning the attached `atomic _footer` pane inside an agent
|
|
3
|
+
* tmux window.
|
|
4
|
+
*
|
|
5
|
+
* Shared between the workflow executor (per-agent windows) and the chat
|
|
6
|
+
* command (single-agent window). Splits the target pane vertically so the
|
|
7
|
+
* top pane keeps running the agent CLI and the bottom pane hosts the
|
|
8
|
+
* React footer.
|
|
9
|
+
*
|
|
10
|
+
* Resolves the CLI entrypoint relative to this module (runtime/ lives at
|
|
11
|
+
* src/sdk/runtime/, so ../../cli.ts is the CLI). `process.argv[1]` points
|
|
12
|
+
* at the orchestrator's executor-entry.ts when called from the executor,
|
|
13
|
+
* so it can't be used here.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import type { AgentType } from "../types.ts";
|
|
18
|
+
import { tmuxRun } from "./tmux.ts";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Rows reserved for the footer pane. Matches the single-row height of
|
|
22
|
+
* `AttachedStatusline` so the agent pane absorbs all remaining space.
|
|
23
|
+
*/
|
|
24
|
+
const FOOTER_PANE_LINES = 1;
|
|
25
|
+
|
|
26
|
+
/** Escape a string for safe interpolation inside a bash double-quoted string. */
|
|
27
|
+
function escBash(s: string): string {
|
|
28
|
+
return s
|
|
29
|
+
.replace(/\x00/g, "")
|
|
30
|
+
.replace(/[\n\r]+/g, " ")
|
|
31
|
+
.replace(/[\\"$`!]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function spawnAttachedFooter(
|
|
35
|
+
windowName: string,
|
|
36
|
+
paneId: string,
|
|
37
|
+
agentType?: AgentType,
|
|
38
|
+
): void {
|
|
39
|
+
const runtime = process.execPath;
|
|
40
|
+
if (!runtime) return;
|
|
41
|
+
const cliPath = join(import.meta.dir, "..", "..", "cli.ts");
|
|
42
|
+
const agentFlag = agentType ? ` --agent "${escBash(agentType)}"` : "";
|
|
43
|
+
const cmd =
|
|
44
|
+
`"${escBash(runtime)}" "${escBash(cliPath)}" _footer ` +
|
|
45
|
+
`--name "${escBash(windowName)}"${agentFlag}`;
|
|
46
|
+
const split = tmuxRun([
|
|
47
|
+
"split-window",
|
|
48
|
+
"-t", paneId,
|
|
49
|
+
"-v", "-l", String(FOOTER_PANE_LINES), "-d",
|
|
50
|
+
"-P", "-F", "#{pane_id}",
|
|
51
|
+
cmd,
|
|
52
|
+
]);
|
|
53
|
+
if (!split.ok) return;
|
|
54
|
+
const footerPaneId = split.stdout.trim();
|
|
55
|
+
if (!footerPaneId) return;
|
|
56
|
+
// Pin the footer to FOOTER_PANE_LINES on every resize so the agent pane
|
|
57
|
+
// absorbs all new space. Tmux's default proportional redistribution
|
|
58
|
+
// would otherwise grow the footer on larger windows. Window-scoped
|
|
59
|
+
// (`-w`) so other windows (e.g. the orchestrator graph) are unaffected.
|
|
60
|
+
tmuxRun([
|
|
61
|
+
"set-hook",
|
|
62
|
+
"-w", "-t", footerPaneId,
|
|
63
|
+
"window-resized",
|
|
64
|
+
`resize-pane -t ${footerPaneId} -y ${FOOTER_PANE_LINES}`,
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Ctrl+C debounce helper for Atomic-managed tmux panes.
|
|
4
|
+
*
|
|
5
|
+
* Invoked from `tmux.conf` on every root-table Ctrl+C:
|
|
6
|
+
*
|
|
7
|
+
* bind -n C-c run-shell -b '"#{@atomic-bun}" "#{@atomic-cc-debounce}" "#{pane_id}"'
|
|
8
|
+
*
|
|
9
|
+
* The binding sits on the shared atomic tmux server, so the debounce
|
|
10
|
+
* applies uniformly to every pane — Claude Code, OpenCode, and Copilot
|
|
11
|
+
* CLI, in both chat and workflow sessions.
|
|
12
|
+
*
|
|
13
|
+
* Rule: forward Ctrl+C only if the previous press is more than QUIET_MS
|
|
14
|
+
* ago. The state file is touched on *every* press (forwarded or
|
|
15
|
+
* swallowed) so sustained spam keeps extending the cooldown instead of
|
|
16
|
+
* letting a press leak through every QUIET_MS interval — which is what
|
|
17
|
+
* would otherwise still trigger an agent CLI's "double-tap to exit"
|
|
18
|
+
* confirmation.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
24
|
+
|
|
25
|
+
/** Quiet period (ms) the user must leave between presses for the next
|
|
26
|
+
* one to be forwarded. Must exceed every integrated agent's exit-confirm
|
|
27
|
+
* window — Claude Code's is the widest (~1.5 s), so 1200 ms is a safe
|
|
28
|
+
* margin that still feels responsive for legitimate double-interrupts. */
|
|
29
|
+
export const QUIET_MS = 1200;
|
|
30
|
+
|
|
31
|
+
/** Pure decision helper — exported so tests can exercise it without
|
|
32
|
+
* touching the filesystem or spawning tmux. Returns `true` when the
|
|
33
|
+
* press should be forwarded, `false` when it should be swallowed. */
|
|
34
|
+
export function shouldForward(
|
|
35
|
+
nowMs: number,
|
|
36
|
+
lastMs: number,
|
|
37
|
+
quietMs: number = QUIET_MS,
|
|
38
|
+
): boolean {
|
|
39
|
+
return nowMs - lastMs > quietMs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Filesystem-safe transform for a tmux pane id (typically `%0`, `%12`).
|
|
43
|
+
* A defensive sanitise keeps the path portable if tmux ever hands us
|
|
44
|
+
* something with shell metacharacters. */
|
|
45
|
+
function stateFileFor(paneId: string): string {
|
|
46
|
+
const safe = paneId.replace(/[^a-zA-Z0-9_%-]/g, "_");
|
|
47
|
+
return join(tmpdir(), `atomic-cc-${safe}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readLastPress(stateFile: string): number {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = Number.parseInt(readFileSync(stateFile, "utf8").trim(), 10);
|
|
53
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolve the multiplexer binary for `tmux send-keys`. When `run-shell`
|
|
60
|
+
* spawns this script the `TMUX` / `PSMUX` env var is already set to the
|
|
61
|
+
* atomic socket, so plain `tmux` / `psmux` routes correctly without
|
|
62
|
+
* `-L`. We only need to pick the right executable name for the host. */
|
|
63
|
+
function resolveMuxBinary(): string {
|
|
64
|
+
if (process.platform === "win32") {
|
|
65
|
+
for (const candidate of ["psmux", "pmux", "tmux"]) {
|
|
66
|
+
if (Bun.which(candidate)) return candidate;
|
|
67
|
+
}
|
|
68
|
+
return "psmux";
|
|
69
|
+
}
|
|
70
|
+
return "tmux";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main(): number {
|
|
74
|
+
const paneId = process.argv[2];
|
|
75
|
+
if (!paneId) return 0;
|
|
76
|
+
|
|
77
|
+
const stateFile = stateFileFor(paneId);
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const last = readLastPress(stateFile);
|
|
80
|
+
|
|
81
|
+
// Always bump the timestamp so held-down or spammed presses keep
|
|
82
|
+
// extending the cooldown — this is what turns the single-press gate
|
|
83
|
+
// into a true quiet-period debounce.
|
|
84
|
+
try {
|
|
85
|
+
writeFileSync(stateFile, String(now));
|
|
86
|
+
} catch {
|
|
87
|
+
// Best-effort: if the tmp dir is read-only we'd rather forward every
|
|
88
|
+
// press than drop them silently.
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!shouldForward(now, last)) return 0;
|
|
92
|
+
|
|
93
|
+
const proc = Bun.spawnSync({
|
|
94
|
+
cmd: [resolveMuxBinary(), "send-keys", "-t", paneId, "C-c"],
|
|
95
|
+
stdin: "ignore",
|
|
96
|
+
stdout: "ignore",
|
|
97
|
+
stderr: "ignore",
|
|
98
|
+
});
|
|
99
|
+
return proc.exitCode ?? 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (import.meta.main) {
|
|
103
|
+
process.exit(main());
|
|
104
|
+
}
|
|
@@ -45,6 +45,7 @@ import type { SessionPromptResponse } from "@opencode-ai/sdk/v2";
|
|
|
45
45
|
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
46
46
|
import * as tmux from "./tmux.ts";
|
|
47
47
|
import { spawnMuxAttach } from "./tmux.ts";
|
|
48
|
+
import { spawnAttachedFooter } from "./attached-footer.ts";
|
|
48
49
|
import { WorkflowLoader } from "./loader.ts";
|
|
49
50
|
import {
|
|
50
51
|
clearClaudeSession,
|
|
@@ -276,34 +277,6 @@ export function applyContainerEnvDefaults(): void {
|
|
|
276
277
|
if (bin) process.env.COPILOT_CLI_PATH = bin;
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
/** Percent of the agent window's vertical space allocated to the React footer pane. */
|
|
280
|
-
const FOOTER_PANE_PERCENT = 5;
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Split the agent window so the top pane runs the agent CLI and the bottom
|
|
284
|
-
* pane runs the React footer (via `atomic _footer`). Allocating a percentage
|
|
285
|
-
* of the vertical layout — rather than an absolute cell count — lets tmux
|
|
286
|
-
* enforce its minimum-pane-size constraints and keeps the split readable on
|
|
287
|
-
* both tall and short terminals.
|
|
288
|
-
*
|
|
289
|
-
* Resolves the CLI entrypoint relative to this file (executor.ts lives at
|
|
290
|
-
* src/sdk/runtime/, so ../../cli.ts is the CLI). `process.argv[1]` points
|
|
291
|
-
* to executor-entry.ts here — a separate process that has no `_footer`
|
|
292
|
-
* subcommand — so we can't use it.
|
|
293
|
-
*/
|
|
294
|
-
function spawnAttachedFooter(windowName: string, paneId: string): void {
|
|
295
|
-
const runtime = process.execPath;
|
|
296
|
-
if (!runtime) return;
|
|
297
|
-
const cliPath = join(import.meta.dir, "..", "..", "cli.ts");
|
|
298
|
-
const cmd = `"${escBash(runtime)}" "${escBash(cliPath)}" _footer --name "${escBash(windowName)}"`;
|
|
299
|
-
tmux.tmuxRun([
|
|
300
|
-
"split-window",
|
|
301
|
-
"-t", paneId,
|
|
302
|
-
"-v", "-l", `${FOOTER_PANE_PERCENT}%`, "-d",
|
|
303
|
-
cmd,
|
|
304
|
-
]);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
280
|
function buildPaneCommand(
|
|
308
281
|
agent: AgentType,
|
|
309
282
|
port: number,
|
package/src/sdk/runtime/theme.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface TerminalTheme {
|
|
|
25
25
|
success: string;
|
|
26
26
|
error: string;
|
|
27
27
|
warning: string;
|
|
28
|
+
mauve: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
@@ -43,6 +44,7 @@ const CATPPUCCIN_MOCHA: TerminalTheme = {
|
|
|
43
44
|
success: "#a6e3a1", // Green
|
|
44
45
|
error: "#f38ba8", // Red
|
|
45
46
|
warning: "#f9e2af", // Yellow
|
|
47
|
+
mauve: "#cba6f7", // Mauve
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
// ---------------------------------------------------------------------------
|
|
@@ -61,6 +63,7 @@ const CATPPUCCIN_LATTE: TerminalTheme = {
|
|
|
61
63
|
success: "#40a02b", // Green
|
|
62
64
|
error: "#d20f39", // Red
|
|
63
65
|
warning: "#df8e1d", // Yellow
|
|
66
|
+
mauve: "#8839ef", // Mauve
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
// ---------------------------------------------------------------------------
|
|
@@ -20,14 +20,10 @@ set -g status-interval 5
|
|
|
20
20
|
set -g focus-events on
|
|
21
21
|
setw -g aggressive-resize on
|
|
22
22
|
|
|
23
|
-
# Status bar —
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
set -g status
|
|
27
|
-
set -g status-right " #{session_name} | %H:%M "
|
|
28
|
-
set -g status-right-length 60
|
|
29
|
-
set -g status-style "bg=#1e1e2e,fg=#cdd6f4"
|
|
30
|
-
set -g status-right-style "fg=#6c7086"
|
|
23
|
+
# Status bar — hidden. The React-rendered AttachedStatusline footer
|
|
24
|
+
# (spawned via `atomic _footer`) already shows the provider pill / pane
|
|
25
|
+
# name, so the default tmux status line would visually compete with it.
|
|
26
|
+
set -g status off
|
|
31
27
|
|
|
32
28
|
# Pane resizing
|
|
33
29
|
bind -r l resize-pane -R 5
|
|
@@ -57,6 +53,15 @@ bind -n C-g select-window -t :0
|
|
|
57
53
|
# Ctrl+\: cycle to next agent window from anywhere
|
|
58
54
|
bind -n C-\\ next-window
|
|
59
55
|
|
|
56
|
+
# Ctrl+C debounce — forward the first press of a burst and swallow the
|
|
57
|
+
# rest while the user is still pressing, preventing the "double-tap to
|
|
58
|
+
# exit" behaviour of Claude Code / OpenCode / Copilot from firing on
|
|
59
|
+
# accidental spam. Runs via bun so the same TypeScript helper works on
|
|
60
|
+
# Linux, macOS, and Windows psmux. @atomic-bun and @atomic-cc-debounce
|
|
61
|
+
# are set to absolute paths by tmux.ts on session creation — no PATH
|
|
62
|
+
# lookup needed inside run-shell.
|
|
63
|
+
bind -n C-c run-shell -b '"#{@atomic-bun}" "#{@atomic-cc-debounce}" "#{pane_id}"'
|
|
64
|
+
|
|
60
65
|
# Escape exits copy-mode (clear selection first if one exists, otherwise cancel)
|
|
61
66
|
bind-key -T copy-mode-vi Escape if-shell -F "#{selection_present}" "send-keys -X clear-selection" "send-keys -X cancel"
|
|
62
67
|
|
|
@@ -68,7 +73,7 @@ bind -T copy-mode-vi MouseDrag1Pane select-pane \; send-keys -X begin-selecti
|
|
|
68
73
|
bind -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel
|
|
69
74
|
bind -T copy-mode-vi MouseDown1Pane send-keys -X clear-selection \; select-pane
|
|
70
75
|
|
|
71
|
-
# ── Workflow protection: unbind destructive defaults
|
|
76
|
+
# ── Workflow protection: unbind destructive and layout-changing defaults ──
|
|
72
77
|
# Atomic manages the tmux session lifecycle programmatically. Users should
|
|
73
78
|
# interact with agents and navigate via Ctrl+G / Ctrl+\ / the graph panel.
|
|
74
79
|
# The prefix key (C-b) is kept only for copy-mode entry (C-b [).
|
|
@@ -84,21 +89,21 @@ bind -T copy-mode-vi MouseDown1Pane send-keys -X clear-selection \; select-pa
|
|
|
84
89
|
# C-b [ copy-mode entry (keyboard fallback for mouse scroll)
|
|
85
90
|
# C-b : command prompt (requires typing a full command — low accidental risk)
|
|
86
91
|
# C-b d detach (non-destructive; just disconnects the client)
|
|
87
|
-
# C-b c new window (non-destructive; doesn't affect existing windows)
|
|
88
|
-
# C-b - custom split vertical (replaces default C-b ")
|
|
89
|
-
# C-b | custom split horizontal (replaces default C-b %)
|
|
90
92
|
|
|
91
93
|
# Prevent window/pane destruction
|
|
92
94
|
unbind & # kill-window
|
|
93
95
|
unbind x # kill-pane
|
|
94
96
|
|
|
97
|
+
# Prevent window/pane creation — Atomic owns the layout. User-initiated
|
|
98
|
+
# splits or new windows would desync the executor's pane registry and
|
|
99
|
+
# leave orphaned shells inside workflow/chat sessions.
|
|
100
|
+
unbind c # new-window
|
|
101
|
+
unbind '"' # default split vertical
|
|
102
|
+
unbind % # default split horizontal
|
|
103
|
+
|
|
95
104
|
# Prevent renaming
|
|
96
105
|
unbind , # rename-window
|
|
97
106
|
unbind '$' # rename-session
|
|
98
107
|
|
|
99
108
|
# Prevent automatic window renaming (complements allow-rename off above)
|
|
100
109
|
setw -g automatic-rename off
|
|
101
|
-
|
|
102
|
-
# Pane splitting
|
|
103
|
-
bind - split-window -v -c "#{pane_current_path}"
|
|
104
|
-
bind | split-window -h -c "#{pane_current_path}"
|
package/src/sdk/runtime/tmux.ts
CHANGED
|
@@ -21,6 +21,11 @@ export const SOCKET_NAME = "atomic";
|
|
|
21
21
|
/** Path to the bundled tmux config (shared by tmux and psmux). */
|
|
22
22
|
const CONFIG_PATH = join(import.meta.dir, "tmux.conf");
|
|
23
23
|
|
|
24
|
+
/** Path to the bundled Ctrl+C debounce script (TypeScript, run via bun
|
|
25
|
+
* so the same file handles Linux, macOS, and Windows without shell
|
|
26
|
+
* dialect gymnastics). Referenced from tmux.conf. */
|
|
27
|
+
const CC_DEBOUNCE_PATH = join(import.meta.dir, "cc-debounce.ts");
|
|
28
|
+
|
|
24
29
|
/** Discriminated result from a tmux command execution. */
|
|
25
30
|
export type TmuxResult =
|
|
26
31
|
| { ok: true; stdout: string }
|
|
@@ -204,6 +209,14 @@ export function createSession(
|
|
|
204
209
|
// Reload config into the running server so keybindings are always current
|
|
205
210
|
// (tmux only loads -f on first server start; source-file updates a running server).
|
|
206
211
|
tmuxRun(["source-file", CONFIG_PATH]);
|
|
212
|
+
// Expose the bun binary and debounce-script paths as server-wide user
|
|
213
|
+
// options so tmux.conf's Ctrl+C binding can invoke them without
|
|
214
|
+
// hardcoding an install path or relying on the user's PATH — which
|
|
215
|
+
// tmux's run-shell does not always inherit in full, especially on
|
|
216
|
+
// Windows psmux. `process.execPath` is the exact bun interpreter
|
|
217
|
+
// currently running atomic, guaranteeing it's executable.
|
|
218
|
+
tmuxRun(["set-option", "-g", "@atomic-bun", process.execPath]);
|
|
219
|
+
tmuxRun(["set-option", "-g", "@atomic-cc-debounce", CC_DEBOUNCE_PATH]);
|
|
207
220
|
return paneId || tmux(["list-panes", "-t", sessionName, "-F", "#{pane_id}"]).split("\n")[0]!;
|
|
208
221
|
}
|
|
209
222
|
|
|
@@ -253,6 +266,19 @@ export function createPane(sessionName: string, command: string): string {
|
|
|
253
266
|
]);
|
|
254
267
|
}
|
|
255
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Replace the running command in an existing pane with a new one.
|
|
271
|
+
*
|
|
272
|
+
* `-k` kills whatever is currently running in the pane (e.g. a still-initializing
|
|
273
|
+
* shell) before tmux spawns the new command. Because tmux execs the command
|
|
274
|
+
* itself rather than forwarding keystrokes through a shell line editor, there
|
|
275
|
+
* is no shell-ready race and no ZLE TCSAFLUSH drop — callers can invoke this
|
|
276
|
+
* immediately after pane creation without waiting for a prompt to appear.
|
|
277
|
+
*/
|
|
278
|
+
export function respawnPane(paneId: string, command: string): void {
|
|
279
|
+
tmuxExec(["respawn-pane", "-k", "-t", paneId, command]);
|
|
280
|
+
}
|
|
281
|
+
|
|
256
282
|
// ---------------------------------------------------------------------------
|
|
257
283
|
// Keystroke sending
|
|
258
284
|
// ---------------------------------------------------------------------------
|
|
@@ -305,29 +331,6 @@ export function sendSpecialKey(paneId: string, key: string): void {
|
|
|
305
331
|
tmuxExec(["send-keys", "-t", paneId, key]);
|
|
306
332
|
}
|
|
307
333
|
|
|
308
|
-
/**
|
|
309
|
-
* Send literal text and submit with C-m (carriage return).
|
|
310
|
-
* Uses C-m instead of Enter for raw-mode TUI compatibility.
|
|
311
|
-
*
|
|
312
|
-
* @param presses - Number of C-m presses (default: 1)
|
|
313
|
-
* @param delayMs - Delay between presses in ms (default: 100)
|
|
314
|
-
*/
|
|
315
|
-
export async function sendKeysAndSubmit(
|
|
316
|
-
paneId: string,
|
|
317
|
-
text: string,
|
|
318
|
-
presses = 1,
|
|
319
|
-
delayMs = 100
|
|
320
|
-
): Promise<void> {
|
|
321
|
-
sendLiteralText(paneId, text);
|
|
322
|
-
|
|
323
|
-
for (let i = 0; i < presses; i++) {
|
|
324
|
-
if (i > 0 && delayMs > 0) {
|
|
325
|
-
await Bun.sleep(delayMs);
|
|
326
|
-
}
|
|
327
|
-
sendSpecialKey(paneId, "C-m");
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
334
|
// ---------------------------------------------------------------------------
|
|
332
335
|
// Pane capture
|
|
333
336
|
// ---------------------------------------------------------------------------
|
|
@@ -655,159 +658,3 @@ export function normalizeTmuxLines(text: string): string {
|
|
|
655
658
|
.trim();
|
|
656
659
|
}
|
|
657
660
|
|
|
658
|
-
/** Split capture into cleaned, non-empty lines. */
|
|
659
|
-
function toPaneLines(captured: string): string[] {
|
|
660
|
-
return captured
|
|
661
|
-
.split("\n")
|
|
662
|
-
.map((l) => l.replace(/\r/g, "").trimEnd())
|
|
663
|
-
.filter((l) => l.trim() !== "");
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// ---------------------------------------------------------------------------
|
|
667
|
-
// Pane state detection (ported from oh-my-codex's tmux-hook-engine.ts)
|
|
668
|
-
// ---------------------------------------------------------------------------
|
|
669
|
-
|
|
670
|
-
/** Returns true when the pane is still bootstrapping (loading/initializing). */
|
|
671
|
-
function paneIsBootstrapping(lines: string[]): boolean {
|
|
672
|
-
return lines.some(
|
|
673
|
-
(line) =>
|
|
674
|
-
/\b(loading|initializing|starting up)\b/i.test(line) ||
|
|
675
|
-
/\bmodel:\s*loading\b/i.test(line) ||
|
|
676
|
-
/\bconnecting\s+to\b/i.test(line),
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Returns true when the pane shows an agent prompt ready for input.
|
|
682
|
-
* Detects Claude Code (❯), Codex (›), and generic (>) prompts.
|
|
683
|
-
*/
|
|
684
|
-
export function paneLooksReady(captured: string): boolean {
|
|
685
|
-
const content = captured.trimEnd();
|
|
686
|
-
if (content === "") return false;
|
|
687
|
-
|
|
688
|
-
const lines = toPaneLines(content);
|
|
689
|
-
if (paneIsBootstrapping(lines)) return false;
|
|
690
|
-
|
|
691
|
-
if (lines.some((line) => /^\s*[›>❯]\s*/u.test(line))) return true;
|
|
692
|
-
if (lines.some((line) => /\bhow can i help(?: you)?\b/i.test(line))) return true;
|
|
693
|
-
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Returns true when the agent has an active task in progress.
|
|
699
|
-
* Checks last 40 lines for known busy indicators.
|
|
700
|
-
*/
|
|
701
|
-
export function paneHasActiveTask(captured: string): boolean {
|
|
702
|
-
const tail = toPaneLines(captured)
|
|
703
|
-
.map((line) => line.trim())
|
|
704
|
-
.slice(-40);
|
|
705
|
-
|
|
706
|
-
return tail.some((l) =>
|
|
707
|
-
/\b\d+\s+background terminal running\b/i.test(l) ||
|
|
708
|
-
/esc to interrupt/i.test(l) ||
|
|
709
|
-
/\bbackground terminal running\b/i.test(l) ||
|
|
710
|
-
/^[·✻]\s+[A-Za-z][A-Za-z0-9''-]*(?:\s+[A-Za-z][A-Za-z0-9''-]*){0,3}(?:…|\.{3})$/u.test(l),
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Returns true when the pane is idle — showing a prompt and not processing.
|
|
716
|
-
* Uses visible-only capture to avoid stale scrollback matches.
|
|
717
|
-
*/
|
|
718
|
-
export function paneIsIdle(paneId: string): boolean {
|
|
719
|
-
const visible = capturePaneVisible(paneId);
|
|
720
|
-
return paneLooksReady(visible) && !paneHasActiveTask(visible);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// ---------------------------------------------------------------------------
|
|
724
|
-
// Readiness wait
|
|
725
|
-
// ---------------------------------------------------------------------------
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* Wait for the pane to be idle (prompt visible, no active task) with
|
|
729
|
-
* exponential backoff. Returns the time spent waiting (ms).
|
|
730
|
-
*/
|
|
731
|
-
export async function waitForPaneReady(paneId: string, timeoutMs: number = 30_000): Promise<number> {
|
|
732
|
-
const startedAt = Date.now();
|
|
733
|
-
let delayMs = 150;
|
|
734
|
-
const maxDelayMs = 8_000;
|
|
735
|
-
|
|
736
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
737
|
-
if (paneIsIdle(paneId)) return Date.now() - startedAt;
|
|
738
|
-
|
|
739
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
740
|
-
if (remaining <= 0) break;
|
|
741
|
-
await Bun.sleep(Math.min(delayMs, remaining));
|
|
742
|
-
delayMs = Math.min(maxDelayMs, delayMs * 2);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
return Date.now() - startedAt;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// ---------------------------------------------------------------------------
|
|
749
|
-
// Submit rounds with per-round verification
|
|
750
|
-
// ---------------------------------------------------------------------------
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Attempt to submit by pressing C-m, verifying after each round.
|
|
754
|
-
* Returns true as soon as the trigger text disappears from the visible
|
|
755
|
-
* capture or an active task is detected.
|
|
756
|
-
*/
|
|
757
|
-
export async function attemptSubmitRounds(
|
|
758
|
-
paneId: string,
|
|
759
|
-
normalizedPrompt: string,
|
|
760
|
-
rounds: number,
|
|
761
|
-
pressesPerRound: number = 1,
|
|
762
|
-
): Promise<boolean> {
|
|
763
|
-
const presses = Math.max(1, Math.floor(pressesPerRound));
|
|
764
|
-
|
|
765
|
-
for (let round = 0; round < rounds; round++) {
|
|
766
|
-
await Bun.sleep(100);
|
|
767
|
-
|
|
768
|
-
for (let press = 0; press < presses; press++) {
|
|
769
|
-
sendSpecialKey(paneId, "C-m");
|
|
770
|
-
if (press < presses - 1) await Bun.sleep(200);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
await Bun.sleep(140);
|
|
774
|
-
|
|
775
|
-
const visible = capturePaneVisible(paneId);
|
|
776
|
-
if (!normalizeTmuxCapture(visible).includes(normalizedPrompt)) return true;
|
|
777
|
-
if (paneHasActiveTask(visible)) return true;
|
|
778
|
-
|
|
779
|
-
await Bun.sleep(140);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
return false;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// ---------------------------------------------------------------------------
|
|
786
|
-
// Output waiting
|
|
787
|
-
// ---------------------------------------------------------------------------
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Wait for a pattern to appear in a tmux pane's output.
|
|
791
|
-
* Polls the pane content at the given interval until the pattern matches
|
|
792
|
-
* or the timeout is reached.
|
|
793
|
-
*
|
|
794
|
-
* @returns The full pane content when the pattern was found
|
|
795
|
-
*/
|
|
796
|
-
export async function waitForOutput(
|
|
797
|
-
paneId: string,
|
|
798
|
-
pattern: RegExp,
|
|
799
|
-
options: { timeoutMs?: number; pollIntervalMs?: number } = {}
|
|
800
|
-
): Promise<string> {
|
|
801
|
-
const { timeoutMs = 30_000, pollIntervalMs = 500 } = options;
|
|
802
|
-
const deadline = Date.now() + timeoutMs;
|
|
803
|
-
|
|
804
|
-
while (Date.now() < deadline) {
|
|
805
|
-
const content = capturePane(paneId);
|
|
806
|
-
if (pattern.test(content)) {
|
|
807
|
-
return content;
|
|
808
|
-
}
|
|
809
|
-
await Bun.sleep(pollIntervalMs);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
throw new Error(`Timed out waiting for pattern ${pattern} in pane ${paneId}`);
|
|
813
|
-
}
|
|
@@ -64,7 +64,6 @@ export {
|
|
|
64
64
|
createPane,
|
|
65
65
|
sendLiteralText,
|
|
66
66
|
sendSpecialKey,
|
|
67
|
-
sendKeysAndSubmit,
|
|
68
67
|
capturePane,
|
|
69
68
|
capturePaneVisible,
|
|
70
69
|
capturePaneScrollback,
|
|
@@ -82,15 +81,9 @@ export {
|
|
|
82
81
|
setSessionEnv,
|
|
83
82
|
getSessionEnv,
|
|
84
83
|
parseSessionName,
|
|
85
|
-
waitForOutput,
|
|
86
84
|
tmuxRun,
|
|
87
85
|
normalizeTmuxCapture,
|
|
88
86
|
normalizeTmuxLines,
|
|
89
|
-
paneLooksReady,
|
|
90
|
-
paneHasActiveTask,
|
|
91
|
-
paneIsIdle,
|
|
92
|
-
waitForPaneReady,
|
|
93
|
-
attemptSubmitRounds,
|
|
94
87
|
} from "../runtime/tmux.ts";
|
|
95
88
|
|
|
96
89
|
// Runtime — workflow discovery
|