@mclean-capital/neura 3.5.0 → 3.5.1

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.
@@ -76397,7 +76397,7 @@ async function handleTaskTool(name, args, ctx) {
76397
76397
  result: {
76398
76398
  dispatched: true,
76399
76399
  workerId: outcome.workerId,
76400
- message: `Worker ${outcome.workerId} dispatched. You'll hear progress updates as it works.`
76400
+ message: `Worker dispatched. You'll hear progress updates as it works.`
76401
76401
  }
76402
76402
  };
76403
76403
  }
@@ -77355,6 +77355,53 @@ async function sweepCrashedWorkers(db, fileExists) {
77355
77355
  // src/workers/agent-worker.ts
77356
77356
  init_work_item_queries();
77357
77357
 
77358
+ // src/stores/task-comment-queries.ts
77359
+ init_mappers();
77360
+ import crypto4 from "crypto";
77361
+ var TASK_COMMENT_CONTENT_MAX_BYTES = 32 * 1024;
77362
+ async function insertComment(db, opts) {
77363
+ if (Buffer.byteLength(opts.content, "utf8") > TASK_COMMENT_CONTENT_MAX_BYTES) {
77364
+ throw new Error(
77365
+ `task comment content exceeds ${TASK_COMMENT_CONTENT_MAX_BYTES} bytes; write to disk and pass attachment_path with a summary in content`
77366
+ );
77367
+ }
77368
+ const id = crypto4.randomUUID();
77369
+ const result = await db.query(
77370
+ `INSERT INTO task_comments (
77371
+ id, task_id, type, author, content, attachment_path, urgency, metadata
77372
+ )
77373
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
77374
+ RETURNING *`,
77375
+ [
77376
+ id,
77377
+ opts.taskId,
77378
+ opts.type,
77379
+ opts.author,
77380
+ opts.content,
77381
+ opts.attachmentPath ?? null,
77382
+ opts.urgency ?? null,
77383
+ opts.metadata ? JSON.stringify(opts.metadata) : null
77384
+ ]
77385
+ );
77386
+ return mapTaskComment(result.rows[0]);
77387
+ }
77388
+ async function countOpenRequests(db, taskId) {
77389
+ const result = await db.query(
77390
+ `SELECT COUNT(*)::TEXT as count FROM task_comments
77391
+ WHERE task_id = $1
77392
+ AND type IN ('clarification_request', 'approval_request')
77393
+ AND id NOT IN (
77394
+ -- requests with a matching response comment are considered resolved
77395
+ SELECT (metadata->>'resolves_comment_id')::text FROM task_comments
77396
+ WHERE task_id = $1
77397
+ AND type IN ('clarification_response', 'approval_response')
77398
+ AND metadata ? 'resolves_comment_id'
77399
+ )`,
77400
+ [taskId]
77401
+ );
77402
+ return Number(result.rows[0]?.count ?? 0);
77403
+ }
77404
+
77358
77405
  // src/workers/worktree-manager.ts
77359
77406
  import { execFileSync } from "node:child_process";
77360
77407
  import {
@@ -77590,7 +77637,15 @@ var CANONICAL_WORKER_SYSTEM_PROMPT = `You are a Neura worker \u2014 a capable en
77590
77637
 
77591
77638
  Your posture: be decisive. You have full tool access \u2014 Read, Write, Edit, Bash \u2014 scoped to an isolated worktree directory (your cwd). Make progress. Don't ask the user to double-check obvious things. Don't propose a plan and wait for approval when the path is clear.
77592
77639
 
77640
+ Your cwd is an isolated sandbox under \`~/.neura/worktrees/<workerId>/\`. It is NOT the user's home directory, desktop, or documents folder. Files you create land in the sandbox, not in user-visible locations. A task whose goal requires writing to the user's Desktop, Documents, or any absolute path outside your cwd cannot be satisfied by writing inside cwd \u2014 that would silently put the file in the wrong place. In that case:
77641
+
77642
+ 1. If the outside path is essential to the task, call \`request_approval\` with a rationale ("task goal requires writing to ~/Desktop, which is outside my sandbox \u2014 authorize access?") and wait. If denied, call \`fail_task\` with \`reason_code: 'impossible'\`.
77643
+ 2. If you can satisfy the goal inside cwd without writing to the user-visible path, do that and make it clear in your \`complete_task\` summary where the file actually lives.
77644
+
77645
+ Do NOT pretend success when you wrote to the wrong place. Silent "it completed" with no file where the user expects it is the worst outcome.
77646
+
77593
77647
  Reversibility rule: before any destructive or hard-to-reverse action **outside** your worktree, call \`request_approval\` and wait for the user's answer. Examples that require approval:
77648
+ - Writing to the user's home directory, Desktop, Documents, or any absolute path outside cwd
77594
77649
  - Deleting files the user owns
77595
77650
  - Force-pushing branches, rewriting git history
77596
77651
  - Sending email, SMS, or other external messages
@@ -78030,6 +78085,31 @@ var AgentWorker = class {
78030
78085
  });
78031
78086
  }
78032
78087
  }
78088
+ if ((status === "failed" || status === "crashed") && result.error) {
78089
+ try {
78090
+ const { reason, detail } = result.error;
78091
+ const MAX_DETAIL_BYTES = 3e4;
78092
+ const safeDetail = detail ? detail.slice(0, MAX_DETAIL_BYTES) : "";
78093
+ const content = safeDetail ? `${reason}: ${safeDetail}` : reason;
78094
+ await insertComment(this.db, {
78095
+ taskId,
78096
+ type: "error",
78097
+ author: "system",
78098
+ content,
78099
+ metadata: {
78100
+ workerId,
78101
+ workerStatus: status,
78102
+ reason
78103
+ }
78104
+ });
78105
+ } catch (err) {
78106
+ log23.warn("failed to persist error comment on task", {
78107
+ workerId,
78108
+ taskId,
78109
+ err: String(err)
78110
+ });
78111
+ }
78112
+ }
78033
78113
  }
78034
78114
  try {
78035
78115
  if (status === "completed" || status === "cancelled") {
@@ -78195,53 +78275,6 @@ function buildClarificationTool(workerId, bridge) {
78195
78275
  return tool;
78196
78276
  }
78197
78277
 
78198
- // src/stores/task-comment-queries.ts
78199
- init_mappers();
78200
- import crypto4 from "crypto";
78201
- var TASK_COMMENT_CONTENT_MAX_BYTES = 32 * 1024;
78202
- async function insertComment(db, opts) {
78203
- if (Buffer.byteLength(opts.content, "utf8") > TASK_COMMENT_CONTENT_MAX_BYTES) {
78204
- throw new Error(
78205
- `task comment content exceeds ${TASK_COMMENT_CONTENT_MAX_BYTES} bytes; write to disk and pass attachment_path with a summary in content`
78206
- );
78207
- }
78208
- const id = crypto4.randomUUID();
78209
- const result = await db.query(
78210
- `INSERT INTO task_comments (
78211
- id, task_id, type, author, content, attachment_path, urgency, metadata
78212
- )
78213
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
78214
- RETURNING *`,
78215
- [
78216
- id,
78217
- opts.taskId,
78218
- opts.type,
78219
- opts.author,
78220
- opts.content,
78221
- opts.attachmentPath ?? null,
78222
- opts.urgency ?? null,
78223
- opts.metadata ? JSON.stringify(opts.metadata) : null
78224
- ]
78225
- );
78226
- return mapTaskComment(result.rows[0]);
78227
- }
78228
- async function countOpenRequests(db, taskId) {
78229
- const result = await db.query(
78230
- `SELECT COUNT(*)::TEXT as count FROM task_comments
78231
- WHERE task_id = $1
78232
- AND type IN ('clarification_request', 'approval_request')
78233
- AND id NOT IN (
78234
- -- requests with a matching response comment are considered resolved
78235
- SELECT (metadata->>'resolves_comment_id')::text FROM task_comments
78236
- WHERE task_id = $1
78237
- AND type IN ('clarification_response', 'approval_response')
78238
- AND metadata ? 'resolves_comment_id'
78239
- )`,
78240
- [taskId]
78241
- );
78242
- return Number(result.rows[0]?.count ?? 0);
78243
- }
78244
-
78245
78278
  // src/tools/task-update-handler.ts
78246
78279
  init_work_item_queries();
78247
78280
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "cancelled", "failed"]);
@@ -78672,6 +78705,21 @@ Reason: ${params.rationale}` : params.action;
78672
78705
  return tools;
78673
78706
  }
78674
78707
 
78708
+ // src/server/lifecycle.ts
78709
+ import { AuthStorage } from "@mariozechner/pi-coding-agent";
78710
+
78711
+ // src/workers/auth-bridge.ts
78712
+ function seedAuthStorageFromConfig(authStorage, providers) {
78713
+ let count = 0;
78714
+ for (const [providerId, creds] of Object.entries(providers)) {
78715
+ if (creds?.apiKey) {
78716
+ authStorage.setRuntimeApiKey(providerId, creds.apiKey);
78717
+ count++;
78718
+ }
78719
+ }
78720
+ return count;
78721
+ }
78722
+
78675
78723
  // src/tools/vision-tools.ts
78676
78724
  var visionToolDefs = [
78677
78725
  {
@@ -79332,6 +79380,9 @@ async function initServices() {
79332
79380
  `${wProvider}/${wModel} model not registered in pi-ai. Check config.routing.worker or verify pi-ai supports this provider.`
79333
79381
  );
79334
79382
  }
79383
+ const authStorage = AuthStorage.create(join4(agentDir, "auth.json"));
79384
+ const seededProviders = seedAuthStorageFromConfig(authStorage, config2.providers);
79385
+ log29.info("seeded pi auth storage from config", { count: seededProviders });
79335
79386
  const piRuntime = new PiRuntime({
79336
79387
  model,
79337
79388
  thinkingLevel: "low",
@@ -79340,7 +79391,8 @@ async function initServices() {
79340
79391
  sessionDir,
79341
79392
  buildTools,
79342
79393
  voiceFanoutBridge,
79343
- skillRegistry
79394
+ skillRegistry,
79395
+ authStorage
79344
79396
  });
79345
79397
  agentWorker = new AgentWorker({ db: rawDb, runtime: piRuntime });
79346
79398
  await agentWorker.recoverFromCrash();