brainclaw 1.5.5 → 1.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.
Files changed (43) hide show
  1. package/dist/brainclaw-vscode.vsix +0 -0
  2. package/dist/cli.js +124 -7
  3. package/dist/commands/bootstrap-loop.js +206 -0
  4. package/dist/commands/loop.js +156 -0
  5. package/dist/commands/loops-handlers.js +110 -55
  6. package/dist/commands/mcp-read-handlers.js +37 -0
  7. package/dist/commands/mcp.js +621 -202
  8. package/dist/commands/questions.js +180 -0
  9. package/dist/commands/reply.js +190 -0
  10. package/dist/commands/session-end.js +105 -3
  11. package/dist/commands/session-start.js +32 -53
  12. package/dist/commands/switch.js +17 -1
  13. package/dist/core/agentrun-reconciler.js +65 -0
  14. package/dist/core/claims.js +29 -0
  15. package/dist/core/dispatch-status.js +219 -0
  16. package/dist/core/entity-operations.js +128 -9
  17. package/dist/core/execution-adapters.js +38 -2
  18. package/dist/core/facade-schema.js +55 -0
  19. package/dist/core/federation-cloud.js +27 -12
  20. package/dist/core/federation-materialize.js +57 -0
  21. package/dist/core/instruction-templates.js +2 -0
  22. package/dist/core/loops/bootstrap-acquire.js +195 -0
  23. package/dist/core/loops/facade-schema.js +68 -1
  24. package/dist/core/loops/hooks/bootstrap-write.js +144 -0
  25. package/dist/core/loops/hooks/notify-operator.js +148 -0
  26. package/dist/core/loops/hooks/survey-source-reader.js +256 -0
  27. package/dist/core/loops/index.js +8 -2
  28. package/dist/core/loops/next-expected.js +63 -0
  29. package/dist/core/loops/presets/bootstrap.js +75 -0
  30. package/dist/core/loops/presets/index.js +16 -0
  31. package/dist/core/loops/store.js +224 -4
  32. package/dist/core/loops/types.js +346 -1
  33. package/dist/core/loops/verbs.js +739 -6
  34. package/dist/core/schema.js +28 -2
  35. package/dist/core/state.js +62 -0
  36. package/dist/facts.js +7 -5
  37. package/dist/facts.json +6 -4
  38. package/docs/concepts/dispatch-lifecycle.md +228 -0
  39. package/docs/concepts/loop-engine.md +55 -0
  40. package/docs/concepts/multi-agent-workflows.md +167 -166
  41. package/docs/concepts/troubleshooting.md +10 -2
  42. package/docs/integrations/overview.md +14 -12
  43. package/package.json +1 -1
Binary file
package/dist/cli.js CHANGED
@@ -105,6 +105,8 @@ import { runMigrate } from './commands/migrate.js';
105
105
  import { runRunProfile } from './commands/run-profile.js';
106
106
  import { runCompact } from './commands/compact.js';
107
107
  import { runHarvestCandidates } from './commands/harvest.js';
108
+ import { runQuestionsCommand } from './commands/questions.js';
109
+ import { runReplyCommand } from './commands/reply.js';
108
110
  import { requireRegisteredAgentIdentity } from './core/agent-registry.js';
109
111
  const program = new Command();
110
112
  function collect(value, previous) {
@@ -194,8 +196,10 @@ program
194
196
  logger.info(`Cleaned ${removed} orphan lock/tmp file(s) in ${memoryDir()}`);
195
197
  }
196
198
  }
197
- else if (explicitCwd) {
198
- // For init/setup, still respect explicit --cwd / --project but nothing else
199
+ else if (explicitCwd && cmdName !== 'init') {
200
+ // For setup commands, still respect explicit --cwd / --project but nothing else.
201
+ // init receives cwd through runInit options so scaffold writers do not
202
+ // depend on changing process.cwd().
199
203
  process.chdir(path.resolve(explicitCwd));
200
204
  }
201
205
  });
@@ -212,8 +216,13 @@ program
212
216
  .option('--no-analyze-repo', 'Skip repository analysis when suggesting a project mode')
213
217
  .option('--no-ai-scan', 'Skip AI surface scan during init')
214
218
  .option('--scan', 'Scan subdirectories for service boundaries and suggest init targets')
219
+ .option('--cwd <path>', 'Override working directory for init scaffolding (parity with other CLI commands)')
215
220
  .action(async (options) => {
216
- await runInit(options);
221
+ // pln#515 step 1: commander binds --cwd to the program-level option even
222
+ // when it appears after `init`, so resolve via program.opts() and feed
223
+ // runInit's existing options.cwd plumb.
224
+ const programCwd = program.opts().cwd;
225
+ await runInit({ ...options, cwd: options.cwd ?? programCwd });
217
226
  });
218
227
  // --- setup ---
219
228
  program
@@ -1296,8 +1305,8 @@ program
1296
1305
  .option('--maintenance-mode <mode>', 'Maintenance mode: full (default) or fast')
1297
1306
  .option('--include-context', 'Output full project context after starting session (replaces separate context call)')
1298
1307
  .option('--json', 'Output as JSON')
1299
- .action((options) => {
1300
- runSessionStart(options);
1308
+ .action(async (options) => {
1309
+ await runSessionStart(options);
1301
1310
  });
1302
1311
  // --- session-end ---
1303
1312
  program
@@ -1313,8 +1322,8 @@ program
1313
1322
  .option('--reviewer <name>', 'Explicit reviewer to route the reflected handoff review to')
1314
1323
  .option('--reflect', 'Include structured reflection questions for the agent to answer')
1315
1324
  .option('--json', 'Output as JSON')
1316
- .action((options) => {
1317
- runSessionEnd({
1325
+ .action(async (options) => {
1326
+ await runSessionEnd({
1318
1327
  ...options,
1319
1328
  autoReflect: options.autoReflect,
1320
1329
  autoRelease: options.autoRelease,
@@ -1873,6 +1882,114 @@ if (isCodevEnabled()) {
1873
1882
  runCodevMetrics(thread, { ...options, cwd: globalOpts.cwd });
1874
1883
  });
1875
1884
  }
1885
+ // --- questions (operator-question artifacts across loops; pln#508 step 4) ---
1886
+ program
1887
+ .command('questions')
1888
+ .description('List pending operator_question artifacts across loops in the current project')
1889
+ .option('--loop <loop_id>', 'Filter to a single loop')
1890
+ .option('--status <status>', 'Filter by status: awaiting (default), answered, timed_out', 'awaiting')
1891
+ .option('--mine', 'Filter to questions targeted at the current agent (v1 heuristic: humans see all awaiting)')
1892
+ .option('--json', 'Output as JSON')
1893
+ .action((options) => {
1894
+ const globalOpts = program.opts();
1895
+ const status = options.status;
1896
+ if (!['awaiting', 'answered', 'timed_out'].includes(status)) {
1897
+ console.error(`Error: --status must be one of awaiting|answered|timed_out (got "${options.status}")`);
1898
+ process.exit(1);
1899
+ }
1900
+ runQuestionsCommand({
1901
+ loop: options.loop,
1902
+ status,
1903
+ mine: options.mine,
1904
+ json: options.json,
1905
+ }, globalOpts.cwd);
1906
+ });
1907
+ // --- bootstrap-loop (open/join/status/cancel a bootstrap loop; pln#513 step 3) ---
1908
+ program
1909
+ .command('bootstrap-loop')
1910
+ .description('Open or join a bootstrap loop on the current project, or query its status')
1911
+ .option('--status', 'Report current state')
1912
+ .option('--cancel', 'Cancel the active bootstrap loop')
1913
+ .option('--yes', 'Skip confirmation prompts')
1914
+ .option('--json', 'Machine-readable output')
1915
+ .action(async (options) => {
1916
+ const globalOpts = program.opts();
1917
+ const { runBootstrapLoopCommand } = await import('./commands/bootstrap-loop.js');
1918
+ await runBootstrapLoopCommand(options, globalOpts.cwd);
1919
+ });
1920
+ // --- loop (drive loop turn verbs; pln#517 step 2) ---
1921
+ const loopCmd = program
1922
+ .command('loop')
1923
+ .description('Drive a loop turn (turn / complete-turn / advance / add-artifact)');
1924
+ loopCmd
1925
+ .command('turn <loop_id>')
1926
+ .description('Issue a turn assignment on a slot')
1927
+ .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
1928
+ .option('--input <text>', 'Free-form input passed to the slot')
1929
+ .option('--role <role>', 'Slot role (resolves the first non-done slot with that role)')
1930
+ .option('--assignment-id <id>', 'Dispatcher-provided assignment id to record on the slot')
1931
+ .option('--json', 'Machine-readable output')
1932
+ .action(async (loop_id, options) => {
1933
+ const globalOpts = program.opts();
1934
+ const { runLoopCommand } = await import('./commands/loop.js');
1935
+ await runLoopCommand('turn', { loop_id }, options, globalOpts.cwd);
1936
+ });
1937
+ loopCmd
1938
+ .command('complete-turn <loop_id>')
1939
+ .description('Complete a slot turn')
1940
+ .requiredOption('--slot <slot_id>', 'Target slot id (lsl_...)')
1941
+ .requiredOption('--outcome <outcome>', 'Turn outcome: done, failed, or cancelled')
1942
+ .option('--failure-reason <text>', 'Reason when outcome is failed')
1943
+ .option('--artifact <json>', 'JSON object payload for an artifact to attach')
1944
+ .option('--json', 'Machine-readable output')
1945
+ .action(async (loop_id, options) => {
1946
+ const globalOpts = program.opts();
1947
+ const { runLoopCommand } = await import('./commands/loop.js');
1948
+ await runLoopCommand('complete-turn', { loop_id }, options, globalOpts.cwd);
1949
+ });
1950
+ loopCmd
1951
+ .command('advance <loop_id>')
1952
+ .description('Advance a loop to its next phase')
1953
+ .option('--to-phase <name>', 'Explicit target phase')
1954
+ .option('--force', 'Bypass phase gate checks')
1955
+ .option('--reason <text>', 'Reason to record on the phase advance event')
1956
+ .option('--json', 'Machine-readable output')
1957
+ .action(async (loop_id, options) => {
1958
+ const globalOpts = program.opts();
1959
+ const { runLoopCommand } = await import('./commands/loop.js');
1960
+ await runLoopCommand('advance', { loop_id }, options, globalOpts.cwd);
1961
+ });
1962
+ loopCmd
1963
+ .command('add-artifact <loop_id>')
1964
+ .description('Attach an artifact to a loop')
1965
+ .requiredOption('--phase <phase>', 'Artifact phase')
1966
+ .requiredOption('--type <type>', 'Artifact type')
1967
+ .requiredOption('--body <json-or-text>', 'Artifact body as JSON or text')
1968
+ .option('--produced-by <agent>', 'Agent that produced the artifact')
1969
+ .option('--ref <ref>', 'JSON ref object, e.g. {"kind":"plan","id":"pln_..."}')
1970
+ .option('--json', 'Machine-readable output')
1971
+ .action(async (loop_id, options) => {
1972
+ const globalOpts = program.opts();
1973
+ const { runLoopCommand } = await import('./commands/loop.js');
1974
+ await runLoopCommand('add-artifact', { loop_id }, options, globalOpts.cwd);
1975
+ });
1976
+ // --- reply (provide_input to an operator_question; pln#508 step 4) ---
1977
+ program
1978
+ .command('reply <qst_id>')
1979
+ .description('Resolve an operator_question artifact (wraps bclaw_loop.provide_input)')
1980
+ .option('--answer <text>', 'Free-form answer text')
1981
+ .option('--choose <option_id>', 'Pick one of the question\'s structured options[].id')
1982
+ .option('--skip', 'Materialize the question\'s suggested_default')
1983
+ .option('--json', 'Output as JSON')
1984
+ .action((qstId, options) => {
1985
+ const globalOpts = program.opts();
1986
+ runReplyCommand(qstId, {
1987
+ answer: options.answer,
1988
+ choose: options.choose,
1989
+ skip: options.skip,
1990
+ json: options.json,
1991
+ }, globalOpts.cwd);
1992
+ });
1876
1993
  // --- run (agent profiles) ---
1877
1994
  program
1878
1995
  .command('run [profile-name]')
@@ -0,0 +1,206 @@
1
+ import readline from 'node:readline';
2
+ import { memoryExists } from '../core/io.js';
3
+ import { acquireBootstrapLoop, BootstrapCoordinationInProgressError, closeLoop, computeNextExpected, findExistingBootstrapLoop, } from '../core/loops/index.js';
4
+ import { resolveCurrentAgentName } from '../core/agent-registry.js';
5
+ function fail(message, exitCode, opts) {
6
+ if (opts.json) {
7
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
8
+ }
9
+ else {
10
+ console.error(`Error: ${message}`);
11
+ }
12
+ process.exit(exitCode);
13
+ }
14
+ function parseQuestionBody(artifact) {
15
+ if (artifact.type !== 'operator_question' || !artifact.body)
16
+ return undefined;
17
+ try {
18
+ return JSON.parse(artifact.body);
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ function formatNextExpected(hint) {
25
+ if (!hint)
26
+ return ' (loop has no further expected action)';
27
+ const bits = [` next: ${hint.action} (${hint.intent})`];
28
+ if (hint.phase)
29
+ bits.push(` phase: ${hint.phase}`);
30
+ if (hint.slot_id)
31
+ bits.push(` slot: ${hint.slot_id}${hint.role ? ` [${hint.role}]` : ''}`);
32
+ if (hint.from_phase && hint.to_phase)
33
+ bits.push(` ${hint.from_phase} → ${hint.to_phase}`);
34
+ if (hint.blocking_on.length)
35
+ bits.push(` blocking_on: ${hint.blocking_on.join(', ')}`);
36
+ if (hint.reason)
37
+ bits.push(` reason: ${hint.reason}`);
38
+ return bits.join('\n');
39
+ }
40
+ function buildResult(action, loop) {
41
+ const hint = action === 'cancelled' ? null : computeNextExpected(loop);
42
+ const openQuestions = loop.open_questions;
43
+ const result = {
44
+ ok: true,
45
+ action,
46
+ loop_id: loop.id,
47
+ current_phase: loop.current_phase,
48
+ status: loop.status,
49
+ open_questions: openQuestions,
50
+ next_expected: hint,
51
+ };
52
+ if (loop.pause_reason) {
53
+ result.pause_reason = loop.pause_reason;
54
+ }
55
+ if (loop.pending_file_apply) {
56
+ result.pending_file_apply = {
57
+ target_path: loop.pending_file_apply.target_path,
58
+ artifact_id: loop.pending_file_apply.artifact_id,
59
+ diff_artifact_id: loop.pending_file_apply.diff_artifact_id,
60
+ };
61
+ }
62
+ if (action === 'joined') {
63
+ result.joined_existing = true;
64
+ }
65
+ return result;
66
+ }
67
+ function printHuman(result, loop) {
68
+ const prefix = result.action === 'opened'
69
+ ? '✔ Opened bootstrap loop'
70
+ : result.action === 'joined'
71
+ ? '↺ Joined existing bootstrap loop'
72
+ : result.action === 'cancelled'
73
+ ? '✔ Cancelled bootstrap loop'
74
+ : '• Bootstrap loop status';
75
+ console.log(`${prefix} ${loop.id}`);
76
+ console.log(` phase: ${loop.current_phase}`);
77
+ console.log(` status: ${loop.status}`);
78
+ if (result.pause_reason) {
79
+ console.log(` pause_reason: ${result.pause_reason}`);
80
+ }
81
+ if (loop.open_questions.length > 0) {
82
+ console.log(` open_questions: ${loop.open_questions.length} (${loop.open_questions.join(', ')})`);
83
+ for (const q of loop.artifacts) {
84
+ const body = parseQuestionBody(q);
85
+ if (body && loop.open_questions.includes(body.question_id)) {
86
+ console.log(` - ${body.question_id}: ${body.question_text}`);
87
+ }
88
+ }
89
+ }
90
+ if (result.pending_file_apply) {
91
+ console.log(` pending_file_apply: ${result.pending_file_apply.target_path}`);
92
+ }
93
+ if (loop.slots.length > 0) {
94
+ const slotDesc = loop.slots
95
+ .map((s) => `${s.role}${s.agent ? `=${s.agent}` : ''} [${s.status}]`)
96
+ .join(', ');
97
+ console.log(` slots: ${slotDesc}`);
98
+ }
99
+ if (result.action !== 'cancelled') {
100
+ console.log(formatNextExpected(result.next_expected ?? null));
101
+ }
102
+ }
103
+ async function confirmCancel(loopId) {
104
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
105
+ try {
106
+ const answer = await new Promise((resolve) => {
107
+ rl.question(`Cancel bootstrap loop ${loopId}? This sets status='cancelled'. [y/N] `, (a) => resolve(a));
108
+ });
109
+ return /^y(es)?$/i.test(answer.trim());
110
+ }
111
+ finally {
112
+ rl.close();
113
+ }
114
+ }
115
+ /**
116
+ * `brainclaw bootstrap-loop` — open, join, query, or cancel the bootstrap
117
+ * loop on the current project. The open/join path delegates to
118
+ * `acquireBootstrapLoop` (src/core/loops/bootstrap-acquire.ts) which
119
+ * implements the same coordination-lock singleton acquire used by the MCP
120
+ * bclaw_coordinate facade. Two concurrent CLI invocations will now converge
121
+ * on the same loop rather than opening duplicates (pln#518 step 1).
122
+ */
123
+ export async function runBootstrapLoopCommand(options = {}, cwd) {
124
+ if (!memoryExists(cwd)) {
125
+ fail('.brainclaw/ not found. Run `brainclaw init` first.', 1, options);
126
+ }
127
+ const modes = [];
128
+ if (options.status)
129
+ modes.push('--status');
130
+ if (options.cancel)
131
+ modes.push('--cancel');
132
+ if (modes.length > 1) {
133
+ fail(`--status and --cancel are mutually exclusive (got: ${modes.join(', ')})`, 1, options);
134
+ }
135
+ if (options.status) {
136
+ const existing = findExistingBootstrapLoop(cwd);
137
+ if (!existing) {
138
+ fail('no active bootstrap loop on this project. Run `brainclaw bootstrap-loop` to open one.', 1, options);
139
+ }
140
+ const result = buildResult('status', existing);
141
+ if (options.json) {
142
+ console.log(JSON.stringify(result, null, 2));
143
+ return;
144
+ }
145
+ printHuman(result, existing);
146
+ return;
147
+ }
148
+ if (options.cancel) {
149
+ const existing = findExistingBootstrapLoop(cwd);
150
+ if (!existing) {
151
+ fail('no active bootstrap loop to cancel on this project.', 1, options);
152
+ }
153
+ if (!options.yes) {
154
+ const confirmed = await confirmCancel(existing.id);
155
+ if (!confirmed) {
156
+ fail('cancellation aborted by operator.', 1, options);
157
+ }
158
+ }
159
+ const actor = resolveCurrentAgentName(cwd);
160
+ let closed;
161
+ try {
162
+ closed = closeLoop({
163
+ id: existing.id,
164
+ final_status: 'cancelled',
165
+ reason: 'operator_cancelled',
166
+ actor,
167
+ }, cwd);
168
+ }
169
+ catch (err) {
170
+ const msg = err instanceof Error ? err.message : String(err);
171
+ fail(`closeLoop verb rejected the call: ${msg}`, 2, options);
172
+ }
173
+ const result = buildResult('cancelled', closed);
174
+ if (options.json) {
175
+ console.log(JSON.stringify(result, null, 2));
176
+ return;
177
+ }
178
+ printHuman(result, closed);
179
+ return;
180
+ }
181
+ // No-args: delegate to the singleton acquire path (pln#518 step 1).
182
+ // acquireBootstrapLoop handles find-existing + coordination-lock + openLoop,
183
+ // preventing two concurrent CLI invocations from both calling openLoop.
184
+ const actor = resolveCurrentAgentName(cwd);
185
+ let loop;
186
+ let action;
187
+ try {
188
+ const acquired = acquireBootstrapLoop({ actor }, cwd);
189
+ loop = acquired.loop;
190
+ action = acquired.action;
191
+ }
192
+ catch (err) {
193
+ if (err instanceof BootstrapCoordinationInProgressError) {
194
+ fail(err.message, 2, options);
195
+ }
196
+ const msg = err instanceof Error ? err.message : String(err);
197
+ fail(`bootstrap-loop acquire failed: ${msg}`, 2, options);
198
+ }
199
+ const result = buildResult(action, loop);
200
+ if (options.json) {
201
+ console.log(JSON.stringify(result, null, 2));
202
+ return;
203
+ }
204
+ printHuman(result, loop);
205
+ }
206
+ //# sourceMappingURL=bootstrap-loop.js.map
@@ -0,0 +1,156 @@
1
+ import { memoryExists } from '../core/io.js';
2
+ import { handleBclawLoop } from './loops-handlers.js';
3
+ function fail(message, exitCode, opts) {
4
+ if (opts.json) {
5
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
6
+ }
7
+ else {
8
+ console.error(`Error: ${message}`);
9
+ }
10
+ process.exit(exitCode);
11
+ }
12
+ function requireLoopId(args, opts) {
13
+ const loopId = args.loop_id;
14
+ if (!loopId || !/^lop_[0-9a-z]+$/.test(loopId)) {
15
+ fail(`invalid loop_id "${loopId ?? ''}" — expected format lop_<hex>`, 1, opts);
16
+ }
17
+ return loopId;
18
+ }
19
+ function requireOption(value, flag, opts) {
20
+ if (value === undefined || value === '') {
21
+ fail(`${flag} is required`, 1, opts);
22
+ }
23
+ return value;
24
+ }
25
+ function parseJsonObject(raw, flag, opts) {
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(raw);
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ fail(`${flag} must be valid JSON object syntax: ${message}`, 1, opts);
33
+ }
34
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
35
+ fail(`${flag} must be a JSON object`, 1, opts);
36
+ }
37
+ return parsed;
38
+ }
39
+ function parseOptionalRef(raw, opts) {
40
+ if (raw === undefined)
41
+ return undefined;
42
+ const parsed = parseJsonObject(raw, '--ref', opts);
43
+ if (typeof parsed.kind !== 'string' || typeof parsed.id !== 'string') {
44
+ fail('--ref must be a JSON object with string fields { "kind", "id" }', 1, opts);
45
+ }
46
+ return { kind: parsed.kind, id: parsed.id };
47
+ }
48
+ function parseBody(raw) {
49
+ try {
50
+ return JSON.stringify(JSON.parse(raw));
51
+ }
52
+ catch {
53
+ return raw;
54
+ }
55
+ }
56
+ function parseOutcome(opts) {
57
+ const outcome = opts.outcome;
58
+ if (outcome !== 'done' && outcome !== 'failed' && outcome !== 'cancelled') {
59
+ fail(`--outcome must be one of done|failed|cancelled (got "${outcome ?? ''}")`, 1, opts);
60
+ }
61
+ return outcome;
62
+ }
63
+ function formatNextExpected(hint) {
64
+ if (!hint)
65
+ return ' (loop has no further expected action)';
66
+ const bits = [` next: ${hint.action} (${hint.intent})`];
67
+ if (hint.phase)
68
+ bits.push(` phase: ${hint.phase}`);
69
+ if (hint.slot_id)
70
+ bits.push(` slot: ${hint.slot_id}${hint.role ? ` [${hint.role}]` : ''}`);
71
+ if (hint.from_phase && hint.to_phase)
72
+ bits.push(` ${hint.from_phase} -> ${hint.to_phase}`);
73
+ if (hint.blocking_on.length)
74
+ bits.push(` blocking_on: ${hint.blocking_on.join(', ')}`);
75
+ if (hint.reason)
76
+ bits.push(` reason: ${hint.reason}`);
77
+ return bits.join('\n');
78
+ }
79
+ function buildRequest(subcommand, loopId, opts) {
80
+ switch (subcommand) {
81
+ case 'turn':
82
+ return {
83
+ intent: 'turn',
84
+ loop_id: loopId,
85
+ slot_id: requireOption(opts.slot, '--slot <slot_id>', opts),
86
+ input: requireOption(opts.input, '--input <text>', opts),
87
+ role: opts.role,
88
+ assignment_id: opts.assignmentId,
89
+ };
90
+ case 'complete-turn': {
91
+ const artifact = opts.artifact
92
+ ? parseJsonObject(opts.artifact, '--artifact', opts)
93
+ : undefined;
94
+ return {
95
+ intent: 'complete_turn',
96
+ loop_id: loopId,
97
+ slot_id: requireOption(opts.slot, '--slot <slot_id>', opts),
98
+ outcome: parseOutcome(opts),
99
+ failure_reason: opts.failureReason,
100
+ artifact,
101
+ };
102
+ }
103
+ case 'advance':
104
+ return {
105
+ intent: 'advance',
106
+ loop_id: loopId,
107
+ to_phase: opts.toPhase,
108
+ force: opts.force,
109
+ reason: opts.reason,
110
+ };
111
+ case 'add-artifact':
112
+ return {
113
+ intent: 'add_artifact',
114
+ loop_id: loopId,
115
+ artifact: {
116
+ phase: requireOption(opts.phase, '--phase <phase>', opts),
117
+ type: requireOption(opts.type, '--type <type>', opts),
118
+ body: parseBody(requireOption(opts.body, '--body <json-or-text>', opts)),
119
+ produced_by: opts.producedBy,
120
+ ref: parseOptionalRef(opts.ref, opts),
121
+ },
122
+ };
123
+ }
124
+ }
125
+ export function runLoopCommand(subcommand, args, options = {}, cwd) {
126
+ if (!memoryExists(cwd)) {
127
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
128
+ process.exit(1);
129
+ }
130
+ const loopId = requireLoopId(args, options);
131
+ const request = buildRequest(subcommand, loopId, options);
132
+ const handled = handleBclawLoop({ args: request, cwd });
133
+ if (handled.response.status !== 'ok') {
134
+ const message = handled.response.error ?? handled.summary;
135
+ fail(`bclaw_loop.${String(request.intent)} rejected the call: ${message}`, 2, options);
136
+ }
137
+ const result = handled.response.result;
138
+ const loop = result.loop;
139
+ const out = {
140
+ ok: true,
141
+ action: subcommand,
142
+ loop_id: loop?.id ?? loopId,
143
+ current_phase: loop?.current_phase,
144
+ status: loop?.status,
145
+ next_expected: result.next_expected ?? null,
146
+ auto_closed: result.auto_closed || undefined,
147
+ };
148
+ if (options.json) {
149
+ console.log(JSON.stringify(out, null, 2));
150
+ return out;
151
+ }
152
+ console.log(`OK loop ${subcommand} ${out.loop_id}${out.current_phase ? ` phase=${out.current_phase}` : ''}${out.status ? ` status=${out.status}` : ''}`);
153
+ console.log(formatNextExpected(out.next_expected));
154
+ return out;
155
+ }
156
+ //# sourceMappingURL=loop.js.map