@themoltnet/pi-extension 0.8.0 → 0.9.0
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/dist/index.d.ts +22 -0
- package/dist/index.js +79 -8
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -281,6 +281,8 @@ declare const Task: TObject< {
|
|
|
281
281
|
cancelledByHumanId: TUnion<[TString, TNull]>;
|
|
282
282
|
cancelReason: TUnion<[TString, TNull]>;
|
|
283
283
|
maxAttempts: TNumber;
|
|
284
|
+
dispatchTimeoutSec: TUnion<[TInteger, TNull]>;
|
|
285
|
+
runningTimeoutSec: TUnion<[TInteger, TNull]>;
|
|
284
286
|
}>;
|
|
285
287
|
|
|
286
288
|
declare type Task = Static<typeof Task>;
|
|
@@ -364,6 +366,26 @@ declare interface TaskReporter {
|
|
|
364
366
|
finalize(usage: TaskUsage): Promise<void>;
|
|
365
367
|
/** Flush buffers + release resources. Called once. Idempotent. */
|
|
366
368
|
close(): Promise<void>;
|
|
369
|
+
/**
|
|
370
|
+
* Signal that aborts when the task is cancelled by the imposer (or a
|
|
371
|
+
* diary writer) while the executor is running. `ApiTaskReporter`
|
|
372
|
+
* aborts this on the next heartbeat that observes `cancelled: true`
|
|
373
|
+
* in the response (#938). Local reporters (`StdoutReporter`,
|
|
374
|
+
* `JsonlTaskReporter`) never abort — there's no remote cancel
|
|
375
|
+
* channel for `FileTaskSource`.
|
|
376
|
+
*
|
|
377
|
+
* Executors should pass this signal into long-running work
|
|
378
|
+
* (LLM calls, sandbox execution, file ops) and surface a
|
|
379
|
+
* `status: 'cancelled'` output when it fires. The runtime also
|
|
380
|
+
* checks the signal post-execute and converts any output to
|
|
381
|
+
* `cancelled` if the executor returned without honoring it.
|
|
382
|
+
*/
|
|
383
|
+
readonly cancelSignal: AbortSignal;
|
|
384
|
+
/**
|
|
385
|
+
* The reason supplied to `/tasks/:id/cancel` by the canceller, if
|
|
386
|
+
* cancellation has been observed. Null until `cancelSignal` aborts.
|
|
387
|
+
*/
|
|
388
|
+
readonly cancelReason: string | null;
|
|
367
389
|
}
|
|
368
390
|
|
|
369
391
|
/**
|
package/dist/index.js
CHANGED
|
@@ -9338,7 +9338,15 @@ Type$1.Object({
|
|
|
9338
9338
|
cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
|
|
9339
9339
|
cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
|
|
9340
9340
|
cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
|
|
9341
|
-
maxAttempts: Type$1.Number({ minimum: 1 })
|
|
9341
|
+
maxAttempts: Type$1.Number({ minimum: 1 }),
|
|
9342
|
+
dispatchTimeoutSec: Type$1.Union([Type$1.Integer({
|
|
9343
|
+
minimum: 1,
|
|
9344
|
+
maximum: 86400
|
|
9345
|
+
}), Type$1.Null()]),
|
|
9346
|
+
runningTimeoutSec: Type$1.Union([Type$1.Integer({
|
|
9347
|
+
minimum: 1,
|
|
9348
|
+
maximum: 86400
|
|
9349
|
+
}), Type$1.Null()])
|
|
9342
9350
|
}, {
|
|
9343
9351
|
$id: "Task",
|
|
9344
9352
|
additionalProperties: false
|
|
@@ -9499,11 +9507,7 @@ function buildAssessBriefPrompt(input, ctx) {
|
|
|
9499
9507
|
*/
|
|
9500
9508
|
function buildCuratePackPrompt(input, ctx) {
|
|
9501
9509
|
const { diaryId, taskPrompt, entryTypes, tagFilters, tokenBudget, recipe } = input;
|
|
9502
|
-
const
|
|
9503
|
-
"semantic",
|
|
9504
|
-
"episodic",
|
|
9505
|
-
"procedural"
|
|
9506
|
-
];
|
|
9510
|
+
const entryTypesPinned = Boolean(entryTypes);
|
|
9507
9511
|
const resolvedRecipe = recipe ?? "topic-focused-v1";
|
|
9508
9512
|
const includeLine = tagFilters?.include?.length ? `- Hard include (ALL must be present on an entry): ${tagFilters.include.map((t) => `\`${t}\``).join(", ")}` : null;
|
|
9509
9513
|
const excludeLine = tagFilters?.exclude?.length ? `- Hard exclude (drop if ANY present): ${tagFilters.exclude.map((t) => `\`${t}\``).join(", ")}` : null;
|
|
@@ -9532,7 +9536,16 @@ function buildCuratePackPrompt(input, ctx) {
|
|
|
9532
9536
|
"",
|
|
9533
9537
|
"## Constraints",
|
|
9534
9538
|
"",
|
|
9535
|
-
`- Entry types
|
|
9539
|
+
entryTypesPinned ? `- Entry types pinned by imposer (do not widen): ${entryTypes.map((t) => `\`${t}\``).join(", ")}` : "- Entry types: **you choose**. The diary contains three kinds:",
|
|
9540
|
+
entryTypesPinned ? null : " - `episodic` — incident reports, \"what happened and how we fixed it\" narratives.",
|
|
9541
|
+
entryTypesPinned ? null : " - `semantic` — durable decisions, patterns, design rationale.",
|
|
9542
|
+
entryTypesPinned ? null : " - `procedural` — commit audit trails / changelog-style provenance.",
|
|
9543
|
+
entryTypesPinned ? null : " Pick the subset that fits the prompt. For \"failures and workarounds\"",
|
|
9544
|
+
entryTypesPinned ? null : " or \"decisions we made\" you generally do NOT want `procedural` — those",
|
|
9545
|
+
entryTypesPinned ? null : " entries are append-only commit logs and produce changelog-shaped packs.",
|
|
9546
|
+
entryTypesPinned ? null : " Include `procedural` only when the prompt explicitly asks for changelog-",
|
|
9547
|
+
entryTypesPinned ? null : " style content (e.g., \"what shipped this week\"). State your choice",
|
|
9548
|
+
entryTypesPinned ? null : " briefly in the final `summary`.",
|
|
9536
9549
|
`- Recipe tag: \`${resolvedRecipe}\` (recorded on pack params)`,
|
|
9537
9550
|
tokenBudget ? `- Token budget (soft cap on final pack): ${tokenBudget}. Pick entry count so the pack fits — estimate ~300 tok/entry as a starting heuristic, tighten after inspecting actual content lengths.` : "- No token budget — size the pack to match the prompt, not an arbitrary target.",
|
|
9538
9551
|
includeLine,
|
|
@@ -10037,6 +10050,20 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10037
10050
|
const attemptN = claimedTask.attemptN;
|
|
10038
10051
|
const startTime = Date.now();
|
|
10039
10052
|
const mountPath = opts.mountPath ?? process.cwd();
|
|
10053
|
+
if (reporter.cancelSignal.aborted) return {
|
|
10054
|
+
taskId: task.id,
|
|
10055
|
+
attemptN,
|
|
10056
|
+
status: "cancelled",
|
|
10057
|
+
output: null,
|
|
10058
|
+
outputCid: null,
|
|
10059
|
+
usage: emptyUsage(opts.provider, opts.model),
|
|
10060
|
+
durationMs: Date.now() - startTime,
|
|
10061
|
+
error: {
|
|
10062
|
+
code: "task_cancelled",
|
|
10063
|
+
message: reporter.cancelReason ?? "Task cancelled before pi executor started.",
|
|
10064
|
+
retryable: false
|
|
10065
|
+
}
|
|
10066
|
+
};
|
|
10040
10067
|
const checkpointPath = opts.checkpointPath ?? await ensureSnapshot({
|
|
10041
10068
|
config: opts.sandboxConfig?.snapshot,
|
|
10042
10069
|
onProgress: opts.onSnapshotProgress ?? ((m) => {
|
|
@@ -10065,6 +10092,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10065
10092
|
let reporterOpen = false;
|
|
10066
10093
|
let session = null;
|
|
10067
10094
|
const finalUsage = emptyUsage(opts.provider, opts.model);
|
|
10095
|
+
let cancelListener = null;
|
|
10068
10096
|
const makeFailedOutput = (code, message, usage = finalUsage) => ({
|
|
10069
10097
|
taskId: task.id,
|
|
10070
10098
|
attemptN,
|
|
@@ -10165,6 +10193,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10165
10193
|
let assistantText = "";
|
|
10166
10194
|
let reporterError = null;
|
|
10167
10195
|
const usage = finalUsage;
|
|
10196
|
+
cancelListener = wireSessionAbort(reporter.cancelSignal, session);
|
|
10168
10197
|
const recordingPromise = [];
|
|
10169
10198
|
const track = (p) => {
|
|
10170
10199
|
recordingPromise.push(p.catch((err) => {
|
|
@@ -10220,10 +10249,11 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10220
10249
|
});
|
|
10221
10250
|
}
|
|
10222
10251
|
await Promise.all(recordingPromise);
|
|
10252
|
+
const cancelled = reporter.cancelSignal.aborted;
|
|
10223
10253
|
let parsedOutput = null;
|
|
10224
10254
|
let parsedOutputCid = null;
|
|
10225
10255
|
let parseError = null;
|
|
10226
|
-
if (!runError && !llmAbort) {
|
|
10256
|
+
if (!runError && !llmAbort && !cancelled) {
|
|
10227
10257
|
const parsed = await parseStructuredTaskOutput(assistantText, task.taskType);
|
|
10228
10258
|
parsedOutput = parsed.output;
|
|
10229
10259
|
parsedOutputCid = parsed.outputCid;
|
|
@@ -10233,6 +10263,20 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10233
10263
|
phase: "output_validation"
|
|
10234
10264
|
});
|
|
10235
10265
|
}
|
|
10266
|
+
if (cancelled) return {
|
|
10267
|
+
taskId: task.id,
|
|
10268
|
+
attemptN,
|
|
10269
|
+
status: "cancelled",
|
|
10270
|
+
output: null,
|
|
10271
|
+
outputCid: null,
|
|
10272
|
+
usage,
|
|
10273
|
+
durationMs: Date.now() - startTime,
|
|
10274
|
+
error: {
|
|
10275
|
+
code: "task_cancelled",
|
|
10276
|
+
message: reporter.cancelReason ?? "Task cancelled by imposer while pi session was running.",
|
|
10277
|
+
retryable: false
|
|
10278
|
+
}
|
|
10279
|
+
};
|
|
10236
10280
|
const status = runError || llmAbort || parseError || reporterError ? "failed" : "completed";
|
|
10237
10281
|
const errorCode = runError?.code ?? parseError?.code ?? reporterError?.code ?? (llmAbort ? "llm_api_error" : void 0);
|
|
10238
10282
|
const errorMessage = runError?.message ?? parseError?.message ?? reporterError?.message ?? (llmAbort ? "LLM API error during turn" : void 0);
|
|
@@ -10253,6 +10297,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
10253
10297
|
} catch (err) {
|
|
10254
10298
|
return makeFailedOutput("executor_unexpected_error", err instanceof Error ? err.message : String(err));
|
|
10255
10299
|
} finally {
|
|
10300
|
+
if (cancelListener) reporter.cancelSignal.removeEventListener("abort", cancelListener);
|
|
10256
10301
|
if (session) try {
|
|
10257
10302
|
session.dispose();
|
|
10258
10303
|
} catch {}
|
|
@@ -10282,6 +10327,32 @@ function emptyUsage(provider, model) {
|
|
|
10282
10327
|
};
|
|
10283
10328
|
}
|
|
10284
10329
|
/**
|
|
10330
|
+
* Wire `cancelSignal` → `session.abort()`. Returns the listener so the
|
|
10331
|
+
* caller can remove it on cleanup. If the signal is already aborted at
|
|
10332
|
+
* call time (cancel landed between session creation and wiring), fires
|
|
10333
|
+
* abort synchronously instead of waiting for an `'abort'` event that
|
|
10334
|
+
* already happened.
|
|
10335
|
+
*
|
|
10336
|
+
* Exported for unit testing without a booted Gondolin VM. The double-
|
|
10337
|
+
* invocation guard handles both the rare "fire from constructor + later
|
|
10338
|
+
* event" race and the (in-practice idempotent) double-call into
|
|
10339
|
+
* `session.abort()`.
|
|
10340
|
+
*/
|
|
10341
|
+
function wireSessionAbort(cancelSignal, session) {
|
|
10342
|
+
let abortInvoked = false;
|
|
10343
|
+
const listener = () => {
|
|
10344
|
+
if (abortInvoked) return;
|
|
10345
|
+
abortInvoked = true;
|
|
10346
|
+
session.abort().catch((err) => {
|
|
10347
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10348
|
+
process.stderr.write(`[pi] session.abort() failed: ${message}\n`);
|
|
10349
|
+
});
|
|
10350
|
+
};
|
|
10351
|
+
if (cancelSignal.aborted) listener();
|
|
10352
|
+
else cancelSignal.addEventListener("abort", listener, { once: true });
|
|
10353
|
+
return listener;
|
|
10354
|
+
}
|
|
10355
|
+
/**
|
|
10285
10356
|
* Cap oversized tool-result payloads before embedding them in a
|
|
10286
10357
|
* `task_messages.payload` row. Bodies above 4 KiB are replaced with a
|
|
10287
10358
|
* `{ truncated, original_size }` marker so the JSONL/DB size stays bounded.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@earendil-works/gondolin": "^0.7.0",
|
|
32
32
|
"@opentelemetry/api": "^1.9.0",
|
|
33
33
|
"@sinclair/typebox": "^0.34.0",
|
|
34
|
-
"@themoltnet/agent-runtime": "0.
|
|
34
|
+
"@themoltnet/agent-runtime": "0.5.0",
|
|
35
35
|
"@themoltnet/sdk": "0.96.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|