@bastani/atomic 0.6.5 → 0.6.6-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/.agents/skills/ado-commit/SKILL.md +2 -0
- package/.agents/skills/ado-create-pr/SKILL.md +2 -0
- package/.agents/skills/advanced-evaluation/SKILL.md +2 -0
- package/.agents/skills/ast-grep/SKILL.md +2 -0
- package/.agents/skills/bdi-mental-states/SKILL.md +2 -0
- package/.agents/skills/bun/SKILL.md +156 -122
- package/.agents/skills/context-compression/SKILL.md +2 -0
- package/.agents/skills/context-degradation/SKILL.md +2 -0
- package/.agents/skills/context-fundamentals/SKILL.md +2 -0
- package/.agents/skills/context-optimization/SKILL.md +2 -0
- package/.agents/skills/create-spec/SKILL.md +2 -0
- package/.agents/skills/docx/SKILL.md +2 -0
- package/.agents/skills/evaluation/SKILL.md +2 -0
- package/.agents/skills/explain-code/SKILL.md +2 -0
- package/.agents/skills/filesystem-context/SKILL.md +2 -0
- package/.agents/skills/find-skills/SKILL.md +2 -0
- package/.agents/skills/gh-commit/SKILL.md +2 -0
- package/.agents/skills/gh-create-pr/SKILL.md +2 -0
- package/.agents/skills/hosted-agents/SKILL.md +2 -0
- package/.agents/skills/impeccable/SKILL.md +117 -304
- package/.agents/skills/impeccable/agents/openai.yaml +4 -0
- package/.agents/skills/{adapt/SKILL.md → impeccable/reference/adapt.md} +2 -11
- package/.agents/skills/{animate/SKILL.md → impeccable/reference/animate.md} +15 -15
- package/.agents/skills/{audit/SKILL.md → impeccable/reference/audit.md} +8 -22
- package/.agents/skills/{bolder/SKILL.md → impeccable/reference/bolder.md} +9 -13
- package/.agents/skills/impeccable/reference/brand.md +114 -0
- package/.agents/skills/{clarify/SKILL.md → impeccable/reference/clarify.md} +2 -11
- package/.agents/skills/{colorize/SKILL.md → impeccable/reference/colorize.md} +23 -12
- package/.agents/skills/impeccable/reference/craft.md +152 -29
- package/.agents/skills/{critique/SKILL.md → impeccable/reference/critique.md} +25 -37
- package/.agents/skills/{delight/SKILL.md → impeccable/reference/delight.md} +9 -11
- package/.agents/skills/{distill/SKILL.md → impeccable/reference/distill.md} +2 -13
- package/.agents/skills/impeccable/reference/document.md +427 -0
- package/.agents/skills/impeccable/reference/extract.md +1 -1
- package/.agents/skills/{harden/SKILL.md → impeccable/reference/harden.md} +1 -43
- package/.agents/skills/{layout/SKILL.md → impeccable/reference/layout.md} +27 -11
- package/.agents/skills/impeccable/reference/live.md +594 -0
- package/.agents/skills/impeccable/reference/motion-design.md +12 -2
- package/.agents/skills/impeccable/reference/onboard.md +234 -0
- package/.agents/skills/{optimize/SKILL.md → impeccable/reference/optimize.md} +4 -12
- package/.agents/skills/{overdrive/SKILL.md → impeccable/reference/overdrive.md} +9 -21
- package/.agents/skills/{critique → impeccable}/reference/personas.md +1 -1
- package/.agents/skills/{polish/SKILL.md → impeccable/reference/polish.md} +31 -23
- package/.agents/skills/impeccable/reference/product.md +62 -0
- package/.agents/skills/{quieter/SKILL.md → impeccable/reference/quieter.md} +7 -11
- package/.agents/skills/impeccable/reference/shape.md +151 -0
- package/.agents/skills/impeccable/reference/teach.md +156 -0
- package/.agents/skills/{typeset/SKILL.md → impeccable/reference/typeset.md} +19 -11
- package/.agents/skills/impeccable/reference/typography.md +31 -14
- package/.agents/skills/impeccable/scripts/cleanup-deprecated.mjs +87 -17
- package/.agents/skills/impeccable/scripts/command-metadata.json +94 -0
- package/.agents/skills/impeccable/scripts/design-parser.mjs +820 -0
- package/.agents/skills/impeccable/scripts/detect-csp.mjs +198 -0
- package/.agents/skills/impeccable/scripts/is-generated.mjs +69 -0
- package/.agents/skills/impeccable/scripts/live-accept.mjs +595 -0
- package/.agents/skills/impeccable/scripts/live-browser.js +4781 -0
- package/.agents/skills/impeccable/scripts/live-inject.mjs +445 -0
- package/.agents/skills/impeccable/scripts/live-poll.mjs +186 -0
- package/.agents/skills/impeccable/scripts/live-server.mjs +694 -0
- package/.agents/skills/impeccable/scripts/live-wrap.mjs +571 -0
- package/.agents/skills/impeccable/scripts/live.mjs +247 -0
- package/.agents/skills/impeccable/scripts/load-context.mjs +141 -0
- package/.agents/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/.agents/skills/impeccable/scripts/pin.mjs +214 -0
- package/.agents/skills/init/SKILL.md +2 -0
- package/.agents/skills/liteparse/SKILL.md +1 -0
- package/.agents/skills/memory-systems/SKILL.md +2 -0
- package/.agents/skills/multi-agent-patterns/SKILL.md +2 -0
- package/.agents/skills/opentui/SKILL.md +1 -0
- package/.agents/skills/pdf/SKILL.md +2 -0
- package/.agents/skills/playwright-cli/SKILL.md +51 -5
- package/.agents/skills/playwright-cli/references/playwright-tests.md +1 -1
- package/.agents/skills/playwright-cli/references/running-code.md +10 -0
- package/.agents/skills/playwright-cli/references/session-management.md +56 -0
- package/.agents/skills/playwright-cli/references/spec-driven-testing.md +305 -0
- package/.agents/skills/playwright-cli/references/test-generation.md +49 -3
- package/.agents/skills/pptx/SKILL.md +2 -0
- package/.agents/skills/project-development/SKILL.md +2 -0
- package/.agents/skills/prompt-engineer/SKILL.md +2 -0
- package/.agents/skills/research-codebase/SKILL.md +2 -0
- package/.agents/skills/ripgrep/SKILL.md +2 -0
- package/.agents/skills/skill-creator/LICENSE.txt +1 -1
- package/.agents/skills/skill-creator/SKILL.md +2 -0
- package/.agents/skills/sl-commit/SKILL.md +2 -0
- package/.agents/skills/sl-submit-diff/SKILL.md +2 -0
- package/.agents/skills/tdd/SKILL.md +4 -0
- package/.agents/skills/tool-design/SKILL.md +2 -0
- package/.agents/skills/typescript-advanced-types/SKILL.md +2 -1
- package/.agents/skills/typescript-expert/SKILL.md +7 -1
- package/.agents/skills/typescript-react-reviewer/SKILL.md +2 -1
- package/.agents/skills/workflow-creator/SKILL.md +75 -72
- package/.agents/skills/workflow-creator/references/session-config.md +48 -1
- package/.agents/skills/xlsx/SKILL.md +2 -0
- package/.opencode/opencode.json +4 -2
- package/dist/sdk/runtime/executor.d.ts +8 -0
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/runtime/port-discovery.d.ts +71 -0
- package/dist/sdk/runtime/port-discovery.d.ts.map +1 -0
- package/dist/sdk/runtime/tmux.d.ts +10 -0
- package/dist/sdk/runtime/tmux.d.ts.map +1 -1
- package/dist/sdk/types.d.ts +1 -0
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts +15 -0
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/opencode/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/sdk/runtime/executor.test.ts +254 -1
- package/src/sdk/runtime/executor.ts +135 -89
- package/src/sdk/runtime/port-discovery.test.ts +573 -0
- package/src/sdk/runtime/port-discovery.ts +496 -0
- package/src/sdk/runtime/tmux.ts +16 -0
- package/src/sdk/types.ts +1 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +24 -6
- package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +52 -13
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +31 -3
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +16 -0
- package/src/sdk/workflows/builtin/ralph/helpers/prompts.ts +70 -3
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +50 -6
- package/.agents/skills/shape/SKILL.md +0 -96
- /package/.agents/skills/{critique → impeccable}/reference/cognitive-load.md +0 -0
- /package/.agents/skills/{critique → impeccable}/reference/heuristics-scoring.md +0 -0
|
@@ -39,9 +39,7 @@ import type {
|
|
|
39
39
|
ProviderClient,
|
|
40
40
|
ProviderSession,
|
|
41
41
|
} from "../types.ts";
|
|
42
|
-
import {
|
|
43
|
-
type ProviderOverrides,
|
|
44
|
-
} from "../../services/config/definitions.ts";
|
|
42
|
+
import { type ProviderOverrides } from "../../services/config/definitions.ts";
|
|
45
43
|
import { getProviderOverrides } from "../../services/config/atomic-config.ts";
|
|
46
44
|
import { getCopilotScmDisableFlags } from "../../services/config/scm-sync.ts";
|
|
47
45
|
import { reconcileOpencodeInstructions } from "../../services/config/additional-instructions.ts";
|
|
@@ -51,6 +49,10 @@ import type { SessionPromptResponse } from "@opencode-ai/sdk/v2";
|
|
|
51
49
|
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
52
50
|
import * as tmux from "./tmux.ts";
|
|
53
51
|
import { spawnMuxAttach } from "./tmux.ts";
|
|
52
|
+
import {
|
|
53
|
+
getListeningPortForPid,
|
|
54
|
+
PORT_DISCOVERY_TIMEOUT_MS,
|
|
55
|
+
} from "./port-discovery.ts";
|
|
54
56
|
import { spawnAttachedFooter } from "./attached-footer.ts";
|
|
55
57
|
import {
|
|
56
58
|
clearClaudeSession,
|
|
@@ -66,8 +68,8 @@ import { buildSnapshot, writeSnapshot } from "./status-writer.ts";
|
|
|
66
68
|
import { errorMessage } from "../errors.ts";
|
|
67
69
|
import { createPainter } from "../../theme/colors.ts";
|
|
68
70
|
|
|
69
|
-
/** Maximum time (ms)
|
|
70
|
-
const
|
|
71
|
+
/** Maximum time (ms) for the SDK probe to succeed after port is discovered. */
|
|
72
|
+
export const SERVER_PROBE_TIMEOUT_MS = 60_000;
|
|
71
73
|
|
|
72
74
|
/** Agent CLI configuration for spawning in tmux panes. */
|
|
73
75
|
const AGENT_CLI: Record<
|
|
@@ -170,33 +172,6 @@ function getSessionsBaseDir(): string {
|
|
|
170
172
|
return join(homedir(), ".atomic", "sessions");
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
async function getRandomPort(): Promise<number> {
|
|
174
|
-
const net = await import("node:net");
|
|
175
|
-
|
|
176
|
-
const MAX_RETRIES = 3;
|
|
177
|
-
let lastPort = 0;
|
|
178
|
-
|
|
179
|
-
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
180
|
-
const port = await new Promise<number>((resolve, reject) => {
|
|
181
|
-
const server = net.createServer();
|
|
182
|
-
server.listen(0, () => {
|
|
183
|
-
const addr = server.address();
|
|
184
|
-
const p = typeof addr === "object" && addr ? addr.port : 0;
|
|
185
|
-
server.close(() => resolve(p));
|
|
186
|
-
});
|
|
187
|
-
server.on("error", reject);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (port > 0) return port;
|
|
191
|
-
lastPort = port;
|
|
192
|
-
await Bun.sleep(50);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
throw new Error(
|
|
196
|
-
`Failed to acquire a random port after ${MAX_RETRIES} attempts (last: ${lastPort})`,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
175
|
/**
|
|
201
176
|
* Resolve a non-JS Copilot CLI binary on PATH.
|
|
202
177
|
*
|
|
@@ -280,9 +255,29 @@ export function applyContainerEnvDefaults(): void {
|
|
|
280
255
|
if (bin) process.env.COPILOT_CLI_PATH = bin;
|
|
281
256
|
}
|
|
282
257
|
|
|
283
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Resolve a CLI binary name to its absolute path using the parent atomic
|
|
260
|
+
* process's PATH. tmux's child shell can have a stripped or differently
|
|
261
|
+
* ordered PATH from the user's interactive shell — most visibly when atomic
|
|
262
|
+
* is launched from a globally-installed bin wrapper rather than `bun run dev`.
|
|
263
|
+
* Resolving here, where we still have the full interactive PATH, mirrors
|
|
264
|
+
* how `attached-footer.ts` injects `process.execPath` + an absolute cli.ts
|
|
265
|
+
* path so the footer always spawns regardless of the child shell's PATH.
|
|
266
|
+
*
|
|
267
|
+
* Falls back to the bare name when the binary isn't found on PATH so behavior
|
|
268
|
+
* stays unchanged for callers running entirely inside a normal interactive shell.
|
|
269
|
+
*/
|
|
270
|
+
function resolveCliBinary(cmd: string): string {
|
|
271
|
+
return Bun.which(cmd, { PATH: process.env.PATH ?? "" }) ?? cmd;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Wrap a path in bash double quotes only when it contains shell-significant characters. */
|
|
275
|
+
function quotePathIfNeeded(path: string): string {
|
|
276
|
+
return /[\s'"$`!\\]/.test(path) ? `"${escBash(path)}"` : path;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function buildPaneCommand(
|
|
284
280
|
agent: AgentType,
|
|
285
|
-
port: number,
|
|
286
281
|
overrides: ProviderOverrides = {},
|
|
287
282
|
extraChatFlags: string[] = [],
|
|
288
283
|
): { command: string; envVars: Record<string, string> } {
|
|
@@ -296,14 +291,16 @@ function buildPaneCommand(
|
|
|
296
291
|
? { ...defaultEnvVars, ...overrides.envVars }
|
|
297
292
|
: defaultEnvVars;
|
|
298
293
|
|
|
294
|
+
const resolvedCmd = quotePathIfNeeded(resolveCliBinary(cmd));
|
|
295
|
+
|
|
299
296
|
switch (agent) {
|
|
300
297
|
case "copilot":
|
|
301
298
|
return {
|
|
302
299
|
command: [
|
|
303
|
-
|
|
300
|
+
resolvedCmd,
|
|
304
301
|
"--ui-server",
|
|
305
302
|
"--port",
|
|
306
|
-
|
|
303
|
+
"0",
|
|
307
304
|
...chatFlags,
|
|
308
305
|
...extraChatFlags,
|
|
309
306
|
].join(" "),
|
|
@@ -311,43 +308,67 @@ function buildPaneCommand(
|
|
|
311
308
|
};
|
|
312
309
|
case "opencode":
|
|
313
310
|
return {
|
|
314
|
-
command: [
|
|
311
|
+
command: [resolvedCmd, "--port", "0", ...chatFlags].join(" "),
|
|
315
312
|
envVars,
|
|
316
313
|
};
|
|
317
|
-
case "claude":
|
|
318
|
-
// Claude is started via createClaudeSession() in the workflow's run()
|
|
314
|
+
case "claude": {
|
|
315
|
+
// Claude is started via createClaudeSession() in the workflow's run().
|
|
316
|
+
// Resolve $SHELL (or the platform default) to an absolute path for the
|
|
317
|
+
// same reason the agent CLIs are resolved above.
|
|
318
|
+
const fallback = process.platform === "win32" ? "pwsh" : "sh";
|
|
319
|
+
const shellCandidate = process.env.SHELL || fallback;
|
|
320
|
+
const resolvedShell =
|
|
321
|
+
shellCandidate.includes("/") || shellCandidate.includes("\\")
|
|
322
|
+
? shellCandidate
|
|
323
|
+
: resolveCliBinary(shellCandidate);
|
|
319
324
|
return {
|
|
320
|
-
command:
|
|
321
|
-
process.env.SHELL || (process.platform === "win32" ? "pwsh" : "sh"),
|
|
325
|
+
command: quotePathIfNeeded(resolvedShell),
|
|
322
326
|
envVars,
|
|
323
327
|
};
|
|
328
|
+
}
|
|
324
329
|
default:
|
|
325
330
|
return assertNever(agent);
|
|
326
331
|
}
|
|
327
332
|
}
|
|
328
333
|
|
|
329
|
-
async function waitForServer(
|
|
334
|
+
export async function waitForServer(
|
|
330
335
|
agent: AgentType,
|
|
331
|
-
port: number,
|
|
332
336
|
paneId: string,
|
|
333
337
|
): Promise<string> {
|
|
334
338
|
if (agent === "claude") return "";
|
|
335
339
|
|
|
336
|
-
const
|
|
337
|
-
const deadline = Date.now() + SERVER_WAIT_TIMEOUT_MS;
|
|
340
|
+
const portDeadline = Date.now() + PORT_DISCOVERY_TIMEOUT_MS;
|
|
338
341
|
|
|
339
|
-
// Wait for the TUI to render
|
|
340
|
-
while (Date.now() <
|
|
342
|
+
// 1. Wait for the agent process to start and the TUI to render.
|
|
343
|
+
while (Date.now() < portDeadline) {
|
|
341
344
|
const content = tmux.capturePane(paneId);
|
|
342
345
|
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
343
346
|
if (lines.length >= 3) break;
|
|
344
347
|
await Bun.sleep(1_000);
|
|
345
348
|
}
|
|
346
349
|
|
|
347
|
-
//
|
|
350
|
+
// 2. Discover the listening port via the agent's PID.
|
|
351
|
+
const panePid = tmux.getPanePid(paneId);
|
|
352
|
+
if (!panePid) {
|
|
353
|
+
throw new Error(`failed to resolve agent PID for pane ${paneId}`);
|
|
354
|
+
}
|
|
355
|
+
const remainingMs = Math.max(0, portDeadline - Date.now());
|
|
356
|
+
const port = await getListeningPortForPid(panePid, {
|
|
357
|
+
timeoutMs: remainingMs,
|
|
358
|
+
});
|
|
359
|
+
if (port === null) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`agent (${agent}) did not bind a TCP port within ${PORT_DISCOVERY_TIMEOUT_MS}ms ` +
|
|
362
|
+
`(pane ${paneId}, pid ${panePid})`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const serverUrl = `localhost:${port}`;
|
|
366
|
+
|
|
367
|
+
// 3. Verify the SDK can actually connect.
|
|
348
368
|
if (agent === "copilot") {
|
|
369
|
+
const probeDeadline = Date.now() + SERVER_PROBE_TIMEOUT_MS;
|
|
349
370
|
const { CopilotClient } = await import("@github/copilot-sdk");
|
|
350
|
-
while (Date.now() <
|
|
371
|
+
while (Date.now() < probeDeadline) {
|
|
351
372
|
try {
|
|
352
373
|
const probe = new CopilotClient({ cliUrl: serverUrl });
|
|
353
374
|
await probe.start();
|
|
@@ -358,10 +379,13 @@ async function waitForServer(
|
|
|
358
379
|
await Bun.sleep(1_000);
|
|
359
380
|
}
|
|
360
381
|
}
|
|
382
|
+
throw new Error(
|
|
383
|
+
`copilot SDK probe did not respond at ${serverUrl} within ${SERVER_PROBE_TIMEOUT_MS}ms`,
|
|
384
|
+
);
|
|
361
385
|
}
|
|
362
386
|
|
|
363
|
-
//
|
|
364
|
-
await Bun.sleep(
|
|
387
|
+
// OpenCode: short settle delay, then return.
|
|
388
|
+
await Bun.sleep(1_000);
|
|
365
389
|
return serverUrl;
|
|
366
390
|
}
|
|
367
391
|
|
|
@@ -482,6 +506,12 @@ export async function executeWorkflow(
|
|
|
482
506
|
const orchestratorEntry = join(import.meta.dir, "orchestrator-entry.ts");
|
|
483
507
|
const workflowSource = definition.source;
|
|
484
508
|
|
|
509
|
+
// Resolve the bun binary to an absolute path here — `process.execPath` is
|
|
510
|
+
// the exact bun interpreter currently running atomic, so we don't depend on
|
|
511
|
+
// bare `bun` being on the tmux child shell's PATH (the same reason
|
|
512
|
+
// `attached-footer.ts` uses it).
|
|
513
|
+
const bunBinary = process.execPath;
|
|
514
|
+
|
|
485
515
|
const launcherScript = isWin
|
|
486
516
|
? [
|
|
487
517
|
`Set-Location "${escPwsh(projectRoot)}"`,
|
|
@@ -489,7 +519,7 @@ export async function executeWorkflow(
|
|
|
489
519
|
`$env:ATOMIC_WF_TMUX = "${escPwsh(tmuxSessionName)}"`,
|
|
490
520
|
`$env:ATOMIC_WF_AGENT = "${escPwsh(agent)}"`,
|
|
491
521
|
`$env:ATOMIC_WF_CWD = "${escPwsh(projectRoot)}"`,
|
|
492
|
-
|
|
522
|
+
`& "${escPwsh(bunBinary)}" run "${escPwsh(orchestratorEntry)}" "${escPwsh(workflowSource)}" "${escPwsh(agent)}" "${escPwsh(inputsB64)}" 2>"${escPwsh(logPath)}"`,
|
|
493
523
|
].join("\n")
|
|
494
524
|
: [
|
|
495
525
|
"#!/bin/bash",
|
|
@@ -498,7 +528,7 @@ export async function executeWorkflow(
|
|
|
498
528
|
`export ATOMIC_WF_TMUX="${escBash(tmuxSessionName)}"`,
|
|
499
529
|
`export ATOMIC_WF_AGENT="${escBash(agent)}"`,
|
|
500
530
|
`export ATOMIC_WF_CWD="${escBash(projectRoot)}"`,
|
|
501
|
-
`
|
|
531
|
+
`"${escBash(bunBinary)}" run "${escBash(orchestratorEntry)}" "${escBash(workflowSource)}" "${escBash(agent)}" "${escBash(inputsB64)}" 2>"${escBash(logPath)}"`,
|
|
502
532
|
].join("\n");
|
|
503
533
|
|
|
504
534
|
await writeFile(launcherPath, launcherScript, { mode: 0o755 });
|
|
@@ -541,12 +571,28 @@ function printDetachedBanner(tmuxSessionName: string): void {
|
|
|
541
571
|
const paint = createPainter();
|
|
542
572
|
process.stdout.write(
|
|
543
573
|
"\n" +
|
|
544
|
-
" " +
|
|
545
|
-
|
|
574
|
+
" " +
|
|
575
|
+
paint("success", "✓") +
|
|
576
|
+
" " +
|
|
577
|
+
paint("text", "workflow started in background", { bold: true }) +
|
|
578
|
+
"\n" +
|
|
579
|
+
" " +
|
|
580
|
+
paint("dim", "session: ") +
|
|
581
|
+
paint("accent", tmuxSessionName) +
|
|
582
|
+
"\n" +
|
|
583
|
+
"\n" +
|
|
584
|
+
" " +
|
|
585
|
+
paint("dim", "attach: ") +
|
|
586
|
+
paint("accent", `atomic workflow session connect ${tmuxSessionName}`) +
|
|
587
|
+
"\n" +
|
|
588
|
+
" " +
|
|
589
|
+
paint("dim", "list: ") +
|
|
590
|
+
paint("accent", "atomic workflow session list") +
|
|
591
|
+
"\n" +
|
|
592
|
+
" " +
|
|
593
|
+
paint("dim", "kill: ") +
|
|
594
|
+
paint("accent", `atomic workflow session kill ${tmuxSessionName}`) +
|
|
546
595
|
"\n" +
|
|
547
|
-
" " + paint("dim", "attach: ") + paint("accent", `atomic workflow session connect ${tmuxSessionName}`) + "\n" +
|
|
548
|
-
" " + paint("dim", "list: ") + paint("accent", "atomic workflow session list") + "\n" +
|
|
549
|
-
" " + paint("dim", "kill: ") + paint("accent", `atomic workflow session kill ${tmuxSessionName}`) + "\n" +
|
|
550
596
|
"\n",
|
|
551
597
|
);
|
|
552
598
|
}
|
|
@@ -579,7 +625,9 @@ function resolveProviderSessionId(
|
|
|
579
625
|
return typeof obj["id"] === "string" ? (obj["id"] as string) : "";
|
|
580
626
|
}
|
|
581
627
|
// claude and copilot both expose `sessionId` as a string.
|
|
582
|
-
return typeof obj["sessionId"] === "string"
|
|
628
|
+
return typeof obj["sessionId"] === "string"
|
|
629
|
+
? (obj["sessionId"] as string)
|
|
630
|
+
: "";
|
|
583
631
|
}
|
|
584
632
|
|
|
585
633
|
/** Type guard for objects with a string `content` property (Copilot assistant.message data). */
|
|
@@ -792,7 +840,9 @@ function renderCopilotTranscript(
|
|
|
792
840
|
* invocations. `reasoning` and `subtask` parts are internal and omitted.
|
|
793
841
|
*/
|
|
794
842
|
function renderOpencodeTranscript(response: {
|
|
795
|
-
parts?: ReadonlyArray<
|
|
843
|
+
parts?: ReadonlyArray<
|
|
844
|
+
{ type?: unknown; text?: unknown } & Record<string, unknown>
|
|
845
|
+
>;
|
|
796
846
|
}): string {
|
|
797
847
|
if (!response.parts) return "";
|
|
798
848
|
const parts: string[] = [];
|
|
@@ -811,8 +861,8 @@ function renderOpencodeTranscript(response: {
|
|
|
811
861
|
const state = part["state"];
|
|
812
862
|
const args =
|
|
813
863
|
state && typeof state === "object"
|
|
814
|
-
? (state as Record<string, unknown>)["input"] ??
|
|
815
|
-
(state as Record<string, unknown>)["args"]
|
|
864
|
+
? ((state as Record<string, unknown>)["input"] ??
|
|
865
|
+
(state as Record<string, unknown>)["args"])
|
|
816
866
|
: undefined;
|
|
817
867
|
parts.push(
|
|
818
868
|
`**→ \`${name}\`**\n\n\`\`\`json\n${renderToolInput(args)}\n\`\`\``,
|
|
@@ -842,9 +892,7 @@ export function renderMessagesToText(messages: SavedMessage[]): string {
|
|
|
842
892
|
|
|
843
893
|
for (const m of messages) {
|
|
844
894
|
if (m.provider === "claude") {
|
|
845
|
-
claudeBatch.push(
|
|
846
|
-
m.data as unknown as { type: string; message: unknown },
|
|
847
|
-
);
|
|
895
|
+
claudeBatch.push(m.data as unknown as { type: string; message: unknown });
|
|
848
896
|
continue;
|
|
849
897
|
}
|
|
850
898
|
flushClaude();
|
|
@@ -880,7 +928,10 @@ function resolveRef(ref: SessionRef): string {
|
|
|
880
928
|
* CopilotSession and lightweight test mocks.
|
|
881
929
|
*/
|
|
882
930
|
export interface CopilotSendSessionSurface {
|
|
883
|
-
on(
|
|
931
|
+
on(
|
|
932
|
+
eventType: string,
|
|
933
|
+
handler: (event: { data?: unknown }) => void,
|
|
934
|
+
): () => void;
|
|
884
935
|
}
|
|
885
936
|
|
|
886
937
|
/**
|
|
@@ -1078,11 +1129,7 @@ export function watchCopilotSessionForElicitation(
|
|
|
1078
1129
|
});
|
|
1079
1130
|
const unsubCompleted = session.on("elicitation.completed", (event) => {
|
|
1080
1131
|
const data = event.data as { requestId?: string } | undefined;
|
|
1081
|
-
if (
|
|
1082
|
-
data?.requestId &&
|
|
1083
|
-
active.delete(data.requestId) &&
|
|
1084
|
-
active.size === 0
|
|
1085
|
-
) {
|
|
1132
|
+
if (data?.requestId && active.delete(data.requestId) && active.size === 0) {
|
|
1086
1133
|
onHIL(false);
|
|
1087
1134
|
}
|
|
1088
1135
|
});
|
|
@@ -1275,12 +1322,10 @@ async function initProviderClientAndSession<A extends AgentType>(
|
|
|
1275
1322
|
switch (agent) {
|
|
1276
1323
|
case "copilot": {
|
|
1277
1324
|
const { CopilotClient, approveAll } = await import("@github/copilot-sdk");
|
|
1278
|
-
const { copilotSubprocessEnv, mergeCopilotSystemMessage } =
|
|
1279
|
-
"../providers/copilot.ts"
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
"../../services/config/additional-instructions.ts"
|
|
1283
|
-
);
|
|
1325
|
+
const { copilotSubprocessEnv, mergeCopilotSystemMessage } =
|
|
1326
|
+
await import("../providers/copilot.ts");
|
|
1327
|
+
const { resolveAdditionalInstructionsContent } =
|
|
1328
|
+
await import("../../services/config/additional-instructions.ts");
|
|
1284
1329
|
const copilotClientOpts = clientOpts as StageClientOptions<"copilot">;
|
|
1285
1330
|
const copilotSessionOpts = sessionOpts as StageSessionOptions<"copilot">;
|
|
1286
1331
|
// Headless: let the SDK spawn its own CLI process (no cliUrl).
|
|
@@ -1311,9 +1356,8 @@ async function initProviderClientAndSession<A extends AgentType>(
|
|
|
1311
1356
|
// In headless stages, add `ask_user` to the session's excludedTools so
|
|
1312
1357
|
// the agent cannot call the interactive question tool — there is no
|
|
1313
1358
|
// human attached to answer and the SDK would otherwise sit blocked.
|
|
1314
|
-
const additionalInstructions =
|
|
1315
|
-
projectRoot
|
|
1316
|
-
);
|
|
1359
|
+
const additionalInstructions =
|
|
1360
|
+
await resolveAdditionalInstructionsContent(projectRoot);
|
|
1317
1361
|
const sessionConfig = {
|
|
1318
1362
|
onPermissionRequest: approveAll,
|
|
1319
1363
|
...copilotSessionOpts,
|
|
@@ -1356,7 +1400,10 @@ async function initProviderClientAndSession<A extends AgentType>(
|
|
|
1356
1400
|
// the session permission ruleset).
|
|
1357
1401
|
return await withHeadlessOpencodeEnv(async () => {
|
|
1358
1402
|
const oc = await createOpencode({ port: 0 });
|
|
1359
|
-
const sessionResult = await oc.client.session.create(
|
|
1403
|
+
const sessionResult = await oc.client.session.create({
|
|
1404
|
+
permission: [{ permission: "*", pattern: "*", action: "allow" }],
|
|
1405
|
+
...ocSessionOpts,
|
|
1406
|
+
});
|
|
1360
1407
|
return {
|
|
1361
1408
|
client: oc.client,
|
|
1362
1409
|
session: sessionResult.data!,
|
|
@@ -1530,11 +1577,9 @@ function createSessionRunner(
|
|
|
1530
1577
|
let panelSessionAdded = false;
|
|
1531
1578
|
|
|
1532
1579
|
try {
|
|
1533
|
-
// ── 6.
|
|
1534
|
-
const port = await getRandomPort();
|
|
1580
|
+
// ── 6. Build pane command (OS allocates port via --port 0) ──
|
|
1535
1581
|
const { command: paneCmd, envVars: paneEnvVars } = buildPaneCommand(
|
|
1536
1582
|
shared.agent,
|
|
1537
|
-
port,
|
|
1538
1583
|
shared.providerOverrides,
|
|
1539
1584
|
shared.extraChatFlags,
|
|
1540
1585
|
);
|
|
@@ -1564,7 +1609,7 @@ function createSessionRunner(
|
|
|
1564
1609
|
|
|
1565
1610
|
spawnAttachedFooter(name, paneId);
|
|
1566
1611
|
|
|
1567
|
-
serverUrl = await waitForServer(shared.agent,
|
|
1612
|
+
serverUrl = await waitForServer(shared.agent, paneId);
|
|
1568
1613
|
|
|
1569
1614
|
shared.panel.addSession(name, graphParents);
|
|
1570
1615
|
panelSessionAdded = true;
|
|
@@ -1592,8 +1637,8 @@ function createSessionRunner(
|
|
|
1592
1637
|
if (!arg) {
|
|
1593
1638
|
throw new Error(
|
|
1594
1639
|
"wrapMessages: empty Claude session id. Call s.save(s.sessionId) " +
|
|
1595
|
-
|
|
1596
|
-
|
|
1640
|
+
"only after a successful s.session.query() (headless wrappers " +
|
|
1641
|
+
"only know their session_id once a query completes).",
|
|
1597
1642
|
);
|
|
1598
1643
|
}
|
|
1599
1644
|
const { getSessionMessages } =
|
|
@@ -1784,7 +1829,7 @@ function createSessionRunner(
|
|
|
1784
1829
|
agent: shared.agent,
|
|
1785
1830
|
paneId,
|
|
1786
1831
|
serverUrl,
|
|
1787
|
-
port,
|
|
1832
|
+
port: serverUrl ? Number(serverUrl.split(":").pop()) : 0,
|
|
1788
1833
|
startedAt: new Date().toISOString(),
|
|
1789
1834
|
},
|
|
1790
1835
|
null,
|
|
@@ -1886,7 +1931,8 @@ export async function runOrchestrator(
|
|
|
1886
1931
|
definition: WorkflowDefinition,
|
|
1887
1932
|
inputs: Record<string, string> = {},
|
|
1888
1933
|
): Promise<void> {
|
|
1889
|
-
const { workflowRunId, tmuxSessionName, agent, cwd } =
|
|
1934
|
+
const { workflowRunId, tmuxSessionName, agent, cwd } =
|
|
1935
|
+
validateOrchestratorEnv();
|
|
1890
1936
|
// A bare prompt string is still useful for the panel header and the
|
|
1891
1937
|
// session-dir metadata.json — both just want something displayable.
|
|
1892
1938
|
// Free-form workflows store their single positional prompt under the
|