@agent-deck/cli 2.0.0 → 2.1.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/agent-harness.d.ts +3 -0
- package/dist/agent-harness.d.ts.map +1 -1
- package/dist/agent-harness.js +64 -6
- package/dist/agent-harness.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-bridge.d.ts +83 -1
- package/dist/mcp-bridge.d.ts.map +1 -1
- package/dist/mcp-bridge.js +252 -7
- package/dist/mcp-bridge.js.map +1 -1
- package/dist/mcp-config.d.ts +13 -1
- package/dist/mcp-config.d.ts.map +1 -1
- package/dist/mcp-config.js +49 -1
- package/dist/mcp-config.js.map +1 -1
- package/dist/muse-skills/agent-deck-playbooks/SKILL.md +24 -0
- package/dist/muse-skills/agent-deck-run/SKILL.md +69 -0
- package/dist/muse-skills/agent-deck-session/SKILL.md +59 -0
- package/dist/muse-skills/agent-deck-setup/SKILL.md +28 -0
- package/dist/muse-skills.d.ts +42 -0
- package/dist/muse-skills.d.ts.map +1 -0
- package/dist/muse-skills.js +149 -0
- package/dist/muse-skills.js.map +1 -0
- package/dist/run-adapter.d.ts +100 -0
- package/dist/run-adapter.d.ts.map +1 -0
- package/dist/run-adapter.js +73 -0
- package/dist/run-adapter.js.map +1 -0
- package/dist/run-claude.d.ts +41 -0
- package/dist/run-claude.d.ts.map +1 -0
- package/dist/run-claude.js +91 -0
- package/dist/run-claude.js.map +1 -0
- package/dist/run-codex.d.ts +75 -0
- package/dist/run-codex.d.ts.map +1 -0
- package/dist/run-codex.js +337 -0
- package/dist/run-codex.js.map +1 -0
- package/dist/run-cursor.d.ts +79 -0
- package/dist/run-cursor.d.ts.map +1 -0
- package/dist/run-cursor.js +455 -0
- package/dist/run-cursor.js.map +1 -0
- package/dist/run-detach.d.ts +36 -0
- package/dist/run-detach.d.ts.map +1 -0
- package/dist/run-detach.js +233 -0
- package/dist/run-detach.js.map +1 -0
- package/dist/run-muse.d.ts +127 -0
- package/dist/run-muse.d.ts.map +1 -0
- package/dist/run-muse.js +499 -0
- package/dist/run-muse.js.map +1 -0
- package/dist/run-status.d.ts +62 -0
- package/dist/run-status.d.ts.map +1 -0
- package/dist/run-status.js +552 -0
- package/dist/run-status.js.map +1 -0
- package/dist/run.d.ts +191 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +680 -0
- package/dist/run.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +51 -10
- package/dist/setup.js.map +1 -1
- package/dist/use.d.ts +1 -1
- package/dist/use.d.ts.map +1 -1
- package/dist/use.js +16 -15
- package/dist/use.js.map +1 -1
- package/package.json +5 -4
package/dist/run.js
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.RUN_FAKE_ENV = exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR = exports.RUN_CLAUDE_NOT_INSTALLED_ERROR = exports.CLAUDE_AUTH_LOGIN_COMMAND = exports.RUN_TOOL_PROFILES = exports.RUN_SETTING_SOURCES = exports.RUN_READONLY_TOOLS = exports.RUN_PERMISSION_MODE = exports.RUN_EDIT_TOOLS = exports.allowedToolsForProfile = void 0;
|
|
40
|
+
exports.parseRunArgs = parseRunArgs;
|
|
41
|
+
exports.findExecutableOnPath = findExecutableOnPath;
|
|
42
|
+
exports.claudeAuthStatusShowsLogin = claudeAuthStatusShowsLogin;
|
|
43
|
+
exports.defaultClaudeClientProbe = defaultClaudeClientProbe;
|
|
44
|
+
exports.loadRunFakeBehavior = loadRunFakeBehavior;
|
|
45
|
+
exports.newRunId = newRunId;
|
|
46
|
+
exports.writeRunMeta = writeRunMeta;
|
|
47
|
+
exports.findReceiptDeckMismatch = findReceiptDeckMismatch;
|
|
48
|
+
exports.validateRunPrereqs = validateRunPrereqs;
|
|
49
|
+
exports.printRunHelp = printRunHelp;
|
|
50
|
+
exports.runRun = runRun;
|
|
51
|
+
exports.runRunCommand = runRunCommand;
|
|
52
|
+
/**
|
|
53
|
+
* `agent-deck run` — start a local agent session bound to a deck (NOT-400, NOT-404).
|
|
54
|
+
*
|
|
55
|
+
* Productizes the throwaway botmancers runner: one command takes a deck, a
|
|
56
|
+
* working directory, and a brief, streams the session to a JSONL log, and
|
|
57
|
+
* records a small meta.json that later tickets (detach, status) can read.
|
|
58
|
+
* Resume continues the same client session id under a new run log.
|
|
59
|
+
*
|
|
60
|
+
* Client-specific launching lives behind the {@link ClientAdapter} interface
|
|
61
|
+
* (`run-adapter.ts`); this file owns arg parsing, the deck binding gate,
|
|
62
|
+
* tool profiles, the JSONL log, and meta.json. New adapters plug in without
|
|
63
|
+
* touching this file's run/log/status paths.
|
|
64
|
+
*
|
|
65
|
+
* `--detach` (NOT-401) re-executes this same command as a background child;
|
|
66
|
+
* status/log/list (NOT-402) poll the run record the child writes.
|
|
67
|
+
*/
|
|
68
|
+
const node_child_process_1 = require("node:child_process");
|
|
69
|
+
const node_crypto_1 = require("node:crypto");
|
|
70
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
71
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
72
|
+
const backend_runtime_1 = require("./backend-runtime");
|
|
73
|
+
const paths_1 = require("./managed/paths");
|
|
74
|
+
const mcp_launcher_1 = require("./mcp-launcher");
|
|
75
|
+
const run_adapter_1 = require("./run-adapter");
|
|
76
|
+
const run_claude_1 = require("./run-claude");
|
|
77
|
+
const run_codex_1 = require("./run-codex");
|
|
78
|
+
const run_cursor_1 = require("./run-cursor");
|
|
79
|
+
const run_muse_1 = require("./run-muse");
|
|
80
|
+
// Tool profiles stay owned by the shared run code (re-exported so existing
|
|
81
|
+
// `from './run'` imports keep working).
|
|
82
|
+
var run_adapter_2 = require("./run-adapter");
|
|
83
|
+
Object.defineProperty(exports, "allowedToolsForProfile", { enumerable: true, get: function () { return run_adapter_2.allowedToolsForProfile; } });
|
|
84
|
+
Object.defineProperty(exports, "RUN_EDIT_TOOLS", { enumerable: true, get: function () { return run_adapter_2.RUN_EDIT_TOOLS; } });
|
|
85
|
+
Object.defineProperty(exports, "RUN_PERMISSION_MODE", { enumerable: true, get: function () { return run_adapter_2.RUN_PERMISSION_MODE; } });
|
|
86
|
+
Object.defineProperty(exports, "RUN_READONLY_TOOLS", { enumerable: true, get: function () { return run_adapter_2.RUN_READONLY_TOOLS; } });
|
|
87
|
+
Object.defineProperty(exports, "RUN_SETTING_SOURCES", { enumerable: true, get: function () { return run_adapter_2.RUN_SETTING_SOURCES; } });
|
|
88
|
+
Object.defineProperty(exports, "RUN_TOOL_PROFILES", { enumerable: true, get: function () { return run_adapter_2.RUN_TOOL_PROFILES; } });
|
|
89
|
+
function parseRunArgs(args) {
|
|
90
|
+
let deckRef;
|
|
91
|
+
let client;
|
|
92
|
+
let cwd;
|
|
93
|
+
let prompt;
|
|
94
|
+
let promptFile;
|
|
95
|
+
let tools = 'readonly';
|
|
96
|
+
let log;
|
|
97
|
+
let resume;
|
|
98
|
+
let detach = false;
|
|
99
|
+
let runIdOverride;
|
|
100
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
101
|
+
const arg = args[i];
|
|
102
|
+
if (arg === '--client') {
|
|
103
|
+
client = args[++i];
|
|
104
|
+
}
|
|
105
|
+
else if (arg === '--cwd') {
|
|
106
|
+
cwd = args[++i];
|
|
107
|
+
}
|
|
108
|
+
else if (arg === '--prompt') {
|
|
109
|
+
prompt = args[++i];
|
|
110
|
+
}
|
|
111
|
+
else if (arg === '--prompt-file') {
|
|
112
|
+
promptFile = args[++i];
|
|
113
|
+
}
|
|
114
|
+
else if (arg === '--tools') {
|
|
115
|
+
tools = args[++i] ?? '';
|
|
116
|
+
}
|
|
117
|
+
else if (arg === '--log') {
|
|
118
|
+
log = args[++i];
|
|
119
|
+
}
|
|
120
|
+
else if (arg === '--resume') {
|
|
121
|
+
resume = args[++i];
|
|
122
|
+
}
|
|
123
|
+
else if (arg === '--detach') {
|
|
124
|
+
detach = true;
|
|
125
|
+
}
|
|
126
|
+
else if (arg === '--_run-id') {
|
|
127
|
+
runIdOverride = args[++i];
|
|
128
|
+
}
|
|
129
|
+
else if (arg === '--help' || arg === '-h') {
|
|
130
|
+
return { error: 'help' };
|
|
131
|
+
}
|
|
132
|
+
else if (arg.startsWith('-')) {
|
|
133
|
+
return { error: `Unknown run option: ${arg}` };
|
|
134
|
+
}
|
|
135
|
+
else if (!deckRef) {
|
|
136
|
+
deckRef = arg;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
return { error: `Unexpected argument: ${arg}` };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!deckRef) {
|
|
143
|
+
return { error: 'deck name or id is required' };
|
|
144
|
+
}
|
|
145
|
+
if (!client) {
|
|
146
|
+
return { error: (0, run_adapter_1.missingRunClientMessage)() };
|
|
147
|
+
}
|
|
148
|
+
if (client !== 'claude' && client !== 'codex' && client !== 'cursor' && client !== 'muse') {
|
|
149
|
+
return { error: (0, run_adapter_1.runClientMessage)(client) };
|
|
150
|
+
}
|
|
151
|
+
if (!cwd) {
|
|
152
|
+
return { error: '--cwd is required' };
|
|
153
|
+
}
|
|
154
|
+
if (prompt !== undefined && promptFile !== undefined) {
|
|
155
|
+
return { error: 'Pass exactly one of --prompt or --prompt-file' };
|
|
156
|
+
}
|
|
157
|
+
if (prompt === undefined && promptFile === undefined) {
|
|
158
|
+
return { error: 'A brief is required: pass --prompt <text> or --prompt-file <path>' };
|
|
159
|
+
}
|
|
160
|
+
if (tools !== 'readonly' && tools !== 'edit') {
|
|
161
|
+
return { error: `--tools must be one of: readonly, edit (got "${tools}")` };
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
deckRef,
|
|
165
|
+
client,
|
|
166
|
+
cwd: node_path_1.default.resolve(cwd),
|
|
167
|
+
...(prompt !== undefined ? { prompt } : {}),
|
|
168
|
+
...(promptFile !== undefined ? { promptFile } : {}),
|
|
169
|
+
tools,
|
|
170
|
+
...(log ? { log: node_path_1.default.resolve(log) } : {}),
|
|
171
|
+
...(resume ? { resume } : {}),
|
|
172
|
+
detach,
|
|
173
|
+
...(runIdOverride ? { runIdOverride } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** The one fix command both preflight remedies name. Verified against `claude auth --help`. */
|
|
177
|
+
exports.CLAUDE_AUTH_LOGIN_COMMAND = 'claude auth login';
|
|
178
|
+
exports.RUN_CLAUDE_NOT_INSTALLED_ERROR = `Claude Code is not installed (no \`claude\` on PATH). Install Claude Code, then run \`${exports.CLAUDE_AUTH_LOGIN_COMMAND}\` to sign in, then retry.`;
|
|
179
|
+
exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR = `Claude Code is not signed in. Run \`${exports.CLAUDE_AUTH_LOGIN_COMMAND}\` to sign in with your own account, then retry.`;
|
|
180
|
+
/** First executable `name` on `pathEnv`, or null. Pure apart from the fs check. */
|
|
181
|
+
function findExecutableOnPath(name, pathEnv = process.env.PATH ?? '') {
|
|
182
|
+
for (const dir of pathEnv.split(node_path_1.default.delimiter)) {
|
|
183
|
+
if (!dir) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const candidate = node_path_1.default.join(dir, name);
|
|
187
|
+
try {
|
|
188
|
+
node_fs_1.default.accessSync(candidate, node_fs_1.default.constants.X_OK);
|
|
189
|
+
return candidate;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Not here; keep scanning.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* True only when `claude auth status` JSON reports a login. Never throws:
|
|
199
|
+
* garbage, an empty string, or a logged-out report all read as signed out.
|
|
200
|
+
*/
|
|
201
|
+
function claudeAuthStatusShowsLogin(stdout) {
|
|
202
|
+
try {
|
|
203
|
+
return JSON.parse(stdout).loggedIn === true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Default Claude Code preflight: the `claude` CLI must be on PATH and
|
|
211
|
+
* `claude auth status` must report a login. Local only — no model call.
|
|
212
|
+
* An explicit ANTHROPIC_API_KEY passes without the OAuth check once the
|
|
213
|
+
* `claude` CLI is found on PATH (API-key onboarding stays out of scope;
|
|
214
|
+
* the friend path signs in via `claude auth login`). Any other failure —
|
|
215
|
+
* logged out, unparseable output, a hung or crashed `claude` — reads as
|
|
216
|
+
* not signed in; re-running the login is the remedy in every case.
|
|
217
|
+
*/
|
|
218
|
+
async function defaultClaudeClientProbe() {
|
|
219
|
+
const claudePath = findExecutableOnPath('claude');
|
|
220
|
+
if (!claudePath) {
|
|
221
|
+
return { ok: false, reason: 'not-installed' };
|
|
222
|
+
}
|
|
223
|
+
if ((process.env.ANTHROPIC_API_KEY ?? '').trim()) {
|
|
224
|
+
return { ok: true };
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const out = (0, node_child_process_1.execFileSync)(claudePath, ['auth', 'status'], {
|
|
228
|
+
encoding: 'utf8',
|
|
229
|
+
timeout: 15_000,
|
|
230
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
231
|
+
});
|
|
232
|
+
return claudeAuthStatusShowsLogin(out)
|
|
233
|
+
? { ok: true }
|
|
234
|
+
: { ok: false, reason: 'not-signed-in' };
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
// The binary vanished between the PATH scan and the exec.
|
|
238
|
+
if (error?.code === 'ENOENT') {
|
|
239
|
+
return { ok: false, reason: 'not-installed' };
|
|
240
|
+
}
|
|
241
|
+
return { ok: false, reason: 'not-signed-in' };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Test-only fake runner for the `run --detach` integration suite. The detached
|
|
246
|
+
* child is a real process, so dependency injection cannot reach it; instead the
|
|
247
|
+
* suite points AGENT_DECK_RUN_FAKE at a JSON behavior file and both the starter
|
|
248
|
+
* (deck lookup) and the child (deck lookup + model stream) honor it. Explicit
|
|
249
|
+
* `deps` always win, so unit tests are unaffected. Never set in production.
|
|
250
|
+
*/
|
|
251
|
+
exports.RUN_FAKE_ENV = 'AGENT_DECK_RUN_FAKE';
|
|
252
|
+
function loadRunFakeBehavior() {
|
|
253
|
+
const file = process.env[exports.RUN_FAKE_ENV]?.trim();
|
|
254
|
+
if (!file) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
return JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
throw new Error(`Cannot read ${exports.RUN_FAKE_ENV} behavior file: ${file}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function sleep(ms) {
|
|
265
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
266
|
+
}
|
|
267
|
+
function fakeQueryFromBehavior(behavior) {
|
|
268
|
+
const sessionId = behavior.sessionId ?? 'ses_fake';
|
|
269
|
+
return async function* ({ prompt, options }) {
|
|
270
|
+
if (behavior.recordOptionsPath) {
|
|
271
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(behavior.recordOptionsPath), { recursive: true });
|
|
272
|
+
node_fs_1.default.writeFileSync(behavior.recordOptionsPath, `${JSON.stringify({ prompt, options }, null, 2)}\n`, 'utf8');
|
|
273
|
+
}
|
|
274
|
+
if (behavior.mode === 'crash') {
|
|
275
|
+
// A crash leaves no log line behind: sync write, then out with no yield.
|
|
276
|
+
node_fs_1.default.writeSync(2, `${behavior.crashStderr ?? 'fake runner crashed'}\n`);
|
|
277
|
+
process.exit(behavior.exitCode ?? 1);
|
|
278
|
+
}
|
|
279
|
+
if (behavior.lineDelayMs) {
|
|
280
|
+
await sleep(behavior.lineDelayMs);
|
|
281
|
+
}
|
|
282
|
+
if (behavior.mode === 'fail') {
|
|
283
|
+
throw new Error(behavior.error ?? 'fake runner failed');
|
|
284
|
+
}
|
|
285
|
+
yield {
|
|
286
|
+
type: 'assistant',
|
|
287
|
+
session_id: sessionId,
|
|
288
|
+
message: { content: [{ type: 'text', text: behavior.text ?? 'fake runner here' }] },
|
|
289
|
+
};
|
|
290
|
+
if (behavior.endDelayMs) {
|
|
291
|
+
await sleep(behavior.endDelayMs);
|
|
292
|
+
}
|
|
293
|
+
yield { type: 'result', subtype: 'success', session_id: sessionId, result: 'fake ok' };
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function fakeResolveDeckFromBehavior(behavior) {
|
|
297
|
+
return async (ref) => ({
|
|
298
|
+
id: behavior.deckId ?? 'deck-fake',
|
|
299
|
+
name: behavior.deckName ?? ref,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function defaultResolveDeck(ref) {
|
|
303
|
+
return (0, backend_runtime_1.createCollectionAdmin)().resolveDeck(ref);
|
|
304
|
+
}
|
|
305
|
+
function selectResolveDeck(deps) {
|
|
306
|
+
if (deps.resolveDeck) {
|
|
307
|
+
return deps.resolveDeck;
|
|
308
|
+
}
|
|
309
|
+
const fake = loadRunFakeBehavior();
|
|
310
|
+
return fake ? fakeResolveDeckFromBehavior(fake) : defaultResolveDeck;
|
|
311
|
+
}
|
|
312
|
+
function selectClaudeProbe(deps) {
|
|
313
|
+
if (deps.claudeProbe) {
|
|
314
|
+
return deps.claudeProbe;
|
|
315
|
+
}
|
|
316
|
+
// The fake runner brings its own working client, like it brings its own
|
|
317
|
+
// deck and model stream; the real probe only guards the real transport.
|
|
318
|
+
if (loadRunFakeBehavior()) {
|
|
319
|
+
return async () => ({ ok: true });
|
|
320
|
+
}
|
|
321
|
+
return defaultClaudeClientProbe;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* `query()` for the Claude adapter: explicit deps win, then the NOT-401
|
|
325
|
+
* detach-integration fake (the detached child is a real process, so the
|
|
326
|
+
* AGENT_DECK_RUN_FAKE behavior file is its only seam), then undefined so the
|
|
327
|
+
* adapter lazily requires the real Claude Agent SDK.
|
|
328
|
+
*/
|
|
329
|
+
function selectClaudeQuery(deps) {
|
|
330
|
+
if (deps.query) {
|
|
331
|
+
return deps.query;
|
|
332
|
+
}
|
|
333
|
+
if (deps.claude?.query) {
|
|
334
|
+
return deps.claude.query;
|
|
335
|
+
}
|
|
336
|
+
const fake = loadRunFakeBehavior();
|
|
337
|
+
return fake ? fakeQueryFromBehavior(fake) : undefined;
|
|
338
|
+
}
|
|
339
|
+
function resolveAdapter(client, deps) {
|
|
340
|
+
if (client === 'codex') {
|
|
341
|
+
return (0, run_codex_1.createCodexAdapter)(deps.codex ?? {});
|
|
342
|
+
}
|
|
343
|
+
if (client === 'cursor') {
|
|
344
|
+
return (0, run_cursor_1.createCursorAdapter)(deps.cursor ?? {});
|
|
345
|
+
}
|
|
346
|
+
if (client === 'muse') {
|
|
347
|
+
return (0, run_muse_1.createMuseAdapter)(deps.muse ?? {});
|
|
348
|
+
}
|
|
349
|
+
const query = selectClaudeQuery(deps);
|
|
350
|
+
return (0, run_claude_1.createClaudeAdapter)({
|
|
351
|
+
...deps.claude,
|
|
352
|
+
...(query ? { query } : {}),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function newRunId() {
|
|
356
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+/, '');
|
|
357
|
+
return `run-${stamp}-${(0, node_crypto_1.randomBytes)(3).toString('hex')}`;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Atomic run-record write: readers (`run status`, detach tests, orchestrators)
|
|
361
|
+
* poll meta.json while the run child rewrites it, and a plain writeFileSync
|
|
362
|
+
* truncates before writing — a concurrent read lands on empty/partial bytes
|
|
363
|
+
* and JSON.parse throws. Writing to a sibling temp file and renaming keeps
|
|
364
|
+
* every observed state a complete document (rename is atomic on POSIX and
|
|
365
|
+
* Windows). The pid suffix keeps the starter's and the child's temp files
|
|
366
|
+
* distinct when both target the same meta.json.
|
|
367
|
+
*/
|
|
368
|
+
function writeRunMeta(metaPath, meta) {
|
|
369
|
+
const tmpPath = `${metaPath}.tmp-${process.pid}`;
|
|
370
|
+
node_fs_1.default.writeFileSync(tmpPath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
371
|
+
node_fs_1.default.renameSync(tmpPath, metaPath);
|
|
372
|
+
}
|
|
373
|
+
function writeMeta(metaPath, meta) {
|
|
374
|
+
writeRunMeta(metaPath, meta);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Session receipt check: the agent harness prints the bound deck as one
|
|
378
|
+
* `get_session_context` receipt line rendering `display_summary`
|
|
379
|
+
* (e.g. `◆ dev · 2 MCP · 0 keys · 1 playbooks`). Returns the mismatching deck
|
|
380
|
+
* token when the log positively shows a deck other than the requested one;
|
|
381
|
+
* a missing receipt is not a mismatch (the pre-start assignment gate is the
|
|
382
|
+
* fail-closed check; this is a second net for a mis-bound session).
|
|
383
|
+
* The fragments are joined before scanning: real clients stream token-level
|
|
384
|
+
* deltas, so one receipt line can arrive split across many text events and a
|
|
385
|
+
* per-fragment scan would miss the mismatch.
|
|
386
|
+
*/
|
|
387
|
+
function findReceiptDeckMismatch(texts, deck) {
|
|
388
|
+
const joined = texts.join('');
|
|
389
|
+
const pattern = /◆\s*([^·\r\n]+?)\s*·/g;
|
|
390
|
+
for (const match of joined.matchAll(pattern)) {
|
|
391
|
+
const token = match[1].trim();
|
|
392
|
+
if (token && token !== deck.name && token !== deck.id) {
|
|
393
|
+
return token;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Everything a run must prove before it may start streaming: a readable brief,
|
|
400
|
+
* an existing cwd, a known deck, and a folder assignment already bound to that
|
|
401
|
+
* deck. Shared by the foreground run and the `--detach` starter so both fail
|
|
402
|
+
* closed on the same inputs with the same messages.
|
|
403
|
+
*/
|
|
404
|
+
async function validateRunPrereqs(options, deps = {}) {
|
|
405
|
+
const resolveDeck = selectResolveDeck(deps);
|
|
406
|
+
const resolveLaunchPlan = deps.resolveLaunchPlan ?? mcp_launcher_1.resolveMcpLaunchPlan;
|
|
407
|
+
let promptText;
|
|
408
|
+
if (options.promptFile !== undefined) {
|
|
409
|
+
try {
|
|
410
|
+
promptText = node_fs_1.default.readFileSync(options.promptFile, 'utf8');
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return { error: `Cannot read prompt file: ${options.promptFile}` };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
promptText = options.prompt ?? '';
|
|
418
|
+
}
|
|
419
|
+
if (!promptText.trim()) {
|
|
420
|
+
return { error: 'Prompt is empty: pass a non-empty --prompt or --prompt-file' };
|
|
421
|
+
}
|
|
422
|
+
let cwdStat;
|
|
423
|
+
try {
|
|
424
|
+
cwdStat = node_fs_1.default.statSync(options.cwd);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return { error: `--cwd does not exist: ${options.cwd}` };
|
|
428
|
+
}
|
|
429
|
+
if (!cwdStat.isDirectory()) {
|
|
430
|
+
return { error: `--cwd is not a directory: ${options.cwd}` };
|
|
431
|
+
}
|
|
432
|
+
const deck = await resolveDeck(options.deckRef);
|
|
433
|
+
if (!deck) {
|
|
434
|
+
return { error: `Deck not found: ${options.deckRef}` };
|
|
435
|
+
}
|
|
436
|
+
// Fail closed before any model call: the session's MCP launch must already
|
|
437
|
+
// resolve to the requested deck. Never rewrite the folder assignment here.
|
|
438
|
+
const plan = await resolveLaunchPlan(options.cwd);
|
|
439
|
+
if (plan.deckId !== deck.id) {
|
|
440
|
+
if (!plan.deckId) {
|
|
441
|
+
return {
|
|
442
|
+
error: `No deck assigned in ${options.cwd}: run \`agent-deck use ${options.deckRef}\` there first, then retry. Refusing to start an unbound session.`,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
error: `Folder ${options.cwd} is bound to "${plan.deckName ?? plan.deckId}" (${plan.deckId}), not "${options.deckRef}". ` +
|
|
447
|
+
`Run \`agent-deck use ${options.deckRef}\` in that folder to rebind it, then retry. Refusing to start a mis-bound session.`,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
// Last and slowest (it shells out to `claude auth status`): for claude runs,
|
|
451
|
+
// the Claude Code client must be installed and signed in before any model
|
|
452
|
+
// call (NOT-405). Other clients (codex) skip this probe; their adapters own
|
|
453
|
+
// their preflight. Shared by the foreground run and the `--detach` starter,
|
|
454
|
+
// so both fail closed with the same one-line remedy instead of an SDK
|
|
455
|
+
// stack trace.
|
|
456
|
+
if (options.client === 'claude') {
|
|
457
|
+
const client = await selectClaudeProbe(deps)();
|
|
458
|
+
if (!client.ok) {
|
|
459
|
+
return {
|
|
460
|
+
error: client.reason === 'not-installed'
|
|
461
|
+
? exports.RUN_CLAUDE_NOT_INSTALLED_ERROR
|
|
462
|
+
: exports.RUN_CLAUDE_NOT_SIGNED_IN_ERROR,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { promptText, deck };
|
|
467
|
+
}
|
|
468
|
+
function printRunHelp() {
|
|
469
|
+
console.log(`Usage:
|
|
470
|
+
agent-deck run <deck> --client claude|codex|cursor|muse --cwd <dir> (--prompt <text>|--prompt-file <path>) [--tools readonly|edit] [--log <path>] [--resume <sessionId>] [--detach]
|
|
471
|
+
agent-deck run status <runId> [--json]
|
|
472
|
+
agent-deck run log <runId> [--tail <n>] [--follow] [--json]
|
|
473
|
+
agent-deck run list [--json] [--limit <n>]
|
|
474
|
+
|
|
475
|
+
Starts a local agent session bound to <deck> and streams a JSONL log.
|
|
476
|
+
The folder's assignment must already name <deck> (agent-deck use <deck>);
|
|
477
|
+
run never rewrites .agent-deck/use.json and fails closed on a mismatch.
|
|
478
|
+
Claude runs require the Claude Code CLI on this machine, signed in
|
|
479
|
+
('claude auth login'); otherwise run fails before any model call.
|
|
480
|
+
A set ANTHROPIC_API_KEY skips the sign-in check (the SDK accepts the
|
|
481
|
+
key instead); API-key setup is otherwise out of scope for this path.
|
|
482
|
+
|
|
483
|
+
--client claude|codex|cursor|muse
|
|
484
|
+
Required.
|
|
485
|
+
codex maps --tools to -s read-only|workspace-write;
|
|
486
|
+
cursor maps --tools edit to --force (readonly passes
|
|
487
|
+
no write flag); all log lines land when the run ends;
|
|
488
|
+
muse maps readonly to --disable-write --disable-shell
|
|
489
|
+
(edit uses the default bounded permissions).
|
|
490
|
+
--cwd <dir> Required. Session working directory.
|
|
491
|
+
--prompt <text> Brief text (exactly one of --prompt/--prompt-file).
|
|
492
|
+
--prompt-file <path> Brief read from a file.
|
|
493
|
+
--tools readonly|edit Tool profile, default readonly. edit adds Edit, Write
|
|
494
|
+
and git-scoped Bash (diff/add/commit/checkout -b).
|
|
495
|
+
--log <path> Log file. Default: ~/.agent-deck/runs/<runId>/run.jsonl
|
|
496
|
+
(meta.json sits next to the log in both cases).
|
|
497
|
+
--resume <sessionId> Continue a previous session id. The profile
|
|
498
|
+
passed on this invocation applies; nothing is inherited.
|
|
499
|
+
codex resume keeps the thread's original sandbox, so
|
|
500
|
+
--tools edit cannot be resumed and fails closed.
|
|
501
|
+
--detach Start in the background in a new session, so the run
|
|
502
|
+
survives this shell disconnecting. Prints runId, pid,
|
|
503
|
+
and the log path, then exits. Every other flag is
|
|
504
|
+
passed to the run unchanged. Stop it with kill <pid>.
|
|
505
|
+
|
|
506
|
+
Poll a run without parsing files by hand:
|
|
507
|
+
|
|
508
|
+
status <runId> State (running|ended|error|died), sessionId, log path.
|
|
509
|
+
log <runId> One readable line per event; --json prints raw JSONL.
|
|
510
|
+
list Recent runs, newest first (default limit 20).`);
|
|
511
|
+
}
|
|
512
|
+
async function runRun(options, deps = {}) {
|
|
513
|
+
const homeDir = deps.homeDir ?? (0, paths_1.agentDeckHome)();
|
|
514
|
+
const prereq = await validateRunPrereqs(options, deps);
|
|
515
|
+
if ('error' in prereq) {
|
|
516
|
+
console.error(prereq.error);
|
|
517
|
+
return 1;
|
|
518
|
+
}
|
|
519
|
+
const { promptText, deck } = prereq;
|
|
520
|
+
const adapter = resolveAdapter(options.client, deps);
|
|
521
|
+
const preflight = await adapter.preflight();
|
|
522
|
+
if (!preflight.ok) {
|
|
523
|
+
console.error(preflight.remedy);
|
|
524
|
+
return 1;
|
|
525
|
+
}
|
|
526
|
+
const startPlan = adapter.planStart({
|
|
527
|
+
cwd: options.cwd,
|
|
528
|
+
prompt: promptText,
|
|
529
|
+
tools: options.tools,
|
|
530
|
+
...(options.resume ? { resumeId: options.resume } : {}),
|
|
531
|
+
});
|
|
532
|
+
if (!startPlan.ok) {
|
|
533
|
+
console.error(startPlan.remedy);
|
|
534
|
+
return 1;
|
|
535
|
+
}
|
|
536
|
+
const runId = options.runIdOverride ?? newRunId();
|
|
537
|
+
const logPath = options.log ?? node_path_1.default.join(homeDir, 'runs', runId, 'run.jsonl');
|
|
538
|
+
const metaPath = node_path_1.default.join(node_path_1.default.dirname(logPath), 'meta.json');
|
|
539
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(logPath), { recursive: true });
|
|
540
|
+
const meta = {
|
|
541
|
+
runId,
|
|
542
|
+
deck: deck.id,
|
|
543
|
+
client: options.client,
|
|
544
|
+
tools: options.tools,
|
|
545
|
+
cwd: options.cwd,
|
|
546
|
+
pid: process.pid,
|
|
547
|
+
startedAt: new Date().toISOString(),
|
|
548
|
+
state: 'running',
|
|
549
|
+
...(startPlan.trustOverride ? { trustOverride: true } : {}),
|
|
550
|
+
};
|
|
551
|
+
writeMeta(metaPath, meta);
|
|
552
|
+
const appendLine = (line) => {
|
|
553
|
+
node_fs_1.default.appendFileSync(logPath, `${JSON.stringify(line)}\n`, 'utf8');
|
|
554
|
+
};
|
|
555
|
+
let sessionId = options.resume ?? null;
|
|
556
|
+
let metaSessionWritten = false;
|
|
557
|
+
const captureSessionId = (value) => {
|
|
558
|
+
if (typeof value === 'string' && value) {
|
|
559
|
+
sessionId = value;
|
|
560
|
+
if (!metaSessionWritten) {
|
|
561
|
+
metaSessionWritten = true;
|
|
562
|
+
meta.sessionId = value;
|
|
563
|
+
writeMeta(metaPath, meta);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
meta.sessionId = value;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
let lastResultSubtype;
|
|
571
|
+
const seenTexts = [];
|
|
572
|
+
try {
|
|
573
|
+
const stream = adapter.start({
|
|
574
|
+
cwd: options.cwd,
|
|
575
|
+
prompt: promptText,
|
|
576
|
+
tools: options.tools,
|
|
577
|
+
...(options.resume ? { resumeId: options.resume } : {}),
|
|
578
|
+
});
|
|
579
|
+
for await (const event of stream) {
|
|
580
|
+
if (event.sessionId !== undefined) {
|
|
581
|
+
captureSessionId(event.sessionId);
|
|
582
|
+
}
|
|
583
|
+
if (event.kind === 'text') {
|
|
584
|
+
seenTexts.push(event.text);
|
|
585
|
+
appendLine({ t: 'text', ts: new Date().toISOString(), text: event.text });
|
|
586
|
+
}
|
|
587
|
+
else if (event.kind === 'tool') {
|
|
588
|
+
appendLine({ t: 'tool', ts: new Date().toISOString(), name: event.name });
|
|
589
|
+
}
|
|
590
|
+
else if (event.kind === 'result') {
|
|
591
|
+
lastResultSubtype = event.subtype;
|
|
592
|
+
appendLine({
|
|
593
|
+
t: 'result',
|
|
594
|
+
ts: new Date().toISOString(),
|
|
595
|
+
sessionId,
|
|
596
|
+
subtype: event.subtype,
|
|
597
|
+
result: event.result,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
appendLine({
|
|
602
|
+
t: 'notice',
|
|
603
|
+
ts: new Date().toISOString(),
|
|
604
|
+
message: event.message,
|
|
605
|
+
sessionId,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
appendLine({ t: 'end', ts: new Date().toISOString(), sessionId });
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
appendLine({
|
|
613
|
+
t: 'error',
|
|
614
|
+
ts: new Date().toISOString(),
|
|
615
|
+
message: String(error instanceof Error ? error.message : error).slice(0, 500),
|
|
616
|
+
sessionId,
|
|
617
|
+
});
|
|
618
|
+
meta.state = 'error';
|
|
619
|
+
if (sessionId) {
|
|
620
|
+
meta.sessionId = sessionId;
|
|
621
|
+
}
|
|
622
|
+
writeMeta(metaPath, meta);
|
|
623
|
+
console.log(`run ${runId} error: log ${logPath} sessionId ${sessionId ?? 'unknown'}`);
|
|
624
|
+
return 1;
|
|
625
|
+
}
|
|
626
|
+
let success = lastResultSubtype === 'success';
|
|
627
|
+
if (success) {
|
|
628
|
+
const mismatch = findReceiptDeckMismatch(seenTexts, deck);
|
|
629
|
+
if (mismatch) {
|
|
630
|
+
success = false;
|
|
631
|
+
console.error(`Session receipt names deck "${mismatch}", not "${deck.name}": marking run error. ` +
|
|
632
|
+
`Rebind the folder with \`agent-deck use ${options.deckRef}\` and retry.`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
meta.state = success ? 'ended' : 'error';
|
|
636
|
+
if (sessionId) {
|
|
637
|
+
meta.sessionId = sessionId;
|
|
638
|
+
}
|
|
639
|
+
writeMeta(metaPath, meta);
|
|
640
|
+
console.log(`run ${runId} ${meta.state}: log ${logPath} sessionId ${sessionId ?? 'unknown'}`);
|
|
641
|
+
return success ? 0 : 1;
|
|
642
|
+
}
|
|
643
|
+
async function runRunCommand(args, deps = {}) {
|
|
644
|
+
// Poll subcommands (NOT-402) share the `run` prefix with launch (NOT-400).
|
|
645
|
+
const [head, ...tail] = args;
|
|
646
|
+
if (head === 'status' || head === 'log' || head === 'list') {
|
|
647
|
+
const inspect = await Promise.resolve().then(() => __importStar(require('./run-status')));
|
|
648
|
+
if (head === 'status') {
|
|
649
|
+
return inspect.runRunStatusCommand(tail, { homeDir: deps.homeDir });
|
|
650
|
+
}
|
|
651
|
+
if (head === 'log') {
|
|
652
|
+
return inspect.runRunLogCommand(tail, {
|
|
653
|
+
homeDir: deps.homeDir,
|
|
654
|
+
...(deps.followPollMs !== undefined ? { followPollMs: deps.followPollMs } : {}),
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return inspect.runRunListCommand(tail, { homeDir: deps.homeDir });
|
|
658
|
+
}
|
|
659
|
+
const parsed = parseRunArgs(args);
|
|
660
|
+
if ('error' in parsed) {
|
|
661
|
+
if (parsed.error === 'help') {
|
|
662
|
+
printRunHelp();
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
console.error(parsed.error);
|
|
666
|
+
return 1;
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
if (parsed.detach) {
|
|
670
|
+
const { startDetachedRun } = await Promise.resolve().then(() => __importStar(require('./run-detach')));
|
|
671
|
+
return await startDetachedRun(args, parsed, deps);
|
|
672
|
+
}
|
|
673
|
+
return await runRun(parsed, deps);
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
console.error(error instanceof Error ? error.message : error);
|
|
677
|
+
return 1;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
//# sourceMappingURL=run.js.map
|