@mclean-capital/neura 3.5.0 → 3.5.2
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 +145 -53
- package/core/server.bundled.mjs.map +4 -4
- package/core/version.txt +1 -1
- package/package.json +1 -1
package/core/server.bundled.mjs
CHANGED
|
@@ -76114,7 +76114,7 @@ var taskToolDefs = [
|
|
|
76114
76114
|
{
|
|
76115
76115
|
type: "function",
|
|
76116
76116
|
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).",
|
|
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). 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
76118
|
parameters: {
|
|
76119
76119
|
type: "object",
|
|
76120
76120
|
properties: {
|
|
@@ -76264,7 +76264,7 @@ var taskToolDefs = [
|
|
|
76264
76264
|
{
|
|
76265
76265
|
type: "function",
|
|
76266
76266
|
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.
|
|
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. 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
76268
|
parameters: {
|
|
76269
76269
|
type: "object",
|
|
76270
76270
|
properties: {
|
|
@@ -76335,7 +76335,16 @@ async function handleTaskTool(name, args, ctx) {
|
|
|
76335
76335
|
const query = args.query;
|
|
76336
76336
|
const task = await ctx.taskTools.getTask(query);
|
|
76337
76337
|
if (!task) return { result: { found: false } };
|
|
76338
|
-
|
|
76338
|
+
let comments = [];
|
|
76339
|
+
try {
|
|
76340
|
+
comments = await ctx.taskTools.listTaskComments(task.id, { limit: 50 });
|
|
76341
|
+
} catch (err) {
|
|
76342
|
+
log15.warn("failed to load task comments for get_task", {
|
|
76343
|
+
taskId: task.id,
|
|
76344
|
+
err: String(err)
|
|
76345
|
+
});
|
|
76346
|
+
}
|
|
76347
|
+
return { result: { found: true, task, comments } };
|
|
76339
76348
|
}
|
|
76340
76349
|
case "update_task": {
|
|
76341
76350
|
if (!ctx.taskTools) return { error: "Task system not available" };
|
|
@@ -76396,8 +76405,7 @@ async function handleTaskTool(name, args, ctx) {
|
|
|
76396
76405
|
return {
|
|
76397
76406
|
result: {
|
|
76398
76407
|
dispatched: true,
|
|
76399
|
-
|
|
76400
|
-
message: `Worker ${outcome.workerId} dispatched. You'll hear progress updates as it works.`
|
|
76408
|
+
message: `Worker dispatched. You'll hear progress updates as it works.`
|
|
76401
76409
|
}
|
|
76402
76410
|
};
|
|
76403
76411
|
}
|
|
@@ -77355,6 +77363,77 @@ async function sweepCrashedWorkers(db, fileExists) {
|
|
|
77355
77363
|
// src/workers/agent-worker.ts
|
|
77356
77364
|
init_work_item_queries();
|
|
77357
77365
|
|
|
77366
|
+
// src/stores/task-comment-queries.ts
|
|
77367
|
+
init_mappers();
|
|
77368
|
+
import crypto4 from "crypto";
|
|
77369
|
+
var TASK_COMMENT_CONTENT_MAX_BYTES = 32 * 1024;
|
|
77370
|
+
async function insertComment(db, opts) {
|
|
77371
|
+
if (Buffer.byteLength(opts.content, "utf8") > TASK_COMMENT_CONTENT_MAX_BYTES) {
|
|
77372
|
+
throw new Error(
|
|
77373
|
+
`task comment content exceeds ${TASK_COMMENT_CONTENT_MAX_BYTES} bytes; write to disk and pass attachment_path with a summary in content`
|
|
77374
|
+
);
|
|
77375
|
+
}
|
|
77376
|
+
const id = crypto4.randomUUID();
|
|
77377
|
+
const result = await db.query(
|
|
77378
|
+
`INSERT INTO task_comments (
|
|
77379
|
+
id, task_id, type, author, content, attachment_path, urgency, metadata
|
|
77380
|
+
)
|
|
77381
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
77382
|
+
RETURNING *`,
|
|
77383
|
+
[
|
|
77384
|
+
id,
|
|
77385
|
+
opts.taskId,
|
|
77386
|
+
opts.type,
|
|
77387
|
+
opts.author,
|
|
77388
|
+
opts.content,
|
|
77389
|
+
opts.attachmentPath ?? null,
|
|
77390
|
+
opts.urgency ?? null,
|
|
77391
|
+
opts.metadata ? JSON.stringify(opts.metadata) : null
|
|
77392
|
+
]
|
|
77393
|
+
);
|
|
77394
|
+
return mapTaskComment(result.rows[0]);
|
|
77395
|
+
}
|
|
77396
|
+
async function listComments(db, opts) {
|
|
77397
|
+
const limit2 = opts.limit ?? 500;
|
|
77398
|
+
const filters = ["task_id = $1"];
|
|
77399
|
+
const values = [opts.taskId];
|
|
77400
|
+
let idx = 2;
|
|
77401
|
+
if (opts.type !== void 0) {
|
|
77402
|
+
const types3 = Array.isArray(opts.type) ? opts.type : [opts.type];
|
|
77403
|
+
const placeholders = types3.map(() => `$${idx++}`).join(", ");
|
|
77404
|
+
filters.push(`type IN (${placeholders})`);
|
|
77405
|
+
values.push(...types3);
|
|
77406
|
+
}
|
|
77407
|
+
if (opts.since) {
|
|
77408
|
+
filters.push(`created_at > $${idx++}`);
|
|
77409
|
+
values.push(opts.since);
|
|
77410
|
+
}
|
|
77411
|
+
const result = await db.query(
|
|
77412
|
+
`SELECT * FROM task_comments
|
|
77413
|
+
WHERE ${filters.join(" AND ")}
|
|
77414
|
+
ORDER BY created_at ASC
|
|
77415
|
+
LIMIT $${idx}`,
|
|
77416
|
+
[...values, limit2]
|
|
77417
|
+
);
|
|
77418
|
+
return result.rows.map((r2) => mapTaskComment(r2));
|
|
77419
|
+
}
|
|
77420
|
+
async function countOpenRequests(db, taskId) {
|
|
77421
|
+
const result = await db.query(
|
|
77422
|
+
`SELECT COUNT(*)::TEXT as count FROM task_comments
|
|
77423
|
+
WHERE task_id = $1
|
|
77424
|
+
AND type IN ('clarification_request', 'approval_request')
|
|
77425
|
+
AND id NOT IN (
|
|
77426
|
+
-- requests with a matching response comment are considered resolved
|
|
77427
|
+
SELECT (metadata->>'resolves_comment_id')::text FROM task_comments
|
|
77428
|
+
WHERE task_id = $1
|
|
77429
|
+
AND type IN ('clarification_response', 'approval_response')
|
|
77430
|
+
AND metadata ? 'resolves_comment_id'
|
|
77431
|
+
)`,
|
|
77432
|
+
[taskId]
|
|
77433
|
+
);
|
|
77434
|
+
return Number(result.rows[0]?.count ?? 0);
|
|
77435
|
+
}
|
|
77436
|
+
|
|
77358
77437
|
// src/workers/worktree-manager.ts
|
|
77359
77438
|
import { execFileSync } from "node:child_process";
|
|
77360
77439
|
import {
|
|
@@ -77590,7 +77669,15 @@ var CANONICAL_WORKER_SYSTEM_PROMPT = `You are a Neura worker \u2014 a capable en
|
|
|
77590
77669
|
|
|
77591
77670
|
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
77671
|
|
|
77672
|
+
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:
|
|
77673
|
+
|
|
77674
|
+
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'\`.
|
|
77675
|
+
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.
|
|
77676
|
+
|
|
77677
|
+
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.
|
|
77678
|
+
|
|
77593
77679
|
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:
|
|
77680
|
+
- Writing to the user's home directory, Desktop, Documents, or any absolute path outside cwd
|
|
77594
77681
|
- Deleting files the user owns
|
|
77595
77682
|
- Force-pushing branches, rewriting git history
|
|
77596
77683
|
- Sending email, SMS, or other external messages
|
|
@@ -78030,6 +78117,31 @@ var AgentWorker = class {
|
|
|
78030
78117
|
});
|
|
78031
78118
|
}
|
|
78032
78119
|
}
|
|
78120
|
+
if ((status === "failed" || status === "crashed") && result.error) {
|
|
78121
|
+
try {
|
|
78122
|
+
const { reason, detail } = result.error;
|
|
78123
|
+
const MAX_DETAIL_BYTES = 3e4;
|
|
78124
|
+
const safeDetail = detail ? detail.slice(0, MAX_DETAIL_BYTES) : "";
|
|
78125
|
+
const content = safeDetail ? `${reason}: ${safeDetail}` : reason;
|
|
78126
|
+
await insertComment(this.db, {
|
|
78127
|
+
taskId,
|
|
78128
|
+
type: "error",
|
|
78129
|
+
author: "system",
|
|
78130
|
+
content,
|
|
78131
|
+
metadata: {
|
|
78132
|
+
workerId,
|
|
78133
|
+
workerStatus: status,
|
|
78134
|
+
reason
|
|
78135
|
+
}
|
|
78136
|
+
});
|
|
78137
|
+
} catch (err) {
|
|
78138
|
+
log23.warn("failed to persist error comment on task", {
|
|
78139
|
+
workerId,
|
|
78140
|
+
taskId,
|
|
78141
|
+
err: String(err)
|
|
78142
|
+
});
|
|
78143
|
+
}
|
|
78144
|
+
}
|
|
78033
78145
|
}
|
|
78034
78146
|
try {
|
|
78035
78147
|
if (status === "completed" || status === "cancelled") {
|
|
@@ -78195,53 +78307,6 @@ function buildClarificationTool(workerId, bridge) {
|
|
|
78195
78307
|
return tool;
|
|
78196
78308
|
}
|
|
78197
78309
|
|
|
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
78310
|
// src/tools/task-update-handler.ts
|
|
78246
78311
|
init_work_item_queries();
|
|
78247
78312
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "cancelled", "failed"]);
|
|
@@ -78672,6 +78737,21 @@ Reason: ${params.rationale}` : params.action;
|
|
|
78672
78737
|
return tools;
|
|
78673
78738
|
}
|
|
78674
78739
|
|
|
78740
|
+
// src/server/lifecycle.ts
|
|
78741
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
78742
|
+
|
|
78743
|
+
// src/workers/auth-bridge.ts
|
|
78744
|
+
function seedAuthStorageFromConfig(authStorage, providers) {
|
|
78745
|
+
let count = 0;
|
|
78746
|
+
for (const [providerId, creds] of Object.entries(providers)) {
|
|
78747
|
+
if (creds?.apiKey) {
|
|
78748
|
+
authStorage.setRuntimeApiKey(providerId, creds.apiKey);
|
|
78749
|
+
count++;
|
|
78750
|
+
}
|
|
78751
|
+
}
|
|
78752
|
+
return count;
|
|
78753
|
+
}
|
|
78754
|
+
|
|
78675
78755
|
// src/tools/vision-tools.ts
|
|
78676
78756
|
var visionToolDefs = [
|
|
78677
78757
|
{
|
|
@@ -79266,6 +79346,9 @@ async function initServices() {
|
|
|
79266
79346
|
return candidates.slice(0, limit2);
|
|
79267
79347
|
},
|
|
79268
79348
|
getTask: (idOrTitle) => store.getWorkItem(idOrTitle),
|
|
79349
|
+
listTaskComments: async (taskId, options) => {
|
|
79350
|
+
return listComments(rawDb, { taskId, limit: options?.limit });
|
|
79351
|
+
},
|
|
79269
79352
|
updateTask: async (idOrTitle, payload) => {
|
|
79270
79353
|
const current = await store.getWorkItem(idOrTitle);
|
|
79271
79354
|
if (!current) return null;
|
|
@@ -79332,6 +79415,9 @@ async function initServices() {
|
|
|
79332
79415
|
`${wProvider}/${wModel} model not registered in pi-ai. Check config.routing.worker or verify pi-ai supports this provider.`
|
|
79333
79416
|
);
|
|
79334
79417
|
}
|
|
79418
|
+
const authStorage = AuthStorage.create(join4(agentDir, "auth.json"));
|
|
79419
|
+
const seededProviders = seedAuthStorageFromConfig(authStorage, config2.providers);
|
|
79420
|
+
log29.info("seeded pi auth storage from config", { count: seededProviders });
|
|
79335
79421
|
const piRuntime = new PiRuntime({
|
|
79336
79422
|
model,
|
|
79337
79423
|
thinkingLevel: "low",
|
|
@@ -79340,7 +79426,8 @@ async function initServices() {
|
|
|
79340
79426
|
sessionDir,
|
|
79341
79427
|
buildTools,
|
|
79342
79428
|
voiceFanoutBridge,
|
|
79343
|
-
skillRegistry
|
|
79429
|
+
skillRegistry,
|
|
79430
|
+
authStorage
|
|
79344
79431
|
});
|
|
79345
79432
|
agentWorker = new AgentWorker({ db: rawDb, runtime: piRuntime });
|
|
79346
79433
|
await agentWorker.recoverFromCrash();
|
|
@@ -98971,6 +99058,11 @@ function attachWebSocket(httpServer2, services2) {
|
|
|
98971
99058
|
return candidates.slice(0, limit2);
|
|
98972
99059
|
},
|
|
98973
99060
|
getTask: (idOrTitle) => resolveTask(store, idOrTitle),
|
|
99061
|
+
listTaskComments: async (taskId, options) => {
|
|
99062
|
+
const db = store.getRawDb?.();
|
|
99063
|
+
if (!db) throw new Error("store does not expose a raw PGlite handle");
|
|
99064
|
+
return listComments(db, { taskId, limit: options?.limit });
|
|
99065
|
+
},
|
|
98974
99066
|
updateTask: async (idOrTitle, payload) => {
|
|
98975
99067
|
const current = await resolveTask(store, idOrTitle);
|
|
98976
99068
|
if (!current) return null;
|