@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.
- package/core/server.bundled.mjs +142 -13
- package/core/server.bundled.mjs.map +4 -4
- package/core/version.txt +1 -1
- package/package.json +2 -1
- package/skills/orchestrator-worker-control/SKILL.md +134 -0
- package/skills/red-test-triage/SKILL.md +80 -0
- package/skills/wiki-upload/SKILL.md +79 -0
- package/stores/task-comment-queries.d.ts +13 -0
- package/stores/task-comment-queries.d.ts.map +1 -1
- package/stores/task-comment-queries.js +7 -1
- package/stores/task-comment-queries.js.map +1 -1
- package/stores/task-comment-queries.test.js +50 -0
- package/stores/task-comment-queries.test.js.map +1 -1
package/core/server.bundled.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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", {
|
|
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;
|