brainclaw 1.5.4 → 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.
- package/README.md +52 -28
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +159 -12
- package/dist/commands/assignment-resource.js +182 -0
- package/dist/commands/bootstrap-loop.js +206 -0
- package/dist/commands/init.js +158 -22
- package/dist/commands/loop.js +156 -0
- package/dist/commands/loops-handlers.js +110 -55
- package/dist/commands/mcp-read-handlers.js +45 -4
- package/dist/commands/mcp.js +628 -205
- package/dist/commands/questions.js +180 -0
- package/dist/commands/reply.js +190 -0
- package/dist/commands/session-end.js +105 -3
- package/dist/commands/session-start.js +32 -53
- package/dist/commands/setup.js +87 -48
- package/dist/commands/switch.js +21 -1
- package/dist/core/agentrun-reconciler.js +65 -0
- package/dist/core/agentruns.js +10 -0
- package/dist/core/assignments.js +29 -10
- package/dist/core/claims.js +29 -0
- package/dist/core/context.js +1 -1
- package/dist/core/coordination.js +1 -1
- package/dist/core/dispatch-status.js +219 -0
- package/dist/core/entity-operations.js +166 -10
- package/dist/core/entity-registry.js +11 -10
- package/dist/core/execution-adapters.js +38 -2
- package/dist/core/facade-schema.js +55 -0
- package/dist/core/federation-cloud.js +27 -12
- package/dist/core/federation-materialize.js +57 -0
- package/dist/core/instruction-templates.js +2 -0
- package/dist/core/loops/bootstrap-acquire.js +195 -0
- package/dist/core/loops/facade-schema.js +68 -1
- package/dist/core/loops/hooks/bootstrap-write.js +144 -0
- package/dist/core/loops/hooks/notify-operator.js +148 -0
- package/dist/core/loops/hooks/survey-source-reader.js +256 -0
- package/dist/core/loops/index.js +8 -2
- package/dist/core/loops/next-expected.js +63 -0
- package/dist/core/loops/presets/bootstrap.js +75 -0
- package/dist/core/loops/presets/index.js +16 -0
- package/dist/core/loops/store.js +224 -4
- package/dist/core/loops/types.js +346 -1
- package/dist/core/loops/verbs.js +739 -6
- package/dist/core/schema.js +31 -2
- package/dist/core/state.js +62 -0
- package/dist/core/store-resolution.js +26 -16
- package/dist/facts.js +7 -5
- package/dist/facts.json +6 -4
- package/docs/cli.md +115 -30
- package/docs/concepts/dispatch-lifecycle.md +228 -0
- package/docs/concepts/loop-engine.md +55 -0
- package/docs/concepts/multi-agent-workflows.md +167 -166
- package/docs/concepts/troubleshooting.md +10 -2
- package/docs/integrations/agents.md +14 -14
- package/docs/integrations/codex.md +15 -12
- package/docs/integrations/mcp.md +10 -4
- package/docs/integrations/overview.md +11 -0
- package/docs/playbooks/productivity/index.md +3 -3
- package/docs/quickstart-existing-project.md +48 -28
- package/docs/quickstart.md +42 -28
- package/package.json +1 -1
|
@@ -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
|
package/dist/commands/init.js
CHANGED
|
@@ -4,7 +4,7 @@ import readline from 'node:readline/promises';
|
|
|
4
4
|
import { registerAgentIdentity, resolveDefaultAgentName, resolveExistingCurrentAgent } from '../core/agent-registry.js';
|
|
5
5
|
import { MEMORY_DIR, memoryExists, ensureMemoryDir, memoryPath, writeFileAtomic } from '../core/io.js';
|
|
6
6
|
import { emptyState, loadState, saveState } from '../core/state.js';
|
|
7
|
-
import { defaultConfig, saveConfig } from '../core/config.js';
|
|
7
|
+
import { defaultConfig, loadConfig, saveConfig } from '../core/config.js';
|
|
8
8
|
import { generateMarkdown } from '../core/markdown.js';
|
|
9
9
|
import { initMemoryRepo } from '../core/memory-git.js';
|
|
10
10
|
import { buildProjectIdentity, resolveExistingProjectIdentity, saveProjectIdentity } from '../core/project-registry.js';
|
|
@@ -18,6 +18,7 @@ import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-s
|
|
|
18
18
|
import { ensureUserStore, hasCompletedSetup } from '../core/setup-state.js';
|
|
19
19
|
import { writeDetectedAgentExport } from './export.js';
|
|
20
20
|
import { writeDetectedAgentHooks } from './hooks.js';
|
|
21
|
+
import { ConfigSchema } from '../core/schema.js';
|
|
21
22
|
export async function runInit(options = {}) {
|
|
22
23
|
const cwd = options.cwd ?? process.cwd();
|
|
23
24
|
const containingMemoryStore = resolveContainingMemoryStore(cwd);
|
|
@@ -58,22 +59,20 @@ export async function runInit(options = {}) {
|
|
|
58
59
|
const existingIdentity = resolveExistingProjectIdentity(cwd);
|
|
59
60
|
const existingCurrentAgent = resolveExistingCurrentAgent(cwd);
|
|
60
61
|
const storageDir = resolveStorageDir(options.storageDir);
|
|
61
|
-
const
|
|
62
|
-
const
|
|
62
|
+
const projectMemoryExists = memoryExists(cwd);
|
|
63
|
+
const existingConfig = projectMemoryExists ? loadExistingConfig(cwd, storageDir) : undefined;
|
|
64
|
+
const topology = resolveTopology(options.topology, existingConfig?.topology);
|
|
65
|
+
const ignoreStrategy = resolveIgnoreStrategy(topology, existingConfig?.ignore_strategy);
|
|
63
66
|
const skipAgentBootstrap = options.skipAgentBootstrap === true || process.env.BRAINCLAW_SKIP_AGENT_BOOTSTRAP === '1';
|
|
64
67
|
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
65
68
|
const skipAiSurfaceScan = testMode || options.noAiScan === true || options.aiScan === false;
|
|
66
|
-
if (memoryExists(cwd) && !options.force) {
|
|
67
|
-
console.error('Error: project memory already exists. Use --force to overwrite.');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
69
|
// Derive project name from directory
|
|
71
70
|
const projectName = path.basename(cwd);
|
|
72
71
|
const shouldAnalyzeRepo = options.analyzeRepo !== false
|
|
73
72
|
&& process.env.BRAINCLAW_SKIP_REPO_ANALYSIS !== '1';
|
|
74
73
|
const analysis = shouldAnalyzeRepo ? analyzeRepository(cwd) : undefined;
|
|
75
|
-
const projectMode = await resolveProjectMode(options, analysis);
|
|
76
|
-
const projectStrategy = await resolveProjectStrategy(options, projectMode);
|
|
74
|
+
const projectMode = await resolveProjectMode(options, analysis, existingConfig?.project_mode);
|
|
75
|
+
const projectStrategy = await resolveProjectStrategy(options, projectMode, existingConfig?.projects?.strategy);
|
|
77
76
|
ensureMemoryDir(cwd, storageDir);
|
|
78
77
|
const currentAgent = registerAgentIdentity({
|
|
79
78
|
agentName: existingCurrentAgent?.agent_name ?? resolveDefaultAgentName(),
|
|
@@ -112,19 +111,21 @@ export async function runInit(options = {}) {
|
|
|
112
111
|
storageDir,
|
|
113
112
|
topology,
|
|
114
113
|
});
|
|
115
|
-
const config =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
const config = buildInitConfig({
|
|
115
|
+
projectName,
|
|
116
|
+
projectIdentity,
|
|
117
|
+
currentAgent: {
|
|
118
|
+
name: currentAgent.agent_name,
|
|
119
|
+
id: currentAgent.agent_id,
|
|
120
|
+
},
|
|
119
121
|
projectMode,
|
|
120
122
|
projectStrategy,
|
|
121
123
|
storageDir,
|
|
122
124
|
topology,
|
|
123
125
|
ignoreStrategy,
|
|
126
|
+
existingConfig: options.force ? undefined : existingConfig,
|
|
127
|
+
compact: options.compact === true,
|
|
124
128
|
});
|
|
125
|
-
if (options.compact) {
|
|
126
|
-
config.markdown = { max_items_per_section: 20, compact_mode: true };
|
|
127
|
-
}
|
|
128
129
|
if (detectedAi && isAgentIntegrationName(detectedAi.name)) {
|
|
129
130
|
upsertAgentIntegrationDeclaration(config, detectedAi.name, 'detected');
|
|
130
131
|
}
|
|
@@ -173,8 +174,19 @@ export async function runInit(options = {}) {
|
|
|
173
174
|
.filter((item) => !item.startsWith('.codeium/'));
|
|
174
175
|
ensureGitignoreEntries(cwd, ['AGENTS.md', '.github/copilot-instructions.md', ...generatedWorkspacePaths, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES]);
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
if (projectMemoryExists) {
|
|
178
|
+
console.log(`✔ Refreshed existing project memory in ${storageDir}/`);
|
|
179
|
+
if (options.force) {
|
|
180
|
+
console.log('✔ Existing memory preserved; rebuilt managed configuration and agent integration files from defaults');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.log('✔ Existing memory preserved; refreshed managed configuration and agent integration files');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(`✔ Initialized project memory in ${storageDir}/`);
|
|
188
|
+
console.log('✔ Created project.md, config.yaml, and split state directories');
|
|
189
|
+
}
|
|
178
190
|
console.log(`✔ Project ID: ${projectIdentity.project_id}`);
|
|
179
191
|
console.log(`✔ Current agent: ${currentAgent.agent_name} (${currentAgent.agent_id})`);
|
|
180
192
|
if (registeredAiAgent) {
|
|
@@ -275,6 +287,12 @@ export async function runInit(options = {}) {
|
|
|
275
287
|
}
|
|
276
288
|
}
|
|
277
289
|
console.log('');
|
|
290
|
+
if (projectMemoryExists) {
|
|
291
|
+
console.log(`Tip: run 'brainclaw enable-agent <agent-name>' when you want to explicitly add another agent to this existing project.`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.log(`Tip: run 'brainclaw init' again later to refresh the detected agent's integration files on this project.`);
|
|
295
|
+
}
|
|
278
296
|
console.log(`Tip: run 'brainclaw context --json' to load the shared memory into your agent session.`);
|
|
279
297
|
}
|
|
280
298
|
function installPostMergeHookIfMissing(cwd) {
|
|
@@ -342,8 +360,19 @@ function looksLikeBrainclawStore(storePath) {
|
|
|
342
360
|
|| fs.existsSync(path.join(storePath, 'project.identity.json'))
|
|
343
361
|
|| fs.existsSync(path.join(storePath, '.git'));
|
|
344
362
|
}
|
|
345
|
-
function resolveTopology(topology) {
|
|
346
|
-
return topology ?? 'embedded';
|
|
363
|
+
function resolveTopology(topology, existingTopology) {
|
|
364
|
+
return topology ?? existingTopology ?? 'embedded';
|
|
365
|
+
}
|
|
366
|
+
function resolveIgnoreStrategy(topology, existingIgnoreStrategy) {
|
|
367
|
+
return existingIgnoreStrategy ?? (topology === 'embedded' ? 'none' : 'project-gitignore');
|
|
368
|
+
}
|
|
369
|
+
function loadExistingConfig(cwd, storageDir) {
|
|
370
|
+
try {
|
|
371
|
+
return loadConfig(cwd, storageDir);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
347
376
|
}
|
|
348
377
|
function ensureProjectGitignore(cwd, storageDir) {
|
|
349
378
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
@@ -358,10 +387,13 @@ function ensureProjectGitignore(cwd, storageDir) {
|
|
|
358
387
|
: `${current.replace(/\s*$/, '')}\n${ignoreLine}\n`;
|
|
359
388
|
fs.writeFileSync(gitignorePath, next, 'utf-8');
|
|
360
389
|
}
|
|
361
|
-
async function resolveProjectMode(options, analysis) {
|
|
390
|
+
async function resolveProjectMode(options, analysis, existingProjectMode) {
|
|
362
391
|
if (options.projectMode) {
|
|
363
392
|
return options.projectMode;
|
|
364
393
|
}
|
|
394
|
+
if (existingProjectMode) {
|
|
395
|
+
return existingProjectMode;
|
|
396
|
+
}
|
|
365
397
|
if (options.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
366
398
|
return 'auto';
|
|
367
399
|
}
|
|
@@ -384,13 +416,16 @@ async function resolveProjectMode(options, analysis) {
|
|
|
384
416
|
rl.close();
|
|
385
417
|
}
|
|
386
418
|
}
|
|
387
|
-
async function resolveProjectStrategy(options, projectMode) {
|
|
419
|
+
async function resolveProjectStrategy(options, projectMode, existingProjectStrategy) {
|
|
388
420
|
if (options.projectStrategy) {
|
|
389
421
|
return options.projectStrategy;
|
|
390
422
|
}
|
|
391
423
|
if (projectMode !== 'multi-project') {
|
|
392
424
|
return 'manual';
|
|
393
425
|
}
|
|
426
|
+
if (existingProjectStrategy) {
|
|
427
|
+
return existingProjectStrategy;
|
|
428
|
+
}
|
|
394
429
|
if (options.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
395
430
|
return 'manual';
|
|
396
431
|
}
|
|
@@ -438,4 +473,105 @@ function parseProjectStrategy(value) {
|
|
|
438
473
|
return undefined;
|
|
439
474
|
}
|
|
440
475
|
}
|
|
476
|
+
function buildInitConfig(input) {
|
|
477
|
+
const fallbackConfig = defaultConfig(input.projectName, {
|
|
478
|
+
projectId: input.projectIdentity.project_id,
|
|
479
|
+
currentAgent: input.currentAgent.name,
|
|
480
|
+
currentAgentId: input.currentAgent.id,
|
|
481
|
+
projectMode: input.projectMode,
|
|
482
|
+
projectStrategy: input.projectStrategy,
|
|
483
|
+
storageDir: input.storageDir,
|
|
484
|
+
topology: input.topology,
|
|
485
|
+
ignoreStrategy: input.ignoreStrategy,
|
|
486
|
+
});
|
|
487
|
+
const config = input.existingConfig
|
|
488
|
+
? mergeConfigWithDefaults(input.existingConfig, fallbackConfig)
|
|
489
|
+
: fallbackConfig;
|
|
490
|
+
const projects = config.projects ?? fallbackConfig.projects;
|
|
491
|
+
config.project_name = input.projectName;
|
|
492
|
+
config.project_id = input.projectIdentity.project_id;
|
|
493
|
+
config.current_agent = input.currentAgent.name;
|
|
494
|
+
config.current_agent_id = input.currentAgent.id;
|
|
495
|
+
config.storage_dir = input.storageDir;
|
|
496
|
+
config.topology = input.topology;
|
|
497
|
+
config.ignore_strategy = input.ignoreStrategy;
|
|
498
|
+
config.project_mode = input.projectMode;
|
|
499
|
+
config.projects = {
|
|
500
|
+
...projects,
|
|
501
|
+
strategy: input.projectStrategy,
|
|
502
|
+
known: projects.known ?? fallbackConfig.projects.known,
|
|
503
|
+
};
|
|
504
|
+
if (input.compact) {
|
|
505
|
+
const markdown = config.markdown ?? fallbackConfig.markdown ?? {
|
|
506
|
+
max_items_per_section: 20,
|
|
507
|
+
compact_mode: false,
|
|
508
|
+
};
|
|
509
|
+
config.markdown = {
|
|
510
|
+
...markdown,
|
|
511
|
+
compact_mode: true,
|
|
512
|
+
max_items_per_section: Math.min(markdown.max_items_per_section, 20),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return config;
|
|
516
|
+
}
|
|
517
|
+
function mergeConfigWithDefaults(existingConfig, fallbackConfig) {
|
|
518
|
+
return ConfigSchema.parse({
|
|
519
|
+
...fallbackConfig,
|
|
520
|
+
...existingConfig,
|
|
521
|
+
projects: {
|
|
522
|
+
...fallbackConfig.projects,
|
|
523
|
+
...(existingConfig.projects ?? {}),
|
|
524
|
+
known: existingConfig.projects?.known ?? fallbackConfig.projects.known,
|
|
525
|
+
},
|
|
526
|
+
redaction: {
|
|
527
|
+
...fallbackConfig.redaction,
|
|
528
|
+
...(existingConfig.redaction ?? {}),
|
|
529
|
+
patterns: existingConfig.redaction?.patterns ?? fallbackConfig.redaction.patterns,
|
|
530
|
+
},
|
|
531
|
+
security: existingConfig.security
|
|
532
|
+
? {
|
|
533
|
+
...fallbackConfig.security,
|
|
534
|
+
...existingConfig.security,
|
|
535
|
+
}
|
|
536
|
+
: fallbackConfig.security,
|
|
537
|
+
markdown: existingConfig.markdown
|
|
538
|
+
? {
|
|
539
|
+
...fallbackConfig.markdown,
|
|
540
|
+
...existingConfig.markdown,
|
|
541
|
+
}
|
|
542
|
+
: fallbackConfig.markdown,
|
|
543
|
+
reflective_memory: existingConfig.reflective_memory
|
|
544
|
+
? {
|
|
545
|
+
...fallbackConfig.reflective_memory,
|
|
546
|
+
...existingConfig.reflective_memory,
|
|
547
|
+
}
|
|
548
|
+
: fallbackConfig.reflective_memory,
|
|
549
|
+
governance: fallbackConfig.governance
|
|
550
|
+
? {
|
|
551
|
+
...fallbackConfig.governance,
|
|
552
|
+
...existingConfig.governance,
|
|
553
|
+
curators: existingConfig.governance?.curators ?? fallbackConfig.governance.curators,
|
|
554
|
+
}
|
|
555
|
+
: existingConfig.governance,
|
|
556
|
+
reputation: existingConfig.reputation
|
|
557
|
+
? {
|
|
558
|
+
...fallbackConfig.reputation,
|
|
559
|
+
...existingConfig.reputation,
|
|
560
|
+
}
|
|
561
|
+
: fallbackConfig.reputation,
|
|
562
|
+
agent_integrations: {
|
|
563
|
+
...fallbackConfig.agent_integrations,
|
|
564
|
+
...(existingConfig.agent_integrations ?? {}),
|
|
565
|
+
declarations: existingConfig.agent_integrations?.declarations ?? fallbackConfig.agent_integrations.declarations,
|
|
566
|
+
},
|
|
567
|
+
claims: existingConfig.claims
|
|
568
|
+
? {
|
|
569
|
+
...fallbackConfig.claims,
|
|
570
|
+
...existingConfig.claims,
|
|
571
|
+
}
|
|
572
|
+
: fallbackConfig.claims,
|
|
573
|
+
sensitive_paths: existingConfig.sensitive_paths ?? fallbackConfig.sensitive_paths,
|
|
574
|
+
cross_project_links: existingConfig.cross_project_links ?? fallbackConfig.cross_project_links,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
441
577
|
//# sourceMappingURL=init.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
|