@themoltnet/pi-extension 0.7.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 CHANGED
@@ -272,6 +272,7 @@ declare const Task: TObject< {
272
272
  imposedByAgentId: TUnion<[TString, TNull]>;
273
273
  imposedByHumanId: TUnion<[TString, TNull]>;
274
274
  acceptedAttemptN: TUnion<[TNumber, TNull]>;
275
+ requiredExecutorTrustLevel: TUnion<[TLiteral<"selfDeclared">, TLiteral<"agentSigned">, TLiteral<"releaseVerifiedTool">, TLiteral<"sandboxAttested">]>;
275
276
  status: TUnion<[TLiteral<"queued">, TLiteral<"dispatched">, TLiteral<"running">, TLiteral<"completed">, TLiteral<"failed">, TLiteral<"cancelled">, TLiteral<"expired">]>;
276
277
  queuedAt: TString;
277
278
  completedAt: TUnion<[TString, TNull]>;
@@ -280,6 +281,8 @@ declare const Task: TObject< {
280
281
  cancelledByHumanId: TUnion<[TString, TNull]>;
281
282
  cancelReason: TUnion<[TString, TNull]>;
282
283
  maxAttempts: TNumber;
284
+ dispatchTimeoutSec: TUnion<[TInteger, TNull]>;
285
+ runningTimeoutSec: TUnion<[TInteger, TNull]>;
283
286
  }>;
284
287
 
285
288
  declare type Task = Static<typeof Task>;
@@ -363,6 +366,26 @@ declare interface TaskReporter {
363
366
  finalize(usage: TaskUsage): Promise<void>;
364
367
  /** Flush buffers + release resources. Called once. Idempotent. */
365
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;
366
389
  }
367
390
 
368
391
  /**
package/dist/index.js CHANGED
@@ -2536,6 +2536,7 @@ function createDiaryGrantsNamespace(context) {
2536
2536
  }
2537
2537
  };
2538
2538
  }
2539
+ new TextEncoder();
2539
2540
  //#endregion
2540
2541
  //#region ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/utils.js
2541
2542
  /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
@@ -4266,6 +4267,13 @@ etc.sha512Sync = (...m) => {
4266
4267
  return hash.digest();
4267
4268
  };
4268
4269
  //#endregion
4270
+ //#region ../crypto-service/src/executor-attestation.ts
4271
+ etc.sha512Sync = (...m) => {
4272
+ const hash = createHash("sha512");
4273
+ m.forEach((msg) => hash.update(msg));
4274
+ return hash.digest();
4275
+ };
4276
+ //#endregion
4269
4277
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
4270
4278
  var textEncoder$1 = new TextEncoder();
4271
4279
  new TextDecoder();
@@ -9224,6 +9232,12 @@ var TaskAttemptStatus = Type$1.Union([
9224
9232
  Type$1.Literal("cancelled"),
9225
9233
  Type$1.Literal("timed_out")
9226
9234
  ], { $id: "TaskAttemptStatus" });
9235
+ var ExecutorTrustLevel = Type$1.Union([
9236
+ Type$1.Literal("selfDeclared"),
9237
+ Type$1.Literal("agentSigned"),
9238
+ Type$1.Literal("releaseVerifiedTool"),
9239
+ Type$1.Literal("sandboxAttested")
9240
+ ], { $id: "ExecutorTrustLevel" });
9227
9241
  var OutputKind = Type$1.Union([Type$1.Literal("artifact"), Type$1.Literal("judgment")], { $id: "OutputKind" });
9228
9242
  var TaskMessageKind = Type$1.Union([
9229
9243
  Type$1.Literal("text_delta"),
@@ -9316,6 +9330,7 @@ Type$1.Object({
9316
9330
  imposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9317
9331
  imposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9318
9332
  acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
9333
+ requiredExecutorTrustLevel: ExecutorTrustLevel,
9319
9334
  status: TaskStatus,
9320
9335
  queuedAt: IsoTimestamp,
9321
9336
  completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
@@ -9323,7 +9338,15 @@ Type$1.Object({
9323
9338
  cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9324
9339
  cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9325
9340
  cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
9326
- 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()])
9327
9350
  }, {
9328
9351
  $id: "Task",
9329
9352
  additionalProperties: false
@@ -9339,6 +9362,10 @@ Type$1.Object({
9339
9362
  status: TaskAttemptStatus,
9340
9363
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9341
9364
  outputCid: Type$1.Union([Cid, Type$1.Null()]),
9365
+ claimedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
9366
+ claimedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9367
+ completedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
9368
+ completedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9342
9369
  error: Type$1.Union([TaskError, Type$1.Null()]),
9343
9370
  usage: Type$1.Union([TaskUsage, Type$1.Null()]),
9344
9371
  contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
@@ -9480,11 +9507,7 @@ function buildAssessBriefPrompt(input, ctx) {
9480
9507
  */
9481
9508
  function buildCuratePackPrompt(input, ctx) {
9482
9509
  const { diaryId, taskPrompt, entryTypes, tagFilters, tokenBudget, recipe } = input;
9483
- const effectiveEntryTypes = entryTypes ?? [
9484
- "semantic",
9485
- "episodic",
9486
- "procedural"
9487
- ];
9510
+ const entryTypesPinned = Boolean(entryTypes);
9488
9511
  const resolvedRecipe = recipe ?? "topic-focused-v1";
9489
9512
  const includeLine = tagFilters?.include?.length ? `- Hard include (ALL must be present on an entry): ${tagFilters.include.map((t) => `\`${t}\``).join(", ")}` : null;
9490
9513
  const excludeLine = tagFilters?.exclude?.length ? `- Hard exclude (drop if ANY present): ${tagFilters.exclude.map((t) => `\`${t}\``).join(", ")}` : null;
@@ -9513,7 +9536,16 @@ function buildCuratePackPrompt(input, ctx) {
9513
9536
  "",
9514
9537
  "## Constraints",
9515
9538
  "",
9516
- `- Entry types in play: ${effectiveEntryTypes.map((t) => `\`${t}\``).join(", ")}`,
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`.",
9517
9549
  `- Recipe tag: \`${resolvedRecipe}\` (recorded on pack params)`,
9518
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.",
9519
9551
  includeLine,
@@ -10018,6 +10050,20 @@ async function executePiTask(claimedTask, reporter, opts) {
10018
10050
  const attemptN = claimedTask.attemptN;
10019
10051
  const startTime = Date.now();
10020
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
+ };
10021
10067
  const checkpointPath = opts.checkpointPath ?? await ensureSnapshot({
10022
10068
  config: opts.sandboxConfig?.snapshot,
10023
10069
  onProgress: opts.onSnapshotProgress ?? ((m) => {
@@ -10046,6 +10092,7 @@ async function executePiTask(claimedTask, reporter, opts) {
10046
10092
  let reporterOpen = false;
10047
10093
  let session = null;
10048
10094
  const finalUsage = emptyUsage(opts.provider, opts.model);
10095
+ let cancelListener = null;
10049
10096
  const makeFailedOutput = (code, message, usage = finalUsage) => ({
10050
10097
  taskId: task.id,
10051
10098
  attemptN,
@@ -10146,6 +10193,7 @@ async function executePiTask(claimedTask, reporter, opts) {
10146
10193
  let assistantText = "";
10147
10194
  let reporterError = null;
10148
10195
  const usage = finalUsage;
10196
+ cancelListener = wireSessionAbort(reporter.cancelSignal, session);
10149
10197
  const recordingPromise = [];
10150
10198
  const track = (p) => {
10151
10199
  recordingPromise.push(p.catch((err) => {
@@ -10201,10 +10249,11 @@ async function executePiTask(claimedTask, reporter, opts) {
10201
10249
  });
10202
10250
  }
10203
10251
  await Promise.all(recordingPromise);
10252
+ const cancelled = reporter.cancelSignal.aborted;
10204
10253
  let parsedOutput = null;
10205
10254
  let parsedOutputCid = null;
10206
10255
  let parseError = null;
10207
- if (!runError && !llmAbort) {
10256
+ if (!runError && !llmAbort && !cancelled) {
10208
10257
  const parsed = await parseStructuredTaskOutput(assistantText, task.taskType);
10209
10258
  parsedOutput = parsed.output;
10210
10259
  parsedOutputCid = parsed.outputCid;
@@ -10214,6 +10263,20 @@ async function executePiTask(claimedTask, reporter, opts) {
10214
10263
  phase: "output_validation"
10215
10264
  });
10216
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
+ };
10217
10280
  const status = runError || llmAbort || parseError || reporterError ? "failed" : "completed";
10218
10281
  const errorCode = runError?.code ?? parseError?.code ?? reporterError?.code ?? (llmAbort ? "llm_api_error" : void 0);
10219
10282
  const errorMessage = runError?.message ?? parseError?.message ?? reporterError?.message ?? (llmAbort ? "LLM API error during turn" : void 0);
@@ -10234,6 +10297,7 @@ async function executePiTask(claimedTask, reporter, opts) {
10234
10297
  } catch (err) {
10235
10298
  return makeFailedOutput("executor_unexpected_error", err instanceof Error ? err.message : String(err));
10236
10299
  } finally {
10300
+ if (cancelListener) reporter.cancelSignal.removeEventListener("abort", cancelListener);
10237
10301
  if (session) try {
10238
10302
  session.dispose();
10239
10303
  } catch {}
@@ -10263,6 +10327,32 @@ function emptyUsage(provider, model) {
10263
10327
  };
10264
10328
  }
10265
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
+ /**
10266
10356
  * Cap oversized tool-result payloads before embedding them in a
10267
10357
  * `task_messages.payload` row. Bodies above 4 KiB are replaced with a
10268
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.7.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,8 +31,8 @@
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.3.0",
35
- "@themoltnet/sdk": "0.95.0"
34
+ "@themoltnet/agent-runtime": "0.5.0",
35
+ "@themoltnet/sdk": "0.96.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@mariozechner/pi-coding-agent": ">=0.67.0",