@bastani/atomic 0.5.4-0 → 0.5.5-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -1
- package/dist/lib/path-root-guard.d.ts +4 -0
- package/dist/lib/path-root-guard.d.ts.map +1 -0
- package/dist/sdk/components/color-utils.d.ts +1 -0
- package/dist/sdk/components/color-utils.d.ts.map +1 -0
- package/dist/sdk/components/connectors.d.ts +3 -2
- package/dist/sdk/components/connectors.d.ts.map +1 -0
- package/dist/sdk/components/connectors.test.d.ts +1 -0
- package/dist/sdk/components/connectors.test.d.ts.map +1 -0
- package/dist/sdk/components/edge.d.ts +2 -1
- package/dist/sdk/components/edge.d.ts.map +1 -0
- package/dist/sdk/components/error-boundary.d.ts +1 -0
- package/dist/sdk/components/error-boundary.d.ts.map +1 -0
- package/dist/sdk/components/graph-theme.d.ts +2 -1
- package/dist/sdk/components/graph-theme.d.ts.map +1 -0
- package/dist/sdk/components/header.d.ts +1 -0
- package/dist/sdk/components/header.d.ts.map +1 -0
- package/dist/sdk/components/hooks.d.ts +15 -0
- package/dist/sdk/components/hooks.d.ts.map +1 -0
- package/dist/sdk/components/layout.d.ts +2 -1
- package/dist/sdk/components/layout.d.ts.map +1 -0
- package/dist/sdk/components/layout.test.d.ts +1 -0
- package/dist/sdk/components/layout.test.d.ts.map +1 -0
- package/dist/sdk/components/node-card.d.ts +5 -3
- package/dist/sdk/components/node-card.d.ts.map +1 -0
- package/dist/sdk/components/orchestrator-panel-contexts.d.ts +3 -2
- package/dist/sdk/components/orchestrator-panel-contexts.d.ts.map +1 -0
- package/dist/sdk/components/orchestrator-panel-store.d.ts +2 -1
- package/dist/sdk/components/orchestrator-panel-store.d.ts.map +1 -0
- package/dist/sdk/components/orchestrator-panel-store.test.d.ts +1 -0
- package/dist/sdk/components/orchestrator-panel-store.test.d.ts.map +1 -0
- package/dist/sdk/components/orchestrator-panel-types.d.ts +1 -0
- package/dist/sdk/components/orchestrator-panel-types.d.ts.map +1 -0
- package/dist/sdk/components/orchestrator-panel.d.ts +2 -1
- package/dist/sdk/components/orchestrator-panel.d.ts.map +1 -0
- package/dist/sdk/components/session-graph-panel.d.ts +1 -0
- package/dist/sdk/components/session-graph-panel.d.ts.map +1 -0
- package/dist/sdk/components/status-helpers.d.ts +2 -1
- package/dist/sdk/components/status-helpers.d.ts.map +1 -0
- package/dist/sdk/components/statusline.d.ts +2 -1
- package/dist/sdk/components/statusline.d.ts.map +1 -0
- package/dist/sdk/components/workflow-picker-panel.d.ts +11 -8
- package/dist/sdk/components/workflow-picker-panel.d.ts.map +1 -0
- package/dist/sdk/define-workflow.d.ts +2 -1
- package/dist/sdk/define-workflow.d.ts.map +1 -0
- package/dist/sdk/define-workflow.test.d.ts +1 -0
- package/dist/sdk/define-workflow.test.d.ts.map +1 -0
- package/dist/sdk/errors.d.ts +3 -0
- package/dist/sdk/errors.d.ts.map +1 -0
- package/dist/sdk/errors.test.d.ts +2 -0
- package/dist/sdk/errors.test.d.ts.map +1 -0
- package/dist/sdk/index.d.ts +7 -6
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/providers/claude.d.ts +17 -6
- package/dist/sdk/providers/claude.d.ts.map +1 -0
- package/dist/sdk/providers/copilot.d.ts +2 -5
- package/dist/sdk/providers/copilot.d.ts.map +1 -0
- package/dist/sdk/providers/opencode.d.ts +2 -5
- package/dist/sdk/providers/opencode.d.ts.map +1 -0
- package/dist/sdk/runtime/discovery.d.ts +2 -1
- package/dist/sdk/runtime/discovery.d.ts.map +1 -0
- package/dist/sdk/runtime/executor-entry.d.ts +1 -0
- package/dist/sdk/runtime/executor-entry.d.ts.map +1 -0
- package/dist/sdk/runtime/executor.d.ts +3 -6
- package/dist/sdk/runtime/executor.d.ts.map +1 -0
- package/dist/sdk/runtime/executor.test.d.ts +1 -0
- package/dist/sdk/runtime/executor.test.d.ts.map +1 -0
- package/dist/sdk/runtime/graph-inference.d.ts +1 -0
- package/dist/sdk/runtime/graph-inference.d.ts.map +1 -0
- package/dist/sdk/runtime/loader.d.ts +5 -7
- package/dist/sdk/runtime/loader.d.ts.map +1 -0
- package/dist/sdk/runtime/panel.d.ts +3 -2
- package/dist/sdk/runtime/panel.d.ts.map +1 -0
- package/dist/sdk/runtime/theme.d.ts +1 -0
- package/dist/sdk/runtime/theme.d.ts.map +1 -0
- package/dist/sdk/runtime/tmux.d.ts +26 -8
- package/dist/sdk/runtime/tmux.d.ts.map +1 -0
- package/dist/sdk/types.d.ts +23 -1
- package/dist/sdk/types.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +2 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/git.d.ts +1 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/git.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts +1 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/review.d.ts +2 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/review.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts +1 -0
- package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts.map +1 -0
- package/dist/sdk/workflows/index.d.ts +14 -14
- package/dist/sdk/workflows/index.d.ts.map +1 -0
- package/dist/services/config/definitions.d.ts +85 -0
- package/dist/services/config/definitions.d.ts.map +1 -0
- package/dist/services/system/copy.d.ts +77 -0
- package/dist/services/system/copy.d.ts.map +1 -0
- package/dist/services/system/detect.d.ts +75 -0
- package/dist/services/system/detect.d.ts.map +1 -0
- package/package.json +13 -34
- package/src/cli.ts +11 -10
- package/src/commands/cli/chat/index.ts +11 -11
- package/src/commands/cli/chat.ts +1 -1
- package/src/commands/cli/config.ts +10 -9
- package/src/commands/cli/init/index.ts +11 -11
- package/src/commands/cli/init/onboarding.ts +4 -4
- package/src/commands/cli/init/scm.ts +5 -5
- package/src/commands/cli/init.ts +1 -1
- package/src/commands/cli/workflow-command.test.ts +19 -11
- package/src/commands/cli/workflow.test.ts +2 -2
- package/src/commands/cli/workflow.ts +6 -6
- package/src/lib/merge.ts +17 -31
- package/src/lib/path-root-guard.ts +2 -2
- package/src/lib/spawn.ts +13 -7
- package/src/scripts/bump-version.ts +1 -1
- package/src/scripts/constants.ts +2 -2
- package/src/sdk/components/header.tsx +21 -23
- package/src/sdk/components/hooks.ts +21 -0
- package/src/sdk/components/node-card.tsx +3 -2
- package/src/sdk/components/session-graph-panel.tsx +14 -18
- package/src/sdk/components/workflow-picker-panel.tsx +201 -216
- package/src/sdk/errors.test.ts +56 -0
- package/src/sdk/errors.ts +5 -0
- package/src/sdk/providers/claude.ts +279 -70
- package/src/sdk/providers/copilot.ts +17 -27
- package/src/sdk/providers/opencode.ts +17 -27
- package/src/sdk/runtime/discovery.ts +18 -18
- package/src/sdk/runtime/executor.test.ts +15 -48
- package/src/sdk/runtime/executor.ts +152 -121
- package/src/sdk/runtime/loader.ts +16 -21
- package/src/sdk/runtime/tmux.ts +95 -32
- package/src/sdk/types.ts +45 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +27 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +25 -16
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +25 -24
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +5 -0
- package/src/sdk/workflows/index.ts +3 -3
- package/src/services/config/atomic-config.ts +7 -8
- package/src/services/config/atomic-global-config.ts +9 -9
- package/src/services/config/config-path.ts +1 -1
- package/src/services/config/definitions.ts +3 -4
- package/src/services/config/index.ts +1 -1
- package/src/services/config/settings.ts +30 -36
- package/src/services/system/agents.ts +3 -3
- package/src/services/system/auto-sync.ts +9 -9
- package/src/services/system/copy.ts +9 -9
- package/src/services/system/file-lock.ts +2 -2
- package/src/services/system/install-ui.ts +2 -2
- package/src/services/system/skills.ts +1 -1
- package/src/theme/colors.ts +1 -1
- package/src/theme/logo.ts +1 -1
- package/tsconfig.json +3 -4
- package/dist/chunk-1gb5qxz9.js +0 -1
- package/dist/chunk-fdk7tact.js +0 -417
- package/dist/chunk-xkxndz5g.js +0 -1041
- package/dist/sdk/index.js +0 -52
- package/dist/sdk/workflows/builtin/ralph/claude/index.js +0 -96
- package/dist/sdk/workflows/builtin/ralph/copilot/index.js +0 -119
- package/dist/sdk/workflows/builtin/ralph/opencode/index.js +0 -148
- package/dist/sdk/workflows/index.js +0 -100
- package/src/commands/cli/chat/client.ts +0 -18
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { WorkflowDefinition, AgentType } from "../types.ts";
|
|
14
14
|
import type { DiscoveredWorkflow } from "./discovery.ts";
|
|
15
|
+
import { errorMessage, WorkflowNotCompiledError, InvalidWorkflowError } from "../errors.ts";
|
|
15
16
|
import { validateCopilotWorkflow } from "../providers/copilot.ts";
|
|
16
17
|
import { validateOpenCodeWorkflow } from "../providers/opencode.ts";
|
|
17
18
|
import { validateClaudeWorkflow } from "../providers/claude.ts";
|
|
@@ -44,11 +45,8 @@ export namespace WorkflowLoader {
|
|
|
44
45
|
/** Output of the resolve stage. */
|
|
45
46
|
export type Resolved = Plan;
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
export
|
|
49
|
-
rule: string;
|
|
50
|
-
message: string;
|
|
51
|
-
}
|
|
48
|
+
/** Source validation warning — alias of the canonical type from types.ts. */
|
|
49
|
+
export type ValidationWarning = import("../types.ts").ValidationWarning;
|
|
52
50
|
|
|
53
51
|
/** Output of the validate stage. */
|
|
54
52
|
export type Validated = Resolved & {
|
|
@@ -97,7 +95,7 @@ export namespace WorkflowLoader {
|
|
|
97
95
|
ok: false,
|
|
98
96
|
stage: "resolve",
|
|
99
97
|
error,
|
|
100
|
-
message:
|
|
98
|
+
message: errorMessage(error),
|
|
101
99
|
};
|
|
102
100
|
}
|
|
103
101
|
}
|
|
@@ -115,8 +113,10 @@ export namespace WorkflowLoader {
|
|
|
115
113
|
return validateOpenCodeWorkflow(source);
|
|
116
114
|
case "claude":
|
|
117
115
|
return validateClaudeWorkflow(source);
|
|
118
|
-
default:
|
|
119
|
-
|
|
116
|
+
default: {
|
|
117
|
+
const _exhaustive: never = agent;
|
|
118
|
+
return _exhaustive;
|
|
119
|
+
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -140,7 +140,7 @@ export namespace WorkflowLoader {
|
|
|
140
140
|
ok: false,
|
|
141
141
|
stage: "validate",
|
|
142
142
|
error,
|
|
143
|
-
message:
|
|
143
|
+
message: errorMessage(error),
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -162,26 +162,21 @@ export namespace WorkflowLoader {
|
|
|
162
162
|
|
|
163
163
|
if (!definition || definition.__brand !== "WorkflowDefinition") {
|
|
164
164
|
if (definition && definition.__brand === "WorkflowBuilder") {
|
|
165
|
+
const err = new WorkflowNotCompiledError(validated.path);
|
|
165
166
|
return {
|
|
166
167
|
ok: false,
|
|
167
168
|
stage: "load",
|
|
168
|
-
error:
|
|
169
|
-
message:
|
|
170
|
-
`Workflow at ${validated.path} was defined but not compiled.\n` +
|
|
171
|
-
` Add .compile() at the end of your defineWorkflow() chain:\n\n` +
|
|
172
|
-
` export default defineWorkflow({ ... })\n` +
|
|
173
|
-
` .run(async (ctx) => { ... })\n` +
|
|
174
|
-
` .compile();`,
|
|
169
|
+
error: err,
|
|
170
|
+
message: err.message,
|
|
175
171
|
};
|
|
176
172
|
}
|
|
177
173
|
|
|
174
|
+
const err = new InvalidWorkflowError(validated.path);
|
|
178
175
|
return {
|
|
179
176
|
ok: false,
|
|
180
177
|
stage: "load",
|
|
181
|
-
error:
|
|
182
|
-
message:
|
|
183
|
-
`${validated.path} does not export a valid WorkflowDefinition.\n` +
|
|
184
|
-
` Make sure it exports defineWorkflow(...).run(...).compile() as the default export.`,
|
|
178
|
+
error: err,
|
|
179
|
+
message: err.message,
|
|
185
180
|
};
|
|
186
181
|
}
|
|
187
182
|
|
|
@@ -194,7 +189,7 @@ export namespace WorkflowLoader {
|
|
|
194
189
|
ok: false,
|
|
195
190
|
stage: "load",
|
|
196
191
|
error,
|
|
197
|
-
message:
|
|
192
|
+
message: errorMessage(error),
|
|
198
193
|
};
|
|
199
194
|
}
|
|
200
195
|
}
|
package/src/sdk/runtime/tmux.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* sending keystrokes, and pane state detection.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { writeFileSync, unlinkSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
10
12
|
import type { Subprocess } from "bun";
|
|
11
13
|
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
@@ -19,6 +21,11 @@ export const SOCKET_NAME = "atomic";
|
|
|
19
21
|
/** Path to the bundled tmux config (shared by tmux and psmux). */
|
|
20
22
|
const CONFIG_PATH = join(import.meta.dir, "tmux.conf");
|
|
21
23
|
|
|
24
|
+
/** Discriminated result from a tmux command execution. */
|
|
25
|
+
export type TmuxResult =
|
|
26
|
+
| { ok: true; stdout: string }
|
|
27
|
+
| { ok: false; stderr: string };
|
|
28
|
+
|
|
22
29
|
// ---------------------------------------------------------------------------
|
|
23
30
|
// Core tmux primitives
|
|
24
31
|
// ---------------------------------------------------------------------------
|
|
@@ -81,7 +88,7 @@ export function isInsideTmux(): boolean {
|
|
|
81
88
|
* Prefers this over the throwing `tmux()` for cases where callers
|
|
82
89
|
* need to handle failure gracefully.
|
|
83
90
|
*/
|
|
84
|
-
export function tmuxRun(args: string[]):
|
|
91
|
+
export function tmuxRun(args: string[]): TmuxResult {
|
|
85
92
|
const binary = getMuxBinary();
|
|
86
93
|
if (!binary) {
|
|
87
94
|
return { ok: false, stderr: "No terminal multiplexer (tmux/psmux) found on PATH" };
|
|
@@ -93,10 +100,9 @@ export function tmuxRun(args: string[]): { ok: true; stdout: string } | { ok: fa
|
|
|
93
100
|
stderr: "pipe",
|
|
94
101
|
});
|
|
95
102
|
if (!result.success) {
|
|
96
|
-
|
|
97
|
-
return { ok: false, stderr };
|
|
103
|
+
return { ok: false, stderr: result.stderr.toString().trim() };
|
|
98
104
|
}
|
|
99
|
-
return { ok: true, stdout:
|
|
105
|
+
return { ok: true, stdout: result.stdout.toString().trim() };
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
@@ -223,14 +229,64 @@ export function createPane(sessionName: string, command: string): string {
|
|
|
223
229
|
// Keystroke sending
|
|
224
230
|
// ---------------------------------------------------------------------------
|
|
225
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Maximum bytes per `send-keys -l` invocation.
|
|
234
|
+
*
|
|
235
|
+
* tmux passes the text as a single command-line argument to the child
|
|
236
|
+
* process. On Linux the per-argument limit (`MAX_ARG_STRLEN`) is 128 KB;
|
|
237
|
+
* on macOS the total `ARG_MAX` is ~1 MB but shared across all args.
|
|
238
|
+
* We stay well under both limits with 50 KB chunks.
|
|
239
|
+
*/
|
|
240
|
+
const SEND_KEYS_CHUNK_SIZE = 50_000;
|
|
241
|
+
|
|
226
242
|
/**
|
|
227
243
|
* Send literal text to a tmux pane using `-l` flag (no special key interpretation).
|
|
228
244
|
* Uses `--` to prevent text starting with `-` from being parsed as flags.
|
|
245
|
+
*
|
|
246
|
+
* Long texts are chunked to avoid OS `ARG_MAX` / `MAX_ARG_STRLEN` limits
|
|
247
|
+
* that cause `tmux send-keys` to fail with "command too long".
|
|
229
248
|
*/
|
|
230
249
|
export function sendLiteralText(paneId: string, text: string): void {
|
|
231
250
|
// Replace newlines with spaces to avoid premature submission
|
|
232
251
|
const normalized = text.replace(/[\r\n]+/g, " ");
|
|
233
|
-
|
|
252
|
+
|
|
253
|
+
if (normalized.length <= SEND_KEYS_CHUNK_SIZE) {
|
|
254
|
+
tmuxExec(["send-keys", "-t", paneId, "-l", "--", normalized]);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (let offset = 0; offset < normalized.length; offset += SEND_KEYS_CHUNK_SIZE) {
|
|
259
|
+
const chunk = normalized.slice(offset, offset + SEND_KEYS_CHUNK_SIZE);
|
|
260
|
+
tmuxExec(["send-keys", "-t", paneId, "-l", "--", chunk]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Send text to a tmux pane via the paste buffer.
|
|
266
|
+
*
|
|
267
|
+
* More reliable than `send-keys -l` for large text:
|
|
268
|
+
* - No OS ARG_MAX / MAX_ARG_STRLEN limits (text goes through a temp file)
|
|
269
|
+
* - Atomic delivery — the entire text is pasted at once
|
|
270
|
+
* - No chunking needed
|
|
271
|
+
*
|
|
272
|
+
* Newlines are normalized to spaces to prevent premature submission,
|
|
273
|
+
* matching `sendLiteralText`'s behavior.
|
|
274
|
+
*/
|
|
275
|
+
export function sendViaPasteBuffer(paneId: string, text: string): void {
|
|
276
|
+
const normalized = text.replace(/[\r\n]+/g, " ");
|
|
277
|
+
const tmp = join(tmpdir(), `atomic-paste-${process.pid}-${Date.now()}.txt`);
|
|
278
|
+
|
|
279
|
+
writeFileSync(tmp, normalized, "utf-8");
|
|
280
|
+
try {
|
|
281
|
+
tmuxExec(["load-buffer", tmp]);
|
|
282
|
+
tmuxExec(["paste-buffer", "-t", paneId, "-d"]);
|
|
283
|
+
} finally {
|
|
284
|
+
try {
|
|
285
|
+
unlinkSync(tmp);
|
|
286
|
+
} catch {
|
|
287
|
+
// Temp file cleanup is best-effort
|
|
288
|
+
}
|
|
289
|
+
}
|
|
234
290
|
}
|
|
235
291
|
|
|
236
292
|
/**
|
|
@@ -247,17 +303,17 @@ export function sendSpecialKey(paneId: string, key: string): void {
|
|
|
247
303
|
* @param presses - Number of C-m presses (default: 1)
|
|
248
304
|
* @param delayMs - Delay between presses in ms (default: 100)
|
|
249
305
|
*/
|
|
250
|
-
export function sendKeysAndSubmit(
|
|
306
|
+
export async function sendKeysAndSubmit(
|
|
251
307
|
paneId: string,
|
|
252
308
|
text: string,
|
|
253
309
|
presses = 1,
|
|
254
310
|
delayMs = 100
|
|
255
|
-
): void {
|
|
311
|
+
): Promise<void> {
|
|
256
312
|
sendLiteralText(paneId, text);
|
|
257
313
|
|
|
258
314
|
for (let i = 0; i < presses; i++) {
|
|
259
315
|
if (i > 0 && delayMs > 0) {
|
|
260
|
-
Bun.
|
|
316
|
+
await Bun.sleep(delayMs);
|
|
261
317
|
}
|
|
262
318
|
sendSpecialKey(paneId, "C-m");
|
|
263
319
|
}
|
|
@@ -281,6 +337,16 @@ export function capturePane(paneId: string, start?: number): string {
|
|
|
281
337
|
return tmux(args);
|
|
282
338
|
}
|
|
283
339
|
|
|
340
|
+
/** Internal capture helper — returns empty string on failure. */
|
|
341
|
+
function capturePaneRaw(paneId: string, scrollbackLines?: number): string {
|
|
342
|
+
const args = ["capture-pane", "-t", paneId, "-p"];
|
|
343
|
+
if (scrollbackLines !== undefined) {
|
|
344
|
+
args.push("-S", `-${scrollbackLines}`);
|
|
345
|
+
}
|
|
346
|
+
const result = tmuxRun(args);
|
|
347
|
+
return result.ok ? result.stdout : "";
|
|
348
|
+
}
|
|
349
|
+
|
|
284
350
|
/**
|
|
285
351
|
* Capture only the visible portion of a pane (no scrollback).
|
|
286
352
|
* Preferred for state detection (ready/busy) to avoid stale prompt lines
|
|
@@ -288,9 +354,7 @@ export function capturePane(paneId: string, start?: number): string {
|
|
|
288
354
|
* Returns empty string on failure instead of throwing.
|
|
289
355
|
*/
|
|
290
356
|
export function capturePaneVisible(paneId: string): string {
|
|
291
|
-
|
|
292
|
-
if (!result.ok) return "";
|
|
293
|
-
return result.stdout;
|
|
357
|
+
return capturePaneRaw(paneId);
|
|
294
358
|
}
|
|
295
359
|
|
|
296
360
|
/**
|
|
@@ -299,9 +363,7 @@ export function capturePaneVisible(paneId: string): string {
|
|
|
299
363
|
* Returns empty string on failure instead of throwing.
|
|
300
364
|
*/
|
|
301
365
|
export function capturePaneScrollback(paneId: string, lines = 200): string {
|
|
302
|
-
|
|
303
|
-
if (!result.ok) return "";
|
|
304
|
-
return result.stdout;
|
|
366
|
+
return capturePaneRaw(paneId, lines);
|
|
305
367
|
}
|
|
306
368
|
|
|
307
369
|
// ---------------------------------------------------------------------------
|
|
@@ -336,22 +398,28 @@ export function sessionExists(sessionName: string): boolean {
|
|
|
336
398
|
return result.ok;
|
|
337
399
|
}
|
|
338
400
|
|
|
339
|
-
/**
|
|
340
|
-
|
|
341
|
-
*/
|
|
342
|
-
export function attachSession(sessionName: string): void {
|
|
401
|
+
/** Build the full argument list for an attach-session command. */
|
|
402
|
+
function buildAttachArgs(sessionName: string): string[] {
|
|
343
403
|
const binary = getMuxBinary();
|
|
344
404
|
if (!binary) {
|
|
345
405
|
throw new Error("No terminal multiplexer (tmux/psmux) found on PATH");
|
|
346
406
|
}
|
|
407
|
+
return [binary, "-f", CONFIG_PATH, "-L", SOCKET_NAME, "attach-session", "-t", sessionName];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Attach to an existing tmux session (takes over the current terminal).
|
|
412
|
+
*/
|
|
413
|
+
export function attachSession(sessionName: string): void {
|
|
414
|
+
const cmd = buildAttachArgs(sessionName);
|
|
347
415
|
const proc = Bun.spawnSync({
|
|
348
|
-
cmd
|
|
416
|
+
cmd,
|
|
349
417
|
stdin: "inherit",
|
|
350
418
|
stdout: "inherit",
|
|
351
419
|
stderr: "pipe",
|
|
352
420
|
});
|
|
353
421
|
if (!proc.success) {
|
|
354
|
-
const stderr =
|
|
422
|
+
const stderr = proc.stderr.toString().trim();
|
|
355
423
|
throw new Error(`Failed to attach to session: ${sessionName}${stderr ? ` (${stderr})` : ""}`);
|
|
356
424
|
}
|
|
357
425
|
}
|
|
@@ -362,14 +430,9 @@ export function attachSession(sessionName: string): void {
|
|
|
362
430
|
* Used by all async attach call sites (executor, chat).
|
|
363
431
|
*/
|
|
364
432
|
export function spawnMuxAttach(sessionName: string): Subprocess {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
return Bun.spawn(
|
|
370
|
-
[binary, "-f", CONFIG_PATH, "-L", SOCKET_NAME, "attach-session", "-t", sessionName],
|
|
371
|
-
{ stdio: ["inherit", "inherit", "inherit"] },
|
|
372
|
-
);
|
|
433
|
+
return Bun.spawn(buildAttachArgs(sessionName), {
|
|
434
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
435
|
+
});
|
|
373
436
|
}
|
|
374
437
|
|
|
375
438
|
/**
|
|
@@ -485,10 +548,10 @@ export function paneHasActiveTask(captured: string): boolean {
|
|
|
485
548
|
.map((line) => line.trim())
|
|
486
549
|
.slice(-40);
|
|
487
550
|
|
|
488
|
-
if (tail.some((l) => /\b\d+\s+background terminal running\b/i.test(l))) return true;
|
|
489
|
-
if (tail.some((l) => /esc to interrupt/i.test(l))) return true;
|
|
490
|
-
if (tail.some((l) => /\bbackground terminal running\b/i.test(l))) return true;
|
|
491
551
|
return tail.some((l) =>
|
|
552
|
+
/\b\d+\s+background terminal running\b/i.test(l) ||
|
|
553
|
+
/esc to interrupt/i.test(l) ||
|
|
554
|
+
/\bbackground terminal running\b/i.test(l) ||
|
|
492
555
|
/^[·✻]\s+[A-Za-z][A-Za-z0-9''-]*(?:\s+[A-Za-z][A-Za-z0-9''-]*){0,3}(?:…|\.{3})$/u.test(l),
|
|
493
556
|
);
|
|
494
557
|
}
|
package/src/sdk/types.ts
CHANGED
|
@@ -95,6 +95,51 @@ export type {
|
|
|
95
95
|
ClaudeQueryDefaults,
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
+
// ─── Validation ─────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/** A source validation warning emitted by provider-specific workflow validators. */
|
|
101
|
+
export interface ValidationWarning {
|
|
102
|
+
rule: string;
|
|
103
|
+
message: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A declarative validation rule: pattern to match + warning to emit. */
|
|
107
|
+
export interface ValidationRule {
|
|
108
|
+
pattern: RegExp;
|
|
109
|
+
rule: string;
|
|
110
|
+
message: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run a set of regex-based validation rules against workflow source code.
|
|
115
|
+
* Returns a warning for each matching pattern.
|
|
116
|
+
*/
|
|
117
|
+
export function validateWorkflowSource(
|
|
118
|
+
source: string,
|
|
119
|
+
rules: readonly ValidationRule[],
|
|
120
|
+
): ValidationWarning[] {
|
|
121
|
+
// Strip single-line comments to avoid false positives from patterns
|
|
122
|
+
// that appear only in comments (e.g., a comment mentioning claudeQuery).
|
|
123
|
+
const stripped = source.replace(/\/\/.*$/gm, "");
|
|
124
|
+
const warnings: ValidationWarning[] = [];
|
|
125
|
+
for (const { pattern, rule, message } of rules) {
|
|
126
|
+
if (pattern.test(stripped)) {
|
|
127
|
+
warnings.push({ rule, message });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return warnings;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a provider-specific workflow validator from a set of rules.
|
|
135
|
+
* Eliminates boilerplate — each provider file only needs to declare its rules.
|
|
136
|
+
*/
|
|
137
|
+
export function createProviderValidator(
|
|
138
|
+
rules: readonly ValidationRule[],
|
|
139
|
+
): (source: string) => ValidationWarning[] {
|
|
140
|
+
return (source) => validateWorkflowSource(source, rules);
|
|
141
|
+
}
|
|
142
|
+
|
|
98
143
|
// ─── Workflow input schemas ─────────────────────────────────────────────────
|
|
99
144
|
|
|
100
145
|
/**
|
|
@@ -79,6 +79,23 @@ import {
|
|
|
79
79
|
slugifyPrompt,
|
|
80
80
|
} from "../helpers/prompts.ts";
|
|
81
81
|
|
|
82
|
+
// ── Timeouts ────────────────────────────────────────────────────────────────
|
|
83
|
+
// Every s.session.query() call passes one of these explicitly — never relying
|
|
84
|
+
// on the 300-second default. Explorer and aggregator stages dispatch sub-agents
|
|
85
|
+
// and can easily run 30+ minutes; a premature timeout causes the stage to
|
|
86
|
+
// complete early, which makes Promise.all resolve and the next stage to launch
|
|
87
|
+
// before parallel stages finish.
|
|
88
|
+
const SCOUT_TIMEOUT_MS = 15 * 60 * 1000; // 15 min — short orientation call
|
|
89
|
+
const HISTORY_TIMEOUT_MS = 20 * 60 * 1000; // 20 min — reads research/ docs
|
|
90
|
+
const EXPLORER_TIMEOUT_MS = 45 * 60 * 1000; // 45 min — multi-step sub-agent dispatch
|
|
91
|
+
const AGGREGATOR_TIMEOUT_MS = 45 * 60 * 1000; // 45 min — reads N explorer reports
|
|
92
|
+
|
|
93
|
+
// Between sub-agent dispatches Claude's TUI briefly shows the prompt indicator
|
|
94
|
+
// without an active-task spinner. Requiring 3 consecutive idle detections
|
|
95
|
+
// prevents the query from returning during these transient gaps.
|
|
96
|
+
const EXPLORER_IDLE_CONFIRM = 3;
|
|
97
|
+
const AGGREGATOR_IDLE_CONFIRM = 3;
|
|
98
|
+
|
|
82
99
|
export default defineWorkflow<"claude">({
|
|
83
100
|
name: "deep-research-codebase",
|
|
84
101
|
description:
|
|
@@ -149,6 +166,7 @@ export default defineWorkflow<"claude">({
|
|
|
149
166
|
explorerCount: actualCount,
|
|
150
167
|
partitionPreview: partitions,
|
|
151
168
|
}),
|
|
169
|
+
{ timeoutMs: SCOUT_TIMEOUT_MS },
|
|
152
170
|
);
|
|
153
171
|
s.save(s.sessionId);
|
|
154
172
|
|
|
@@ -177,6 +195,7 @@ export default defineWorkflow<"claude">({
|
|
|
177
195
|
// synthesis as prose (no file write — consumed via transcript).
|
|
178
196
|
await s.session.query(
|
|
179
197
|
buildHistoryPrompt({ question: prompt, root }),
|
|
198
|
+
{ timeoutMs: HISTORY_TIMEOUT_MS },
|
|
180
199
|
);
|
|
181
200
|
s.save(s.sessionId);
|
|
182
201
|
},
|
|
@@ -236,6 +255,10 @@ export default defineWorkflow<"claude">({
|
|
|
236
255
|
scratchPath,
|
|
237
256
|
root,
|
|
238
257
|
}),
|
|
258
|
+
{
|
|
259
|
+
timeoutMs: EXPLORER_TIMEOUT_MS,
|
|
260
|
+
idleConfirmCount: EXPLORER_IDLE_CONFIRM,
|
|
261
|
+
},
|
|
239
262
|
);
|
|
240
263
|
s.save(s.sessionId);
|
|
241
264
|
|
|
@@ -286,6 +309,10 @@ export default defineWorkflow<"claude">({
|
|
|
286
309
|
scoutOverview,
|
|
287
310
|
historyOverview,
|
|
288
311
|
}),
|
|
312
|
+
{
|
|
313
|
+
timeoutMs: AGGREGATOR_TIMEOUT_MS,
|
|
314
|
+
idleConfirmCount: AGGREGATOR_IDLE_CONFIRM,
|
|
315
|
+
},
|
|
289
316
|
);
|
|
290
317
|
s.save(s.sessionId);
|
|
291
318
|
},
|
|
@@ -137,9 +137,10 @@ export function buildExplorerPrompt(opts: {
|
|
|
137
137
|
.map((u) => `\`${path.join(opts.root, u.path)}\``)
|
|
138
138
|
.join(", ");
|
|
139
139
|
|
|
140
|
-
const orientation =
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
const orientation =
|
|
141
|
+
opts.scoutOverview.trim().length > 0
|
|
142
|
+
? opts.scoutOverview.trim()
|
|
143
|
+
: "(scout overview unavailable — proceed without)";
|
|
143
144
|
|
|
144
145
|
return [
|
|
145
146
|
`<RESEARCH_QUESTION>`,
|
|
@@ -418,13 +419,15 @@ export function buildExplorerPromptGeneric(opts: {
|
|
|
418
419
|
.map((u) => `\`${path.join(opts.root, u.path)}\``)
|
|
419
420
|
.join(", ");
|
|
420
421
|
|
|
421
|
-
const orientation =
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
const orientation =
|
|
423
|
+
opts.scoutOverview.trim().length > 0
|
|
424
|
+
? opts.scoutOverview.trim()
|
|
425
|
+
: "(scout overview unavailable — proceed without)";
|
|
424
426
|
|
|
425
|
-
const history =
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
const history =
|
|
428
|
+
opts.historyOverview.trim().length > 0
|
|
429
|
+
? opts.historyOverview.trim()
|
|
430
|
+
: "(no historical research surfaced)";
|
|
428
431
|
|
|
429
432
|
return [
|
|
430
433
|
`<RESEARCH_QUESTION>`,
|
|
@@ -670,7 +673,11 @@ export function buildAggregatorPrompt(opts: {
|
|
|
670
673
|
totalLoc: number;
|
|
671
674
|
totalFiles: number;
|
|
672
675
|
explorerCount: number;
|
|
673
|
-
explorerFiles: {
|
|
676
|
+
explorerFiles: {
|
|
677
|
+
index: number;
|
|
678
|
+
scratchPath: string;
|
|
679
|
+
partition: PartitionUnit[];
|
|
680
|
+
}[];
|
|
674
681
|
finalPath: string;
|
|
675
682
|
scoutOverview: string;
|
|
676
683
|
historyOverview: string;
|
|
@@ -682,13 +689,15 @@ export function buildAggregatorPrompt(opts: {
|
|
|
682
689
|
})
|
|
683
690
|
.join("\n");
|
|
684
691
|
|
|
685
|
-
const orientation =
|
|
686
|
-
|
|
687
|
-
|
|
692
|
+
const orientation =
|
|
693
|
+
opts.scoutOverview.trim().length > 0
|
|
694
|
+
? opts.scoutOverview.trim()
|
|
695
|
+
: "(scout overview unavailable)";
|
|
688
696
|
|
|
689
|
-
const history =
|
|
690
|
-
|
|
691
|
-
|
|
697
|
+
const history =
|
|
698
|
+
opts.historyOverview.trim().length > 0
|
|
699
|
+
? opts.historyOverview.trim()
|
|
700
|
+
: "(no historical research surfaced)";
|
|
692
701
|
|
|
693
702
|
return [
|
|
694
703
|
`<RESEARCH_QUESTION>`,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Everything here is pure TypeScript + child_process — no LLM calls.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Use Bun.spawnSync instead of node:child_process for consistency with the rest of the codebase.
|
|
17
17
|
|
|
18
18
|
/** Source-file extensions we treat as "code" for LOC accounting. */
|
|
19
19
|
const CODE_EXTENSIONS = new Set<string>([
|
|
@@ -72,12 +72,13 @@ export type CodebaseScout = {
|
|
|
72
72
|
|
|
73
73
|
/** Resolve the project root. Prefers `git rev-parse --show-toplevel`. */
|
|
74
74
|
export function getCodebaseRoot(): string {
|
|
75
|
-
const r = spawnSync(
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const r = Bun.spawnSync({
|
|
76
|
+
cmd: ["git", "rev-parse", "--show-toplevel"],
|
|
77
|
+
stdout: "pipe",
|
|
78
|
+
stderr: "pipe",
|
|
78
79
|
});
|
|
79
|
-
if (r.
|
|
80
|
-
return r.stdout.trim();
|
|
80
|
+
if (r.success && r.stdout) {
|
|
81
|
+
return r.stdout.toString().trim();
|
|
81
82
|
}
|
|
82
83
|
return process.cwd();
|
|
83
84
|
}
|
|
@@ -91,29 +92,29 @@ function isCodeFile(p: string): boolean {
|
|
|
91
92
|
|
|
92
93
|
/** List all files in the repository. Prefers git ls-files (respects .gitignore). */
|
|
93
94
|
function listAllFiles(root: string): string[] {
|
|
94
|
-
const git = spawnSync(
|
|
95
|
+
const git = Bun.spawnSync({
|
|
96
|
+
cmd: ["git", "ls-files"],
|
|
95
97
|
cwd: root,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
98
|
+
stdout: "pipe",
|
|
99
|
+
stderr: "pipe",
|
|
99
100
|
});
|
|
100
|
-
if (git.
|
|
101
|
-
return git.stdout.split("\n").filter((l) => l.length > 0);
|
|
101
|
+
if (git.success && git.stdout) {
|
|
102
|
+
return git.stdout.toString().split("\n").filter((l) => l.length > 0);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// Fallback: shell out to find with the standard ignore patterns.
|
|
105
|
-
const args: string[] = [".", "-type", "f"];
|
|
106
|
+
const args: string[] = ["find", ".", "-type", "f"];
|
|
106
107
|
for (const pattern of FIND_IGNORE_PATTERNS) {
|
|
107
108
|
args.push("-not", "-path", `*/${pattern}/*`);
|
|
108
109
|
}
|
|
109
|
-
const find = spawnSync(
|
|
110
|
+
const find = Bun.spawnSync({
|
|
111
|
+
cmd: args,
|
|
110
112
|
cwd: root,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
113
|
+
stdout: "pipe",
|
|
114
|
+
stderr: "pipe",
|
|
114
115
|
});
|
|
115
|
-
if (find.
|
|
116
|
-
return find.stdout
|
|
116
|
+
if (find.success && find.stdout) {
|
|
117
|
+
return find.stdout.toString()
|
|
117
118
|
.split("\n")
|
|
118
119
|
.map((p) => p.replace(/^\.\//, ""))
|
|
119
120
|
.filter((p) => p.length > 0);
|
|
@@ -135,14 +136,14 @@ function countLines(root: string, files: string[]): Map<string, number> {
|
|
|
135
136
|
const BATCH = 200;
|
|
136
137
|
for (let i = 0; i < files.length; i += BATCH) {
|
|
137
138
|
const batch = files.slice(i, i + BATCH);
|
|
138
|
-
const r = spawnSync(
|
|
139
|
+
const r = Bun.spawnSync({
|
|
140
|
+
cmd: ["wc", "-l", "--", ...batch],
|
|
139
141
|
cwd: root,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
142
|
+
stdout: "pipe",
|
|
143
|
+
stderr: "pipe",
|
|
143
144
|
});
|
|
144
145
|
if (!r.stdout) continue;
|
|
145
|
-
for (const line of r.stdout.split("\n")) {
|
|
146
|
+
for (const line of r.stdout.toString().split("\n")) {
|
|
146
147
|
const m = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
147
148
|
// Regex groups are typed `string | undefined` under strict mode even
|
|
148
149
|
// when the whole match succeeded — guard explicitly.
|
|
@@ -26,6 +26,10 @@ import { safeGitStatusS } from "../helpers/git.ts";
|
|
|
26
26
|
const MAX_LOOPS = 10;
|
|
27
27
|
const CONSECUTIVE_CLEAN_THRESHOLD = 2;
|
|
28
28
|
|
|
29
|
+
// The orchestrator stage implements the actual code changes and can run for
|
|
30
|
+
// a very long time on large tasks. 24 hours prevents premature timeout.
|
|
31
|
+
const ORCHESTRATOR_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
32
|
+
|
|
29
33
|
/** Wrap a prompt with a Claude Code @-mention so the named sub-agent runs it. */
|
|
30
34
|
function asAgentCall(agentName: string, prompt: string): string {
|
|
31
35
|
return `@"${agentName} (agent)" ${prompt}`;
|
|
@@ -78,6 +82,7 @@ export default defineWorkflow<"claude">({
|
|
|
78
82
|
"orchestrator",
|
|
79
83
|
buildOrchestratorPrompt(prompt),
|
|
80
84
|
),
|
|
85
|
+
{ timeoutMs: ORCHESTRATOR_TIMEOUT_MS },
|
|
81
86
|
);
|
|
82
87
|
s.save(s.sessionId);
|
|
83
88
|
},
|
|
@@ -10,6 +10,7 @@ export { defineWorkflow, WorkflowBuilder } from "../define-workflow.ts";
|
|
|
10
10
|
|
|
11
11
|
export type {
|
|
12
12
|
AgentType,
|
|
13
|
+
ValidationWarning,
|
|
13
14
|
Transcript,
|
|
14
15
|
SavedMessage,
|
|
15
16
|
SaveTranscript,
|
|
@@ -44,15 +45,14 @@ export type { SessionMessage as ClaudeSessionMessage } from "@anthropic-ai/claud
|
|
|
44
45
|
|
|
45
46
|
// Providers
|
|
46
47
|
export { createClaudeSession, claudeQuery, clearClaudeSession, validateClaudeWorkflow } from "../providers/claude.ts";
|
|
47
|
-
export type { ClaudeSessionOptions, ClaudeQueryOptions, ClaudeQueryResult
|
|
48
|
+
export type { ClaudeSessionOptions, ClaudeQueryOptions, ClaudeQueryResult } from "../providers/claude.ts";
|
|
48
49
|
|
|
49
50
|
export { validateCopilotWorkflow } from "../providers/copilot.ts";
|
|
50
|
-
export type { CopilotValidationWarning } from "../providers/copilot.ts";
|
|
51
51
|
|
|
52
52
|
export { validateOpenCodeWorkflow } from "../providers/opencode.ts";
|
|
53
|
-
export type { OpenCodeValidationWarning } from "../providers/opencode.ts";
|
|
54
53
|
|
|
55
54
|
// Runtime — tmux utilities
|
|
55
|
+
export type { TmuxResult } from "../runtime/tmux.ts";
|
|
56
56
|
export {
|
|
57
57
|
SOCKET_NAME,
|
|
58
58
|
isTmuxInstalled,
|