@locusai/cli 0.7.3 → 0.7.6
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 +413 -130
- package/bin/locus.js +32401 -29085
- 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 = [
|
|
@@ -17275,6 +17522,99 @@ class CodexRunner {
|
|
|
17275
17522
|
}
|
|
17276
17523
|
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
17277
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
|
+
}
|
|
17278
17618
|
executeRun(prompt) {
|
|
17279
17619
|
return new Promise((resolve2, reject) => {
|
|
17280
17620
|
const outputPath = join2(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
@@ -35504,111 +35844,10 @@ class LocusClient {
|
|
|
35504
35844
|
}
|
|
35505
35845
|
}
|
|
35506
35846
|
|
|
35507
|
-
// ../sdk/src/agent/artifact-syncer.ts
|
|
35508
|
-
import {
|
|
35509
|
-
existsSync as existsSync2,
|
|
35510
|
-
mkdirSync,
|
|
35511
|
-
readdirSync,
|
|
35512
|
-
readFileSync as readFileSync2,
|
|
35513
|
-
statSync,
|
|
35514
|
-
writeFileSync
|
|
35515
|
-
} from "node:fs";
|
|
35516
|
-
import { join as join3 } from "node:path";
|
|
35517
|
-
class ArtifactSyncer {
|
|
35518
|
-
deps;
|
|
35519
|
-
constructor(deps) {
|
|
35520
|
-
this.deps = deps;
|
|
35521
|
-
}
|
|
35522
|
-
async getOrCreateArtifactsGroup() {
|
|
35523
|
-
try {
|
|
35524
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
35525
|
-
const artifactsGroup = groups.find((g) => g.name === "Artifacts");
|
|
35526
|
-
if (artifactsGroup) {
|
|
35527
|
-
return artifactsGroup.id;
|
|
35528
|
-
}
|
|
35529
|
-
const newGroup = await this.deps.client.docs.createGroup(this.deps.workspaceId, {
|
|
35530
|
-
name: "Artifacts",
|
|
35531
|
-
order: 999
|
|
35532
|
-
});
|
|
35533
|
-
this.deps.log("Created 'Artifacts' group for agent-generated docs", "info");
|
|
35534
|
-
return newGroup.id;
|
|
35535
|
-
} catch (error48) {
|
|
35536
|
-
this.deps.log(`Failed to get/create Artifacts group: ${error48}`, "error");
|
|
35537
|
-
throw error48;
|
|
35538
|
-
}
|
|
35539
|
-
}
|
|
35540
|
-
async sync() {
|
|
35541
|
-
const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
|
|
35542
|
-
if (!existsSync2(artifactsDir)) {
|
|
35543
|
-
mkdirSync(artifactsDir, { recursive: true });
|
|
35544
|
-
return;
|
|
35545
|
-
}
|
|
35546
|
-
try {
|
|
35547
|
-
const files = readdirSync(artifactsDir);
|
|
35548
|
-
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
35549
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
35550
|
-
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
35551
|
-
let artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
35552
|
-
if (!artifactsGroupId) {
|
|
35553
|
-
artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
35554
|
-
}
|
|
35555
|
-
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
35556
|
-
for (const file2 of files) {
|
|
35557
|
-
const filePath = join3(artifactsDir, file2);
|
|
35558
|
-
if (statSync(filePath).isFile()) {
|
|
35559
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
35560
|
-
const title = file2.replace(/\.md$/, "").trim();
|
|
35561
|
-
if (!title)
|
|
35562
|
-
continue;
|
|
35563
|
-
const existing = existingDocs.find((d) => d.title === title);
|
|
35564
|
-
if (existing) {
|
|
35565
|
-
if (existing.content !== content || existing.groupId !== artifactsGroupId) {
|
|
35566
|
-
await this.deps.client.docs.update(existing.id, this.deps.workspaceId, { content, groupId: artifactsGroupId });
|
|
35567
|
-
this.deps.log(`Updated artifact: ${file2}`, "success");
|
|
35568
|
-
}
|
|
35569
|
-
} else {
|
|
35570
|
-
await this.deps.client.docs.create(this.deps.workspaceId, {
|
|
35571
|
-
title,
|
|
35572
|
-
content,
|
|
35573
|
-
groupId: artifactsGroupId,
|
|
35574
|
-
type: "GENERAL" /* GENERAL */
|
|
35575
|
-
});
|
|
35576
|
-
this.deps.log(`Created artifact: ${file2}`, "success");
|
|
35577
|
-
}
|
|
35578
|
-
}
|
|
35579
|
-
}
|
|
35580
|
-
for (const doc3 of existingDocs) {
|
|
35581
|
-
if (doc3.groupId === artifactsGroupId) {
|
|
35582
|
-
const fileName = `${doc3.title}.md`;
|
|
35583
|
-
const filePath = join3(artifactsDir, fileName);
|
|
35584
|
-
if (!existsSync2(filePath)) {
|
|
35585
|
-
writeFileSync(filePath, doc3.content || "");
|
|
35586
|
-
this.deps.log(`Fetched artifact: ${fileName}`, "success");
|
|
35587
|
-
}
|
|
35588
|
-
} else {
|
|
35589
|
-
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
35590
|
-
const groupName = groupMap.get(doc3.groupId || "") || "General";
|
|
35591
|
-
const groupDir = join3(documentsDir, groupName);
|
|
35592
|
-
if (!existsSync2(groupDir)) {
|
|
35593
|
-
mkdirSync(groupDir, { recursive: true });
|
|
35594
|
-
}
|
|
35595
|
-
const fileName = `${doc3.title}.md`;
|
|
35596
|
-
const filePath = join3(groupDir, fileName);
|
|
35597
|
-
if (!existsSync2(filePath) || readFileSync2(filePath, "utf-8") !== doc3.content) {
|
|
35598
|
-
writeFileSync(filePath, doc3.content || "");
|
|
35599
|
-
}
|
|
35600
|
-
}
|
|
35601
|
-
}
|
|
35602
|
-
} catch (error48) {
|
|
35603
|
-
this.deps.log(`Failed to sync artifacts: ${error48}`, "error");
|
|
35604
|
-
}
|
|
35605
|
-
}
|
|
35606
|
-
}
|
|
35607
|
-
|
|
35608
35847
|
// ../sdk/src/core/indexer.ts
|
|
35609
35848
|
import { createHash } from "node:crypto";
|
|
35610
|
-
import { existsSync as
|
|
35611
|
-
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";
|
|
35612
35851
|
|
|
35613
35852
|
// ../../node_modules/globby/index.js
|
|
35614
35853
|
import process4 from "node:process";
|
|
@@ -36121,7 +36360,7 @@ class CodebaseIndexer {
|
|
|
36121
36360
|
fullReindexRatioThreshold = 0.2;
|
|
36122
36361
|
constructor(projectPath) {
|
|
36123
36362
|
this.projectPath = projectPath;
|
|
36124
|
-
this.indexPath =
|
|
36363
|
+
this.indexPath = join3(projectPath, ".locus", "codebase-index.json");
|
|
36125
36364
|
}
|
|
36126
36365
|
async index(onProgress, treeSummarizer, force = false) {
|
|
36127
36366
|
if (!treeSummarizer) {
|
|
@@ -36177,11 +36416,11 @@ class CodebaseIndexer {
|
|
|
36177
36416
|
}
|
|
36178
36417
|
}
|
|
36179
36418
|
async getFileTree() {
|
|
36180
|
-
const gitmodulesPath =
|
|
36419
|
+
const gitmodulesPath = join3(this.projectPath, ".gitmodules");
|
|
36181
36420
|
const submoduleIgnores = [];
|
|
36182
|
-
if (
|
|
36421
|
+
if (existsSync2(gitmodulesPath)) {
|
|
36183
36422
|
try {
|
|
36184
|
-
const content =
|
|
36423
|
+
const content = readFileSync2(gitmodulesPath, "utf-8");
|
|
36185
36424
|
const lines = content.split(`
|
|
36186
36425
|
`);
|
|
36187
36426
|
for (const line of lines) {
|
|
@@ -36237,9 +36476,9 @@ class CodebaseIndexer {
|
|
|
36237
36476
|
});
|
|
36238
36477
|
}
|
|
36239
36478
|
loadIndex() {
|
|
36240
|
-
if (
|
|
36479
|
+
if (existsSync2(this.indexPath)) {
|
|
36241
36480
|
try {
|
|
36242
|
-
return JSON.parse(
|
|
36481
|
+
return JSON.parse(readFileSync2(this.indexPath, "utf-8"));
|
|
36243
36482
|
} catch {
|
|
36244
36483
|
return null;
|
|
36245
36484
|
}
|
|
@@ -36248,10 +36487,10 @@ class CodebaseIndexer {
|
|
|
36248
36487
|
}
|
|
36249
36488
|
saveIndex(index) {
|
|
36250
36489
|
const dir = dirname(this.indexPath);
|
|
36251
|
-
if (!
|
|
36252
|
-
|
|
36490
|
+
if (!existsSync2(dir)) {
|
|
36491
|
+
mkdirSync(dir, { recursive: true });
|
|
36253
36492
|
}
|
|
36254
|
-
|
|
36493
|
+
writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
36255
36494
|
}
|
|
36256
36495
|
cloneIndex(index) {
|
|
36257
36496
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -36267,7 +36506,7 @@ class CodebaseIndexer {
|
|
|
36267
36506
|
}
|
|
36268
36507
|
hashFile(filePath) {
|
|
36269
36508
|
try {
|
|
36270
|
-
const content =
|
|
36509
|
+
const content = readFileSync2(join3(this.projectPath, filePath), "utf-8");
|
|
36271
36510
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
36272
36511
|
} catch {
|
|
36273
36512
|
return null;
|
|
@@ -36371,8 +36610,52 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
36371
36610
|
}
|
|
36372
36611
|
}
|
|
36373
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
|
+
|
|
36374
36657
|
// ../sdk/src/core/prompt-builder.ts
|
|
36375
|
-
import { existsSync as existsSync4, readdirSync
|
|
36658
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
36376
36659
|
import { homedir } from "node:os";
|
|
36377
36660
|
import { join as join5 } from "node:path";
|
|
36378
36661
|
class PromptBuilder {
|
|
@@ -36463,7 +36746,7 @@ ${serverContext.context}
|
|
|
36463
36746
|
`;
|
|
36464
36747
|
prompt += `You have access to the following documentation directories for context:
|
|
36465
36748
|
`;
|
|
36466
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
36749
|
+
prompt += `- Artifacts: \`.locus/artifacts\`)
|
|
36467
36750
|
`;
|
|
36468
36751
|
prompt += `- Documents: \`.locus/documents\`
|
|
36469
36752
|
`;
|
|
@@ -36577,9 +36860,9 @@ ${fallback}
|
|
|
36577
36860
|
`;
|
|
36578
36861
|
prompt += `You have access to the following documentation directories for context:
|
|
36579
36862
|
`;
|
|
36580
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
36863
|
+
prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
36581
36864
|
`;
|
|
36582
|
-
prompt += `- Documents: \`.locus/documents\`
|
|
36865
|
+
prompt += `- Documents: \`.locus/documents\` (synced from cloud)
|
|
36583
36866
|
`;
|
|
36584
36867
|
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
36585
36868
|
|
|
@@ -36625,12 +36908,12 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
36625
36908
|
}
|
|
36626
36909
|
getProjectStructure() {
|
|
36627
36910
|
try {
|
|
36628
|
-
const entries =
|
|
36911
|
+
const entries = readdirSync(this.projectPath);
|
|
36629
36912
|
const folders = entries.filter((e) => {
|
|
36630
36913
|
if (e.startsWith(".") || e === "node_modules")
|
|
36631
36914
|
return false;
|
|
36632
36915
|
try {
|
|
36633
|
-
return
|
|
36916
|
+
return statSync(join5(this.projectPath, e)).isDirectory();
|
|
36634
36917
|
} catch {
|
|
36635
36918
|
return false;
|
|
36636
36919
|
}
|
|
@@ -36688,9 +36971,9 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
36688
36971
|
if (!existsSync4(dirPath))
|
|
36689
36972
|
return;
|
|
36690
36973
|
try {
|
|
36691
|
-
const entries =
|
|
36974
|
+
const entries = readdirSync(dirPath).filter((name) => {
|
|
36692
36975
|
try {
|
|
36693
|
-
return
|
|
36976
|
+
return statSync(join5(dirPath, name)).isDirectory();
|
|
36694
36977
|
} catch {
|
|
36695
36978
|
return false;
|
|
36696
36979
|
}
|
|
@@ -36789,7 +37072,7 @@ class AgentWorker {
|
|
|
36789
37072
|
client;
|
|
36790
37073
|
aiRunner;
|
|
36791
37074
|
indexerService;
|
|
36792
|
-
|
|
37075
|
+
documentFetcher;
|
|
36793
37076
|
taskExecutor;
|
|
36794
37077
|
consecutiveEmpty = 0;
|
|
36795
37078
|
maxEmpty = 60;
|
|
@@ -36821,7 +37104,7 @@ class AgentWorker {
|
|
|
36821
37104
|
projectPath,
|
|
36822
37105
|
log
|
|
36823
37106
|
});
|
|
36824
|
-
this.
|
|
37107
|
+
this.documentFetcher = new DocumentFetcher({
|
|
36825
37108
|
client: this.client,
|
|
36826
37109
|
workspaceId: config2.workspaceId,
|
|
36827
37110
|
projectPath,
|
|
@@ -36907,9 +37190,9 @@ class AgentWorker {
|
|
|
36907
37190
|
this.log(`Claimed: ${task2.title}`, "success");
|
|
36908
37191
|
const result = await this.executeTask(task2);
|
|
36909
37192
|
try {
|
|
36910
|
-
await this.
|
|
37193
|
+
await this.documentFetcher.fetch();
|
|
36911
37194
|
} catch (err) {
|
|
36912
|
-
this.log(`
|
|
37195
|
+
this.log(`Document fetch failed: ${err}`, "error");
|
|
36913
37196
|
}
|
|
36914
37197
|
if (result.success) {
|
|
36915
37198
|
this.log(`Completed: ${task2.title}`, "success");
|