@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.
Files changed (3) hide show
  1. package/bin/agent/worker.js +481 -129
  2. package/bin/locus.js +32990 -29569
  3. package/package.json +2 -2
@@ -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 statSync2(path, optionsOrSettings) {
14883
+ function statSync(path, optionsOrSettings) {
14884
14884
  const settings = getSettings(optionsOrSettings);
14885
14885
  return sync.read(path, settings);
14886
14886
  }
14887
- exports.statSync = statSync2;
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]: "sonnet",
17022
- [PROVIDER.CODEX]: "gpt-5.1-codex-mini"
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 existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
35612
- import { dirname, join as join4 } from "node:path";
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 = join4(projectPath, ".locus", "codebase-index.json");
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 = join4(this.projectPath, ".gitmodules");
36419
+ const gitmodulesPath = join3(this.projectPath, ".gitmodules");
36182
36420
  const submoduleIgnores = [];
36183
- if (existsSync3(gitmodulesPath)) {
36421
+ if (existsSync2(gitmodulesPath)) {
36184
36422
  try {
36185
- const content = readFileSync3(gitmodulesPath, "utf-8");
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 (existsSync3(this.indexPath)) {
36479
+ if (existsSync2(this.indexPath)) {
36242
36480
  try {
36243
- return JSON.parse(readFileSync3(this.indexPath, "utf-8"));
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 (!existsSync3(dir)) {
36253
- mkdirSync2(dir, { recursive: true });
36490
+ if (!existsSync2(dir)) {
36491
+ mkdirSync(dir, { recursive: true });
36254
36492
  }
36255
- writeFileSync2(this.indexPath, JSON.stringify(index, null, 2));
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 = readFileSync3(join4(this.projectPath, filePath), "utf-8");
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 as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
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 = readdirSync2(this.projectPath);
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 statSync2(join5(this.projectPath, e)).isDirectory();
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 = readdirSync2(dirPath).filter((name) => {
36974
+ const entries = readdirSync(dirPath).filter((name) => {
36623
36975
  try {
36624
- return statSync2(join5(dirPath, name)).isDirectory();
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
- artifactSyncer;
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.artifactSyncer = new ArtifactSyncer({
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.artifactSyncer.sync();
37193
+ await this.documentFetcher.fetch();
36842
37194
  } catch (err) {
36843
- this.log(`Artifact sync failed: ${err}`, "error");
37195
+ this.log(`Document fetch failed: ${err}`, "error");
36844
37196
  }
36845
37197
  if (result.success) {
36846
37198
  this.log(`Completed: ${task2.title}`, "success");