@locusai/cli 0.7.1 → 0.7.4
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/bin/agent/worker.js +481 -129
- package/bin/locus.js +32990 -29569
- package/package.json +2 -2
package/bin/agent/worker.js
CHANGED
|
@@ -14880,11 +14880,11 @@ var require_out = __commonJS((exports) => {
|
|
|
14880
14880
|
async.read(path, getSettings(optionsOrSettingsOrCallback), callback);
|
|
14881
14881
|
}
|
|
14882
14882
|
exports.stat = stat;
|
|
14883
|
-
function
|
|
14883
|
+
function statSync(path, optionsOrSettings) {
|
|
14884
14884
|
const settings = getSettings(optionsOrSettings);
|
|
14885
14885
|
return sync.read(path, settings);
|
|
14886
14886
|
}
|
|
14887
|
-
exports.statSync =
|
|
14887
|
+
exports.statSync = statSync;
|
|
14888
14888
|
function getSettings(settingsOrOptions = {}) {
|
|
14889
14889
|
if (settingsOrOptions instanceof settings_1.default) {
|
|
14890
14890
|
return settingsOrOptions;
|
|
@@ -17018,8 +17018,8 @@ var PROVIDER = {
|
|
|
17018
17018
|
CODEX: "codex"
|
|
17019
17019
|
};
|
|
17020
17020
|
var DEFAULT_MODEL = {
|
|
17021
|
-
[PROVIDER.CLAUDE]: "
|
|
17022
|
-
[PROVIDER.CODEX]: "gpt-5.
|
|
17021
|
+
[PROVIDER.CLAUDE]: "opus",
|
|
17022
|
+
[PROVIDER.CODEX]: "gpt-5.2-codex"
|
|
17023
17023
|
};
|
|
17024
17024
|
var LOCUS_CONFIG = {
|
|
17025
17025
|
dir: ".locus",
|
|
@@ -17028,7 +17028,8 @@ var LOCUS_CONFIG = {
|
|
|
17028
17028
|
contextFile: "CLAUDE.md",
|
|
17029
17029
|
artifactsDir: "artifacts",
|
|
17030
17030
|
documentsDir: "documents",
|
|
17031
|
-
agentSkillsDir: ".agent/skills"
|
|
17031
|
+
agentSkillsDir: ".agent/skills",
|
|
17032
|
+
sessionsDir: "sessions"
|
|
17032
17033
|
};
|
|
17033
17034
|
function getLocusPath(projectPath, fileName) {
|
|
17034
17035
|
if (fileName === "contextFile") {
|
|
@@ -17108,11 +17109,17 @@ class ClaudeRunner {
|
|
|
17108
17109
|
model;
|
|
17109
17110
|
log;
|
|
17110
17111
|
projectPath;
|
|
17112
|
+
eventEmitter;
|
|
17113
|
+
currentToolName;
|
|
17114
|
+
activeTools = new Map;
|
|
17111
17115
|
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log) {
|
|
17112
17116
|
this.model = model;
|
|
17113
17117
|
this.log = log;
|
|
17114
17118
|
this.projectPath = resolve(projectPath);
|
|
17115
17119
|
}
|
|
17120
|
+
setEventEmitter(emitter) {
|
|
17121
|
+
this.eventEmitter = emitter;
|
|
17122
|
+
}
|
|
17116
17123
|
async run(prompt, _isPlanning = false) {
|
|
17117
17124
|
const maxRetries = 3;
|
|
17118
17125
|
let lastError = null;
|
|
@@ -17132,6 +17139,246 @@ class ClaudeRunner {
|
|
|
17132
17139
|
}
|
|
17133
17140
|
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
17134
17141
|
}
|
|
17142
|
+
async* runStream(prompt) {
|
|
17143
|
+
const args = [
|
|
17144
|
+
"--dangerously-skip-permissions",
|
|
17145
|
+
"--print",
|
|
17146
|
+
"--verbose",
|
|
17147
|
+
"--output-format",
|
|
17148
|
+
"stream-json",
|
|
17149
|
+
"--include-partial-messages",
|
|
17150
|
+
"--model",
|
|
17151
|
+
this.model
|
|
17152
|
+
];
|
|
17153
|
+
const env = {
|
|
17154
|
+
...process.env,
|
|
17155
|
+
FORCE_COLOR: "1",
|
|
17156
|
+
TERM: "xterm-256color"
|
|
17157
|
+
};
|
|
17158
|
+
this.eventEmitter?.emitSessionStarted({
|
|
17159
|
+
model: this.model,
|
|
17160
|
+
provider: "claude"
|
|
17161
|
+
});
|
|
17162
|
+
this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
|
|
17163
|
+
const claude = spawn("claude", args, {
|
|
17164
|
+
cwd: this.projectPath,
|
|
17165
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17166
|
+
env
|
|
17167
|
+
});
|
|
17168
|
+
let buffer = "";
|
|
17169
|
+
let stderrBuffer = "";
|
|
17170
|
+
let resolveChunk = null;
|
|
17171
|
+
const chunkQueue = [];
|
|
17172
|
+
let processEnded = false;
|
|
17173
|
+
let errorMessage = "";
|
|
17174
|
+
let finalContent = "";
|
|
17175
|
+
let isThinking = false;
|
|
17176
|
+
const enqueueChunk = (chunk) => {
|
|
17177
|
+
this.emitEventForChunk(chunk, isThinking);
|
|
17178
|
+
if (chunk.type === "thinking") {
|
|
17179
|
+
isThinking = true;
|
|
17180
|
+
} else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
|
|
17181
|
+
if (isThinking) {
|
|
17182
|
+
this.eventEmitter?.emitThinkingStoped();
|
|
17183
|
+
isThinking = false;
|
|
17184
|
+
}
|
|
17185
|
+
}
|
|
17186
|
+
if (chunk.type === "text_delta") {
|
|
17187
|
+
finalContent += chunk.content;
|
|
17188
|
+
}
|
|
17189
|
+
if (resolveChunk) {
|
|
17190
|
+
const resolve2 = resolveChunk;
|
|
17191
|
+
resolveChunk = null;
|
|
17192
|
+
resolve2(chunk);
|
|
17193
|
+
} else {
|
|
17194
|
+
chunkQueue.push(chunk);
|
|
17195
|
+
}
|
|
17196
|
+
};
|
|
17197
|
+
const signalEnd = () => {
|
|
17198
|
+
processEnded = true;
|
|
17199
|
+
if (resolveChunk) {
|
|
17200
|
+
resolveChunk(null);
|
|
17201
|
+
resolveChunk = null;
|
|
17202
|
+
}
|
|
17203
|
+
};
|
|
17204
|
+
claude.stdout.on("data", (data) => {
|
|
17205
|
+
buffer += data.toString();
|
|
17206
|
+
const lines = buffer.split(`
|
|
17207
|
+
`);
|
|
17208
|
+
buffer = lines.pop() || "";
|
|
17209
|
+
for (const line of lines) {
|
|
17210
|
+
const chunk = this.parseStreamLineToChunk(line);
|
|
17211
|
+
if (chunk) {
|
|
17212
|
+
enqueueChunk(chunk);
|
|
17213
|
+
}
|
|
17214
|
+
}
|
|
17215
|
+
});
|
|
17216
|
+
claude.stderr.on("data", (data) => {
|
|
17217
|
+
const chunk = data.toString();
|
|
17218
|
+
stderrBuffer += chunk;
|
|
17219
|
+
const lines = stderrBuffer.split(`
|
|
17220
|
+
`);
|
|
17221
|
+
stderrBuffer = lines.pop() || "";
|
|
17222
|
+
for (const line of lines) {
|
|
17223
|
+
if (!this.shouldSuppressLine(line)) {
|
|
17224
|
+
process.stderr.write(`${line}
|
|
17225
|
+
`);
|
|
17226
|
+
}
|
|
17227
|
+
}
|
|
17228
|
+
});
|
|
17229
|
+
claude.on("error", (err) => {
|
|
17230
|
+
errorMessage = `Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`;
|
|
17231
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
|
|
17232
|
+
signalEnd();
|
|
17233
|
+
});
|
|
17234
|
+
claude.on("close", (code) => {
|
|
17235
|
+
if (stderrBuffer && !this.shouldSuppressLine(stderrBuffer)) {
|
|
17236
|
+
process.stderr.write(`${stderrBuffer}
|
|
17237
|
+
`);
|
|
17238
|
+
}
|
|
17239
|
+
if (code !== 0 && !errorMessage) {
|
|
17240
|
+
errorMessage = this.createExecutionError(code, stderrBuffer).message;
|
|
17241
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
|
|
17242
|
+
}
|
|
17243
|
+
signalEnd();
|
|
17244
|
+
});
|
|
17245
|
+
claude.stdin.write(prompt);
|
|
17246
|
+
claude.stdin.end();
|
|
17247
|
+
while (true) {
|
|
17248
|
+
if (chunkQueue.length > 0) {
|
|
17249
|
+
const chunk = chunkQueue.shift();
|
|
17250
|
+
if (chunk)
|
|
17251
|
+
yield chunk;
|
|
17252
|
+
} else if (processEnded) {
|
|
17253
|
+
if (errorMessage) {
|
|
17254
|
+
yield { type: "error", error: errorMessage };
|
|
17255
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
17256
|
+
} else {
|
|
17257
|
+
if (finalContent) {
|
|
17258
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
17259
|
+
}
|
|
17260
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
17261
|
+
}
|
|
17262
|
+
break;
|
|
17263
|
+
} else {
|
|
17264
|
+
const chunk = await new Promise((resolve2) => {
|
|
17265
|
+
resolveChunk = resolve2;
|
|
17266
|
+
});
|
|
17267
|
+
if (chunk === null) {
|
|
17268
|
+
if (errorMessage) {
|
|
17269
|
+
yield { type: "error", error: errorMessage };
|
|
17270
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
17271
|
+
} else {
|
|
17272
|
+
if (finalContent) {
|
|
17273
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
17274
|
+
}
|
|
17275
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
17276
|
+
}
|
|
17277
|
+
break;
|
|
17278
|
+
}
|
|
17279
|
+
yield chunk;
|
|
17280
|
+
}
|
|
17281
|
+
}
|
|
17282
|
+
}
|
|
17283
|
+
emitEventForChunk(chunk, isThinking) {
|
|
17284
|
+
if (!this.eventEmitter)
|
|
17285
|
+
return;
|
|
17286
|
+
switch (chunk.type) {
|
|
17287
|
+
case "text_delta":
|
|
17288
|
+
this.eventEmitter.emitTextDelta(chunk.content);
|
|
17289
|
+
break;
|
|
17290
|
+
case "tool_use":
|
|
17291
|
+
if (this.currentToolName) {
|
|
17292
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
17293
|
+
}
|
|
17294
|
+
this.currentToolName = chunk.tool;
|
|
17295
|
+
this.eventEmitter.emitToolStarted(chunk.tool, chunk.id);
|
|
17296
|
+
break;
|
|
17297
|
+
case "thinking":
|
|
17298
|
+
if (!isThinking) {
|
|
17299
|
+
this.eventEmitter.emitThinkingStarted(chunk.content);
|
|
17300
|
+
}
|
|
17301
|
+
break;
|
|
17302
|
+
case "result":
|
|
17303
|
+
if (this.currentToolName) {
|
|
17304
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
17305
|
+
this.currentToolName = undefined;
|
|
17306
|
+
}
|
|
17307
|
+
break;
|
|
17308
|
+
case "error":
|
|
17309
|
+
this.eventEmitter.emitErrorOccurred(chunk.error);
|
|
17310
|
+
break;
|
|
17311
|
+
}
|
|
17312
|
+
}
|
|
17313
|
+
parseStreamLineToChunk(line) {
|
|
17314
|
+
if (!line.trim())
|
|
17315
|
+
return null;
|
|
17316
|
+
try {
|
|
17317
|
+
const item = JSON.parse(line);
|
|
17318
|
+
return this.processStreamItemToChunk(item);
|
|
17319
|
+
} catch {
|
|
17320
|
+
return null;
|
|
17321
|
+
}
|
|
17322
|
+
}
|
|
17323
|
+
processStreamItemToChunk(item) {
|
|
17324
|
+
if (item.type === "result") {
|
|
17325
|
+
return { type: "result", content: item.result || "" };
|
|
17326
|
+
}
|
|
17327
|
+
if (item.type === "stream_event" && item.event) {
|
|
17328
|
+
return this.handleEventToChunk(item.event);
|
|
17329
|
+
}
|
|
17330
|
+
return null;
|
|
17331
|
+
}
|
|
17332
|
+
handleEventToChunk(event) {
|
|
17333
|
+
const { type, delta, content_block, index } = event;
|
|
17334
|
+
if (type === "content_block_delta" && delta?.type === "text_delta") {
|
|
17335
|
+
return { type: "text_delta", content: delta.text || "" };
|
|
17336
|
+
}
|
|
17337
|
+
if (type === "content_block_delta" && delta?.type === "input_json_delta" && delta.partial_json !== undefined && index !== undefined) {
|
|
17338
|
+
const activeTool = this.activeTools.get(index);
|
|
17339
|
+
if (activeTool) {
|
|
17340
|
+
activeTool.parameterJson += delta.partial_json;
|
|
17341
|
+
}
|
|
17342
|
+
return null;
|
|
17343
|
+
}
|
|
17344
|
+
if (type === "content_block_start" && content_block) {
|
|
17345
|
+
if (content_block.type === "tool_use" && content_block.name) {
|
|
17346
|
+
if (index !== undefined) {
|
|
17347
|
+
this.activeTools.set(index, {
|
|
17348
|
+
name: content_block.name,
|
|
17349
|
+
id: content_block.id,
|
|
17350
|
+
index,
|
|
17351
|
+
parameterJson: "",
|
|
17352
|
+
startTime: Date.now()
|
|
17353
|
+
});
|
|
17354
|
+
}
|
|
17355
|
+
return {
|
|
17356
|
+
type: "tool_use",
|
|
17357
|
+
tool: content_block.name,
|
|
17358
|
+
id: content_block.id
|
|
17359
|
+
};
|
|
17360
|
+
}
|
|
17361
|
+
if (content_block.type === "thinking") {
|
|
17362
|
+
return { type: "thinking" };
|
|
17363
|
+
}
|
|
17364
|
+
}
|
|
17365
|
+
if (type === "content_block_stop" && index !== undefined) {
|
|
17366
|
+
const activeTool = this.activeTools.get(index);
|
|
17367
|
+
if (activeTool?.parameterJson) {
|
|
17368
|
+
try {
|
|
17369
|
+
const parameters = JSON.parse(activeTool.parameterJson);
|
|
17370
|
+
return {
|
|
17371
|
+
type: "tool_parameters",
|
|
17372
|
+
tool: activeTool.name,
|
|
17373
|
+
id: activeTool.id,
|
|
17374
|
+
parameters
|
|
17375
|
+
};
|
|
17376
|
+
} catch {}
|
|
17377
|
+
}
|
|
17378
|
+
return null;
|
|
17379
|
+
}
|
|
17380
|
+
return null;
|
|
17381
|
+
}
|
|
17135
17382
|
executeRun(prompt) {
|
|
17136
17383
|
return new Promise((resolve2, reject) => {
|
|
17137
17384
|
const args = [
|
|
@@ -17227,7 +17474,6 @@ class ClaudeRunner {
|
|
|
17227
17474
|
if (type === "content_block_start" && content_block) {
|
|
17228
17475
|
if (content_block.type === "tool_use" && content_block.name) {
|
|
17229
17476
|
this.log?.(`
|
|
17230
|
-
|
|
17231
17477
|
${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
17232
17478
|
`, "info");
|
|
17233
17479
|
}
|
|
@@ -17276,6 +17522,99 @@ class CodexRunner {
|
|
|
17276
17522
|
}
|
|
17277
17523
|
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
17278
17524
|
}
|
|
17525
|
+
async* runStream(prompt) {
|
|
17526
|
+
const outputPath = join2(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
17527
|
+
const args = this.buildArgs(outputPath);
|
|
17528
|
+
const codex = spawn2("codex", args, {
|
|
17529
|
+
cwd: this.projectPath,
|
|
17530
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17531
|
+
env: process.env,
|
|
17532
|
+
shell: false
|
|
17533
|
+
});
|
|
17534
|
+
let resolveChunk = null;
|
|
17535
|
+
const chunkQueue = [];
|
|
17536
|
+
let processEnded = false;
|
|
17537
|
+
let errorMessage = "";
|
|
17538
|
+
let finalOutput = "";
|
|
17539
|
+
const enqueueChunk = (chunk) => {
|
|
17540
|
+
if (resolveChunk) {
|
|
17541
|
+
const resolve2 = resolveChunk;
|
|
17542
|
+
resolveChunk = null;
|
|
17543
|
+
resolve2(chunk);
|
|
17544
|
+
} else {
|
|
17545
|
+
chunkQueue.push(chunk);
|
|
17546
|
+
}
|
|
17547
|
+
};
|
|
17548
|
+
const signalEnd = () => {
|
|
17549
|
+
processEnded = true;
|
|
17550
|
+
if (resolveChunk) {
|
|
17551
|
+
resolveChunk(null);
|
|
17552
|
+
resolveChunk = null;
|
|
17553
|
+
}
|
|
17554
|
+
};
|
|
17555
|
+
const processOutput = (data) => {
|
|
17556
|
+
const msg = data.toString();
|
|
17557
|
+
finalOutput += msg;
|
|
17558
|
+
for (const rawLine of msg.split(`
|
|
17559
|
+
`)) {
|
|
17560
|
+
const line = rawLine.trim();
|
|
17561
|
+
if (!line)
|
|
17562
|
+
continue;
|
|
17563
|
+
if (/^thinking\b/i.test(line)) {
|
|
17564
|
+
enqueueChunk({ type: "thinking", content: line });
|
|
17565
|
+
} else if (/^[→•✓]/.test(line) || /^Plan update\b/.test(line)) {
|
|
17566
|
+
enqueueChunk({
|
|
17567
|
+
type: "tool_use",
|
|
17568
|
+
tool: line.replace(/^[→•✓]\s*/, "")
|
|
17569
|
+
});
|
|
17570
|
+
} else if (this.shouldDisplay(line)) {
|
|
17571
|
+
enqueueChunk({ type: "text_delta", content: `${line}
|
|
17572
|
+
` });
|
|
17573
|
+
}
|
|
17574
|
+
}
|
|
17575
|
+
};
|
|
17576
|
+
codex.stdout.on("data", processOutput);
|
|
17577
|
+
codex.stderr.on("data", processOutput);
|
|
17578
|
+
codex.on("error", (err) => {
|
|
17579
|
+
errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
|
|
17580
|
+
signalEnd();
|
|
17581
|
+
});
|
|
17582
|
+
codex.on("close", (code) => {
|
|
17583
|
+
this.cleanupTempFile(outputPath);
|
|
17584
|
+
if (code === 0) {
|
|
17585
|
+
const result = this.readOutput(outputPath, finalOutput);
|
|
17586
|
+
enqueueChunk({ type: "result", content: result });
|
|
17587
|
+
} else if (!errorMessage) {
|
|
17588
|
+
errorMessage = this.createErrorFromOutput(code, finalOutput).message;
|
|
17589
|
+
}
|
|
17590
|
+
signalEnd();
|
|
17591
|
+
});
|
|
17592
|
+
codex.stdin.write(prompt);
|
|
17593
|
+
codex.stdin.end();
|
|
17594
|
+
while (true) {
|
|
17595
|
+
if (chunkQueue.length > 0) {
|
|
17596
|
+
const chunk = chunkQueue.shift();
|
|
17597
|
+
if (chunk)
|
|
17598
|
+
yield chunk;
|
|
17599
|
+
} else if (processEnded) {
|
|
17600
|
+
if (errorMessage) {
|
|
17601
|
+
yield { type: "error", error: errorMessage };
|
|
17602
|
+
}
|
|
17603
|
+
break;
|
|
17604
|
+
} else {
|
|
17605
|
+
const chunk = await new Promise((resolve2) => {
|
|
17606
|
+
resolveChunk = resolve2;
|
|
17607
|
+
});
|
|
17608
|
+
if (chunk === null) {
|
|
17609
|
+
if (errorMessage) {
|
|
17610
|
+
yield { type: "error", error: errorMessage };
|
|
17611
|
+
}
|
|
17612
|
+
break;
|
|
17613
|
+
}
|
|
17614
|
+
yield chunk;
|
|
17615
|
+
}
|
|
17616
|
+
}
|
|
17617
|
+
}
|
|
17279
17618
|
executeRun(prompt) {
|
|
17280
17619
|
return new Promise((resolve2, reject) => {
|
|
17281
17620
|
const outputPath = join2(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
@@ -35505,111 +35844,10 @@ class LocusClient {
|
|
|
35505
35844
|
}
|
|
35506
35845
|
}
|
|
35507
35846
|
|
|
35508
|
-
// ../sdk/src/agent/artifact-syncer.ts
|
|
35509
|
-
import {
|
|
35510
|
-
existsSync as existsSync2,
|
|
35511
|
-
mkdirSync,
|
|
35512
|
-
readdirSync,
|
|
35513
|
-
readFileSync as readFileSync2,
|
|
35514
|
-
statSync,
|
|
35515
|
-
writeFileSync
|
|
35516
|
-
} from "node:fs";
|
|
35517
|
-
import { join as join3 } from "node:path";
|
|
35518
|
-
class ArtifactSyncer {
|
|
35519
|
-
deps;
|
|
35520
|
-
constructor(deps) {
|
|
35521
|
-
this.deps = deps;
|
|
35522
|
-
}
|
|
35523
|
-
async getOrCreateArtifactsGroup() {
|
|
35524
|
-
try {
|
|
35525
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
35526
|
-
const artifactsGroup = groups.find((g) => g.name === "Artifacts");
|
|
35527
|
-
if (artifactsGroup) {
|
|
35528
|
-
return artifactsGroup.id;
|
|
35529
|
-
}
|
|
35530
|
-
const newGroup = await this.deps.client.docs.createGroup(this.deps.workspaceId, {
|
|
35531
|
-
name: "Artifacts",
|
|
35532
|
-
order: 999
|
|
35533
|
-
});
|
|
35534
|
-
this.deps.log("Created 'Artifacts' group for agent-generated docs", "info");
|
|
35535
|
-
return newGroup.id;
|
|
35536
|
-
} catch (error48) {
|
|
35537
|
-
this.deps.log(`Failed to get/create Artifacts group: ${error48}`, "error");
|
|
35538
|
-
throw error48;
|
|
35539
|
-
}
|
|
35540
|
-
}
|
|
35541
|
-
async sync() {
|
|
35542
|
-
const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
|
|
35543
|
-
if (!existsSync2(artifactsDir)) {
|
|
35544
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
35545
|
-
return;
|
|
35546
|
-
}
|
|
35547
|
-
try {
|
|
35548
|
-
const files = readdirSync(artifactsDir);
|
|
35549
|
-
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
35550
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
35551
|
-
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
35552
|
-
let artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
35553
|
-
if (!artifactsGroupId) {
|
|
35554
|
-
artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
35555
|
-
}
|
|
35556
|
-
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
35557
|
-
for (const file2 of files) {
|
|
35558
|
-
const filePath = join3(artifactsDir, file2);
|
|
35559
|
-
if (statSync(filePath).isFile()) {
|
|
35560
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
35561
|
-
const title = file2.replace(/\.md$/, "").trim();
|
|
35562
|
-
if (!title)
|
|
35563
|
-
continue;
|
|
35564
|
-
const existing = existingDocs.find((d) => d.title === title);
|
|
35565
|
-
if (existing) {
|
|
35566
|
-
if (existing.content !== content || existing.groupId !== artifactsGroupId) {
|
|
35567
|
-
await this.deps.client.docs.update(existing.id, this.deps.workspaceId, { content, groupId: artifactsGroupId });
|
|
35568
|
-
this.deps.log(`Updated artifact: ${file2}`, "success");
|
|
35569
|
-
}
|
|
35570
|
-
} else {
|
|
35571
|
-
await this.deps.client.docs.create(this.deps.workspaceId, {
|
|
35572
|
-
title,
|
|
35573
|
-
content,
|
|
35574
|
-
groupId: artifactsGroupId,
|
|
35575
|
-
type: "GENERAL" /* GENERAL */
|
|
35576
|
-
});
|
|
35577
|
-
this.deps.log(`Created artifact: ${file2}`, "success");
|
|
35578
|
-
}
|
|
35579
|
-
}
|
|
35580
|
-
}
|
|
35581
|
-
for (const doc3 of existingDocs) {
|
|
35582
|
-
if (doc3.groupId === artifactsGroupId) {
|
|
35583
|
-
const fileName = `${doc3.title}.md`;
|
|
35584
|
-
const filePath = join3(artifactsDir, fileName);
|
|
35585
|
-
if (!existsSync2(filePath)) {
|
|
35586
|
-
writeFileSync(filePath, doc3.content || "");
|
|
35587
|
-
this.deps.log(`Fetched artifact: ${fileName}`, "success");
|
|
35588
|
-
}
|
|
35589
|
-
} else {
|
|
35590
|
-
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
35591
|
-
const groupName = groupMap.get(doc3.groupId || "") || "General";
|
|
35592
|
-
const groupDir = join3(documentsDir, groupName);
|
|
35593
|
-
if (!existsSync2(groupDir)) {
|
|
35594
|
-
mkdirSync(groupDir, { recursive: true });
|
|
35595
|
-
}
|
|
35596
|
-
const fileName = `${doc3.title}.md`;
|
|
35597
|
-
const filePath = join3(groupDir, fileName);
|
|
35598
|
-
if (!existsSync2(filePath) || readFileSync2(filePath, "utf-8") !== doc3.content) {
|
|
35599
|
-
writeFileSync(filePath, doc3.content || "");
|
|
35600
|
-
}
|
|
35601
|
-
}
|
|
35602
|
-
}
|
|
35603
|
-
} catch (error48) {
|
|
35604
|
-
this.deps.log(`Failed to sync artifacts: ${error48}`, "error");
|
|
35605
|
-
}
|
|
35606
|
-
}
|
|
35607
|
-
}
|
|
35608
|
-
|
|
35609
35847
|
// ../sdk/src/core/indexer.ts
|
|
35610
35848
|
import { createHash } from "node:crypto";
|
|
35611
|
-
import { existsSync as
|
|
35612
|
-
import { dirname, join as
|
|
35849
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
35850
|
+
import { dirname, join as join3 } from "node:path";
|
|
35613
35851
|
|
|
35614
35852
|
// ../../node_modules/globby/index.js
|
|
35615
35853
|
import process4 from "node:process";
|
|
@@ -36122,7 +36360,7 @@ class CodebaseIndexer {
|
|
|
36122
36360
|
fullReindexRatioThreshold = 0.2;
|
|
36123
36361
|
constructor(projectPath) {
|
|
36124
36362
|
this.projectPath = projectPath;
|
|
36125
|
-
this.indexPath =
|
|
36363
|
+
this.indexPath = join3(projectPath, ".locus", "codebase-index.json");
|
|
36126
36364
|
}
|
|
36127
36365
|
async index(onProgress, treeSummarizer, force = false) {
|
|
36128
36366
|
if (!treeSummarizer) {
|
|
@@ -36178,11 +36416,11 @@ class CodebaseIndexer {
|
|
|
36178
36416
|
}
|
|
36179
36417
|
}
|
|
36180
36418
|
async getFileTree() {
|
|
36181
|
-
const gitmodulesPath =
|
|
36419
|
+
const gitmodulesPath = join3(this.projectPath, ".gitmodules");
|
|
36182
36420
|
const submoduleIgnores = [];
|
|
36183
|
-
if (
|
|
36421
|
+
if (existsSync2(gitmodulesPath)) {
|
|
36184
36422
|
try {
|
|
36185
|
-
const content =
|
|
36423
|
+
const content = readFileSync2(gitmodulesPath, "utf-8");
|
|
36186
36424
|
const lines = content.split(`
|
|
36187
36425
|
`);
|
|
36188
36426
|
for (const line of lines) {
|
|
@@ -36238,9 +36476,9 @@ class CodebaseIndexer {
|
|
|
36238
36476
|
});
|
|
36239
36477
|
}
|
|
36240
36478
|
loadIndex() {
|
|
36241
|
-
if (
|
|
36479
|
+
if (existsSync2(this.indexPath)) {
|
|
36242
36480
|
try {
|
|
36243
|
-
return JSON.parse(
|
|
36481
|
+
return JSON.parse(readFileSync2(this.indexPath, "utf-8"));
|
|
36244
36482
|
} catch {
|
|
36245
36483
|
return null;
|
|
36246
36484
|
}
|
|
@@ -36249,10 +36487,10 @@ class CodebaseIndexer {
|
|
|
36249
36487
|
}
|
|
36250
36488
|
saveIndex(index) {
|
|
36251
36489
|
const dir = dirname(this.indexPath);
|
|
36252
|
-
if (!
|
|
36253
|
-
|
|
36490
|
+
if (!existsSync2(dir)) {
|
|
36491
|
+
mkdirSync(dir, { recursive: true });
|
|
36254
36492
|
}
|
|
36255
|
-
|
|
36493
|
+
writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
36256
36494
|
}
|
|
36257
36495
|
cloneIndex(index) {
|
|
36258
36496
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -36268,7 +36506,7 @@ class CodebaseIndexer {
|
|
|
36268
36506
|
}
|
|
36269
36507
|
hashFile(filePath) {
|
|
36270
36508
|
try {
|
|
36271
|
-
const content =
|
|
36509
|
+
const content = readFileSync2(join3(this.projectPath, filePath), "utf-8");
|
|
36272
36510
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
36273
36511
|
} catch {
|
|
36274
36512
|
return null;
|
|
@@ -36372,8 +36610,52 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
36372
36610
|
}
|
|
36373
36611
|
}
|
|
36374
36612
|
|
|
36613
|
+
// ../sdk/src/agent/document-fetcher.ts
|
|
36614
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
36615
|
+
import { join as join4 } from "node:path";
|
|
36616
|
+
class DocumentFetcher {
|
|
36617
|
+
deps;
|
|
36618
|
+
constructor(deps) {
|
|
36619
|
+
this.deps = deps;
|
|
36620
|
+
}
|
|
36621
|
+
async fetch() {
|
|
36622
|
+
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
36623
|
+
if (!existsSync3(documentsDir)) {
|
|
36624
|
+
mkdirSync2(documentsDir, { recursive: true });
|
|
36625
|
+
}
|
|
36626
|
+
try {
|
|
36627
|
+
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
36628
|
+
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
36629
|
+
const docs2 = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
36630
|
+
const artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
36631
|
+
let fetchedCount = 0;
|
|
36632
|
+
for (const doc3 of docs2) {
|
|
36633
|
+
if (doc3.groupId === artifactsGroupId) {
|
|
36634
|
+
continue;
|
|
36635
|
+
}
|
|
36636
|
+
const groupName = groupMap.get(doc3.groupId || "") || "General";
|
|
36637
|
+
const groupDir = join4(documentsDir, groupName);
|
|
36638
|
+
if (!existsSync3(groupDir)) {
|
|
36639
|
+
mkdirSync2(groupDir, { recursive: true });
|
|
36640
|
+
}
|
|
36641
|
+
const fileName = `${doc3.title}.md`;
|
|
36642
|
+
const filePath = join4(groupDir, fileName);
|
|
36643
|
+
if (!existsSync3(filePath) || readFileSync3(filePath, "utf-8") !== doc3.content) {
|
|
36644
|
+
writeFileSync2(filePath, doc3.content || "");
|
|
36645
|
+
fetchedCount++;
|
|
36646
|
+
}
|
|
36647
|
+
}
|
|
36648
|
+
if (fetchedCount > 0) {
|
|
36649
|
+
this.deps.log(`Fetched ${fetchedCount} document(s) from server`, "info");
|
|
36650
|
+
}
|
|
36651
|
+
} catch (error48) {
|
|
36652
|
+
this.deps.log(`Failed to fetch documents: ${error48}`, "error");
|
|
36653
|
+
}
|
|
36654
|
+
}
|
|
36655
|
+
}
|
|
36656
|
+
|
|
36375
36657
|
// ../sdk/src/core/prompt-builder.ts
|
|
36376
|
-
import { existsSync as existsSync4, readdirSync
|
|
36658
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
36377
36659
|
import { homedir } from "node:os";
|
|
36378
36660
|
import { join as join5 } from "node:path";
|
|
36379
36661
|
class PromptBuilder {
|
|
@@ -36464,7 +36746,7 @@ ${serverContext.context}
|
|
|
36464
36746
|
`;
|
|
36465
36747
|
prompt += `You have access to the following documentation directories for context:
|
|
36466
36748
|
`;
|
|
36467
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
36749
|
+
prompt += `- Artifacts: \`.locus/artifacts\`)
|
|
36468
36750
|
`;
|
|
36469
36751
|
prompt += `- Documents: \`.locus/documents\`
|
|
36470
36752
|
`;
|
|
@@ -36526,6 +36808,76 @@ ${comment.text}
|
|
|
36526
36808
|
2. **Artifact Management**: If you create any high-level documentation (PRDs, technical drafts, architecture docs), you MUST save them in \`.locus/artifacts/\`. Do NOT create them in the root directory.
|
|
36527
36809
|
3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
36528
36810
|
4. When finished successfully, output: <promise>COMPLETE</promise>
|
|
36811
|
+
`;
|
|
36812
|
+
return prompt;
|
|
36813
|
+
}
|
|
36814
|
+
async buildGenericPrompt(query) {
|
|
36815
|
+
let prompt = `# Direct Execution
|
|
36816
|
+
|
|
36817
|
+
`;
|
|
36818
|
+
prompt += `## Prompt
|
|
36819
|
+
${query}
|
|
36820
|
+
|
|
36821
|
+
`;
|
|
36822
|
+
const projectConfig = this.getProjectConfig();
|
|
36823
|
+
if (projectConfig) {
|
|
36824
|
+
prompt += `## Project Metadata
|
|
36825
|
+
`;
|
|
36826
|
+
prompt += `- Version: ${projectConfig.version || "Unknown"}
|
|
36827
|
+
`;
|
|
36828
|
+
prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
|
|
36829
|
+
|
|
36830
|
+
`;
|
|
36831
|
+
}
|
|
36832
|
+
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
36833
|
+
let hasLocalContext = false;
|
|
36834
|
+
if (existsSync4(contextPath)) {
|
|
36835
|
+
try {
|
|
36836
|
+
const context = readFileSync4(contextPath, "utf-8");
|
|
36837
|
+
if (context.trim().length > 20) {
|
|
36838
|
+
prompt += `## Project Context (Local)
|
|
36839
|
+
${context}
|
|
36840
|
+
|
|
36841
|
+
`;
|
|
36842
|
+
hasLocalContext = true;
|
|
36843
|
+
}
|
|
36844
|
+
} catch (err) {
|
|
36845
|
+
console.warn(`Warning: Could not read context file: ${err}`);
|
|
36846
|
+
}
|
|
36847
|
+
}
|
|
36848
|
+
if (!hasLocalContext) {
|
|
36849
|
+
const fallback = this.getFallbackContext();
|
|
36850
|
+
if (fallback) {
|
|
36851
|
+
prompt += `## Project Context (README Fallback)
|
|
36852
|
+
${fallback}
|
|
36853
|
+
|
|
36854
|
+
`;
|
|
36855
|
+
}
|
|
36856
|
+
}
|
|
36857
|
+
prompt += this.getProjectStructure();
|
|
36858
|
+
prompt += this.getSkillsInfo();
|
|
36859
|
+
prompt += `## Project Knowledge Base
|
|
36860
|
+
`;
|
|
36861
|
+
prompt += `You have access to the following documentation directories for context:
|
|
36862
|
+
`;
|
|
36863
|
+
prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
36864
|
+
`;
|
|
36865
|
+
prompt += `- Documents: \`.locus/documents\` (synced from cloud)
|
|
36866
|
+
`;
|
|
36867
|
+
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
36868
|
+
|
|
36869
|
+
`;
|
|
36870
|
+
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
36871
|
+
if (existsSync4(indexPath)) {
|
|
36872
|
+
prompt += `## Codebase Overview
|
|
36873
|
+
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
36874
|
+
|
|
36875
|
+
`;
|
|
36876
|
+
}
|
|
36877
|
+
prompt += `## Instructions
|
|
36878
|
+
1. Execute the prompt based on the provided project context.
|
|
36879
|
+
2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
36880
|
+
3. When finished successfully, output: <promise>COMPLETE</promise>
|
|
36529
36881
|
`;
|
|
36530
36882
|
return prompt;
|
|
36531
36883
|
}
|
|
@@ -36556,12 +36908,12 @@ ${comment.text}
|
|
|
36556
36908
|
}
|
|
36557
36909
|
getProjectStructure() {
|
|
36558
36910
|
try {
|
|
36559
|
-
const entries =
|
|
36911
|
+
const entries = readdirSync(this.projectPath);
|
|
36560
36912
|
const folders = entries.filter((e) => {
|
|
36561
36913
|
if (e.startsWith(".") || e === "node_modules")
|
|
36562
36914
|
return false;
|
|
36563
36915
|
try {
|
|
36564
|
-
return
|
|
36916
|
+
return statSync(join5(this.projectPath, e)).isDirectory();
|
|
36565
36917
|
} catch {
|
|
36566
36918
|
return false;
|
|
36567
36919
|
}
|
|
@@ -36619,9 +36971,9 @@ ${comment.text}
|
|
|
36619
36971
|
if (!existsSync4(dirPath))
|
|
36620
36972
|
return;
|
|
36621
36973
|
try {
|
|
36622
|
-
const entries =
|
|
36974
|
+
const entries = readdirSync(dirPath).filter((name) => {
|
|
36623
36975
|
try {
|
|
36624
|
-
return
|
|
36976
|
+
return statSync(join5(dirPath, name)).isDirectory();
|
|
36625
36977
|
} catch {
|
|
36626
36978
|
return false;
|
|
36627
36979
|
}
|
|
@@ -36720,7 +37072,7 @@ class AgentWorker {
|
|
|
36720
37072
|
client;
|
|
36721
37073
|
aiRunner;
|
|
36722
37074
|
indexerService;
|
|
36723
|
-
|
|
37075
|
+
documentFetcher;
|
|
36724
37076
|
taskExecutor;
|
|
36725
37077
|
consecutiveEmpty = 0;
|
|
36726
37078
|
maxEmpty = 60;
|
|
@@ -36752,7 +37104,7 @@ class AgentWorker {
|
|
|
36752
37104
|
projectPath,
|
|
36753
37105
|
log
|
|
36754
37106
|
});
|
|
36755
|
-
this.
|
|
37107
|
+
this.documentFetcher = new DocumentFetcher({
|
|
36756
37108
|
client: this.client,
|
|
36757
37109
|
workspaceId: config2.workspaceId,
|
|
36758
37110
|
projectPath,
|
|
@@ -36838,9 +37190,9 @@ class AgentWorker {
|
|
|
36838
37190
|
this.log(`Claimed: ${task2.title}`, "success");
|
|
36839
37191
|
const result = await this.executeTask(task2);
|
|
36840
37192
|
try {
|
|
36841
|
-
await this.
|
|
37193
|
+
await this.documentFetcher.fetch();
|
|
36842
37194
|
} catch (err) {
|
|
36843
|
-
this.log(`
|
|
37195
|
+
this.log(`Document fetch failed: ${err}`, "error");
|
|
36844
37196
|
}
|
|
36845
37197
|
if (result.success) {
|
|
36846
37198
|
this.log(`Completed: ${task2.title}`, "success");
|