@mclean-capital/neura 3.5.1 → 3.5.3

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.
@@ -73035,7 +73035,9 @@ function loadNeuraSkills(options = {}) {
73035
73035
  const repoLocal = resolve(cwd, ".neura", "skills");
73036
73036
  const global2 = options.globalSkillsDir ?? resolve(homedir2(), ".neura", "skills");
73037
73037
  const explicit = options.explicitPaths ?? [];
73038
- const skillPaths = [repoLocal, global2, ...explicit];
73038
+ const skillPaths = [repoLocal, global2];
73039
+ if (options.bundledSkillsDir) skillPaths.push(options.bundledSkillsDir);
73040
+ skillPaths.push(...explicit);
73039
73041
  log12.info("loading Neura skills (repo-local \u2192 global \u2192 explicit, pi defaults excluded)", {
73040
73042
  cwd,
73041
73043
  skillPaths
@@ -73142,6 +73144,7 @@ var SkillWatcher = class {
73142
73144
  registry;
73143
73145
  cwd;
73144
73146
  globalSkillsDir;
73147
+ bundledSkillsDir;
73145
73148
  explicitPaths;
73146
73149
  debounceMs;
73147
73150
  onReload;
@@ -73153,6 +73156,7 @@ var SkillWatcher = class {
73153
73156
  this.registry = options.registry;
73154
73157
  this.cwd = options.cwd ?? process.cwd();
73155
73158
  this.globalSkillsDir = options.globalSkillsDir ?? resolve2(homedir3(), ".neura", "skills");
73159
+ this.bundledSkillsDir = options.bundledSkillsDir;
73156
73160
  this.explicitPaths = options.explicitPaths ?? [];
73157
73161
  this.debounceMs = options.debounceMs ?? 200;
73158
73162
  this.onReload = options.onReload;
@@ -73165,7 +73169,9 @@ var SkillWatcher = class {
73165
73169
  if (this.watcher) return;
73166
73170
  this.reload();
73167
73171
  const repoLocal = resolve2(this.cwd, ".neura", "skills");
73168
- const paths = [repoLocal, this.globalSkillsDir, ...this.explicitPaths];
73172
+ const paths = [repoLocal, this.globalSkillsDir];
73173
+ if (this.bundledSkillsDir) paths.push(this.bundledSkillsDir);
73174
+ paths.push(...this.explicitPaths);
73169
73175
  log13.info("starting skill watcher", { paths, debounceMs: this.debounceMs });
73170
73176
  this.watcher = chokidar.watch(paths, {
73171
73177
  ignoreInitial: true,
@@ -73241,6 +73247,7 @@ var SkillWatcher = class {
73241
73247
  const result = loadNeuraSkills({
73242
73248
  cwd: this.cwd,
73243
73249
  globalSkillsDir: this.globalSkillsDir,
73250
+ bundledSkillsDir: this.bundledSkillsDir,
73244
73251
  explicitPaths: this.explicitPaths
73245
73252
  });
73246
73253
  this.registry.replaceAll(result.skills);
@@ -76084,6 +76091,18 @@ var MEMORY_NAMES = /* @__PURE__ */ new Set([
76084
76091
  "memory_stats"
76085
76092
  ]);
76086
76093
 
76094
+ // src/tools/voice-redact.ts
76095
+ function redactTaskForVoice(task) {
76096
+ const { workerId, ...rest } = task;
76097
+ return { ...rest, hasActiveWorker: workerId !== null };
76098
+ }
76099
+ function redactCommentForVoice(comment) {
76100
+ if (comment.author.startsWith("worker:")) {
76101
+ return { ...comment, author: "worker" };
76102
+ }
76103
+ return comment;
76104
+ }
76105
+
76087
76106
  // src/tools/task-tools.ts
76088
76107
  var log15 = new Logger("tool:task");
76089
76108
  var ALL_STATUSES = [
@@ -76114,7 +76133,7 @@ var taskToolDefs = [
76114
76133
  {
76115
76134
  type: "function",
76116
76135
  name: "create_task",
76117
- description: "Create a new task. Use when the user asks you to do something actionable (file operations, research, code changes, reminders). Orchestrator uses create_task to brief a worker BEFORE dispatching \u2014 pair with dispatch_worker when the user confirms. Include `goal` whenever possible (what success looks like).",
76136
+ description: "Create a new task. Use when the user asks you to do something actionable (file operations, research, code changes, reminders). Orchestrator uses create_task to brief a worker BEFORE dispatching \u2014 pair with dispatch_worker when the user confirms. Include `goal` whenever possible (what success looks like). The returned `id` is an internal handle for subsequent tool calls \u2014 NEVER speak it aloud; refer to the task by its title when talking to the user.",
76118
76137
  parameters: {
76119
76138
  type: "object",
76120
76139
  properties: {
@@ -76264,7 +76283,7 @@ var taskToolDefs = [
76264
76283
  {
76265
76284
  type: "function",
76266
76285
  name: "dispatch_worker",
76267
- description: "Dispatch a worker to execute an existing task. The task must already have a clear goal + context \u2014 call create_task first, confirm with the user for non-trivial / destructive work, then dispatch. Returns a worker_id immediately; progress flows via comments on the task.",
76286
+ description: "Dispatch a worker to execute an existing task. The task must already have a clear goal + context \u2014 call create_task first, confirm with the user for non-trivial / destructive work, then dispatch. Progress flows via comments on the task (see get_task). When confirming to the user, speak naturally about the task ('Dispatching the worker now') \u2014 do NOT quote task IDs or worker IDs aloud, the TTS reads UUIDs letter by letter and it's jarring.",
76268
76287
  parameters: {
76269
76288
  type: "object",
76270
76289
  properties: {
@@ -76325,7 +76344,12 @@ async function handleTaskTool(name, args, ctx) {
76325
76344
  goal: t2.goal,
76326
76345
  source: t2.source,
76327
76346
  version: t2.version,
76328
- workerId: t2.workerId
76347
+ // workerId deliberately omitted from the voice-facing
76348
+ // listing — TTS reads UUIDs letter-by-letter. Callers
76349
+ // that need the worker id should go through
76350
+ // list_active_workers (which runs internal tool calls
76351
+ // that don't narrate).
76352
+ hasActiveWorker: t2.workerId !== null
76329
76353
  }))
76330
76354
  }
76331
76355
  };
@@ -76335,7 +76359,27 @@ async function handleTaskTool(name, args, ctx) {
76335
76359
  const query = args.query;
76336
76360
  const task = await ctx.taskTools.getTask(query);
76337
76361
  if (!task) return { result: { found: false } };
76338
- return { result: { found: true, task } };
76362
+ let comments = [];
76363
+ try {
76364
+ const recentDesc = await ctx.taskTools.listTaskComments(task.id, {
76365
+ limit: 50,
76366
+ order: "desc",
76367
+ excludeTypes: ["heartbeat"]
76368
+ });
76369
+ comments = recentDesc.reverse();
76370
+ } catch (err) {
76371
+ log15.error("failed to load task comments for get_task", {
76372
+ taskId: task.id,
76373
+ err: String(err)
76374
+ });
76375
+ }
76376
+ return {
76377
+ result: {
76378
+ found: true,
76379
+ task: redactTaskForVoice(task),
76380
+ comments: comments.map(redactCommentForVoice)
76381
+ }
76382
+ };
76339
76383
  }
76340
76384
  case "update_task": {
76341
76385
  if (!ctx.taskTools) return { error: "Task system not available" };
@@ -76396,7 +76440,6 @@ async function handleTaskTool(name, args, ctx) {
76396
76440
  return {
76397
76441
  result: {
76398
76442
  dispatched: true,
76399
- workerId: outcome.workerId,
76400
76443
  message: `Worker dispatched. You'll hear progress updates as it works.`
76401
76444
  }
76402
76445
  };
@@ -77385,6 +77428,44 @@ async function insertComment(db, opts) {
77385
77428
  );
77386
77429
  return mapTaskComment(result.rows[0]);
77387
77430
  }
77431
+ async function listComments(db, opts) {
77432
+ const limit2 = opts.limit ?? 500;
77433
+ const order = opts.order ?? "asc";
77434
+ const filters = ["task_id = $1"];
77435
+ const values = [opts.taskId];
77436
+ let idx = 2;
77437
+ if (opts.type !== void 0) {
77438
+ const types3 = Array.isArray(opts.type) ? opts.type : [opts.type];
77439
+ const placeholders = types3.map(() => `$${idx++}`).join(", ");
77440
+ filters.push(`type IN (${placeholders})`);
77441
+ values.push(...types3);
77442
+ } else if (opts.excludeTypes && opts.excludeTypes.length > 0) {
77443
+ const placeholders = opts.excludeTypes.map(() => `$${idx++}`).join(", ");
77444
+ filters.push(`type NOT IN (${placeholders})`);
77445
+ values.push(...opts.excludeTypes);
77446
+ }
77447
+ if (opts.since) {
77448
+ filters.push(`created_at > $${idx++}`);
77449
+ values.push(opts.since);
77450
+ }
77451
+ const result = await db.query(
77452
+ `SELECT * FROM task_comments
77453
+ WHERE ${filters.join(" AND ")}
77454
+ ORDER BY created_at ${order === "desc" ? "DESC" : "ASC"}
77455
+ LIMIT $${idx}`,
77456
+ [...values, limit2]
77457
+ );
77458
+ return result.rows.map((r2) => mapTaskComment(r2));
77459
+ }
77460
+ async function pruneHeartbeats(db, taskId, author) {
77461
+ const result = await db.query(
77462
+ `DELETE FROM task_comments
77463
+ WHERE task_id = $1 AND type = 'heartbeat' AND author = $2
77464
+ RETURNING id`,
77465
+ [taskId, author]
77466
+ );
77467
+ return result.rows.length;
77468
+ }
77388
77469
  async function countOpenRequests(db, taskId) {
77389
77470
  const result = await db.query(
77390
77471
  `SELECT COUNT(*)::TEXT as count FROM task_comments
@@ -78423,6 +78504,12 @@ async function applyTaskUpdate(args) {
78423
78504
  urgency: payload.comment.urgency ?? null,
78424
78505
  metadata: payload.comment.metadata ?? null
78425
78506
  });
78507
+ if (payload.comment.type !== "heartbeat" && actor.startsWith("worker:")) {
78508
+ try {
78509
+ await pruneHeartbeats(db, task.id, actor);
78510
+ } catch {
78511
+ }
78512
+ }
78426
78513
  }
78427
78514
  const refreshed = await getWorkItem(db, task.id);
78428
78515
  if (!refreshed) {
@@ -78932,22 +79019,40 @@ async function handleWorkerControlTool(name, args, ctx) {
78932
79019
  case "pause_worker": {
78933
79020
  const workerId = args.worker_id;
78934
79021
  const result = await ctx.workerControl.pauseWorker(workerId);
78935
- return { result };
79022
+ return {
79023
+ result: { paused: result.paused, ...result.reason ? { reason: result.reason } : {} }
79024
+ };
78936
79025
  }
78937
79026
  case "resume_worker": {
78938
79027
  const workerId = args.worker_id;
78939
79028
  const message = args.message;
78940
79029
  const result = await ctx.workerControl.resumeWorker(workerId, message);
78941
- return { result };
79030
+ return {
79031
+ result: { resumed: result.resumed, ...result.reason ? { reason: result.reason } : {} }
79032
+ };
78942
79033
  }
78943
79034
  case "cancel_worker": {
78944
79035
  const workerId = args.worker_id;
78945
79036
  const result = await ctx.workerControl.cancelWorker(workerId);
78946
- return { result };
79037
+ return {
79038
+ result: {
79039
+ cancelled: result.cancelled,
79040
+ ...result.reason ? { reason: result.reason } : {}
79041
+ }
79042
+ };
78947
79043
  }
78948
79044
  case "list_active_workers": {
78949
79045
  const workers = await ctx.workerControl.listActive();
78950
- return { result: { count: workers.length, workers } };
79046
+ return {
79047
+ result: {
79048
+ count: workers.length,
79049
+ workers: workers.map((w) => ({
79050
+ status: w.status,
79051
+ skillName: w.skillName,
79052
+ startedAt: w.startedAt
79053
+ }))
79054
+ }
79055
+ };
78951
79056
  }
78952
79057
  default:
78953
79058
  return null;
@@ -79256,14 +79361,20 @@ async function initServices() {
79256
79361
  const agentDir = join4(config2.neuraHome, "agent");
79257
79362
  const sessionDir = defaultSessionDir(agentDir);
79258
79363
  const globalSkillsDir = join4(homedir5(), ".neura", "skills");
79364
+ const bundleDir = dirname2(fileURLToPath(import.meta.url));
79365
+ const bundledSkillsDir = join4(bundleDir, "..", "skills");
79259
79366
  skillRegistry = new SkillRegistry();
79260
79367
  skillWatcher = new SkillWatcher({
79261
79368
  registry: skillRegistry,
79262
79369
  cwd: process.cwd(),
79263
- globalSkillsDir
79370
+ globalSkillsDir,
79371
+ bundledSkillsDir
79264
79372
  });
79265
79373
  await skillWatcher.start();
79266
- log29.info("skill registry loaded", { count: skillRegistry.size });
79374
+ log29.info("skill registry loaded", {
79375
+ count: skillRegistry.size,
79376
+ bundledSkillsDir
79377
+ });
79267
79378
  voiceFanoutBridge = new VoiceFanoutBridge({
79268
79379
  interjector: { interject: () => Promise.resolve() }
79269
79380
  });
@@ -79314,6 +79425,14 @@ async function initServices() {
79314
79425
  return candidates.slice(0, limit2);
79315
79426
  },
79316
79427
  getTask: (idOrTitle) => store.getWorkItem(idOrTitle),
79428
+ listTaskComments: async (taskId, options) => {
79429
+ return listComments(rawDb, {
79430
+ taskId,
79431
+ limit: options?.limit,
79432
+ order: options?.order,
79433
+ excludeTypes: options?.excludeTypes
79434
+ });
79435
+ },
79317
79436
  updateTask: async (idOrTitle, payload) => {
79318
79437
  const current = await store.getWorkItem(idOrTitle);
79319
79438
  if (!current) return null;
@@ -99023,6 +99142,16 @@ function attachWebSocket(httpServer2, services2) {
99023
99142
  return candidates.slice(0, limit2);
99024
99143
  },
99025
99144
  getTask: (idOrTitle) => resolveTask(store, idOrTitle),
99145
+ listTaskComments: async (taskId, options) => {
99146
+ const db = store.getRawDb?.();
99147
+ if (!db) throw new Error("store does not expose a raw PGlite handle");
99148
+ return listComments(db, {
99149
+ taskId,
99150
+ limit: options?.limit,
99151
+ order: options?.order,
99152
+ excludeTypes: options?.excludeTypes
99153
+ });
99154
+ },
99026
99155
  updateTask: async (idOrTitle, payload) => {
99027
99156
  const current = await resolveTask(store, idOrTitle);
99028
99157
  if (!current) return null;