@themoltnet/pi-extension 0.30.0 → 0.31.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
@@ -145,6 +145,14 @@ export declare function createMoltNetTools(config: MoltNetToolsConfig): ToolDefi
145
145
 
146
146
  export declare function createPiOtelExtension(options?: PiOtelOptions): (pi: ExtensionAPI) => void;
147
147
 
148
+ export declare function createPiRetryTriage(options: {
149
+ model: Model<Api>;
150
+ thinkingLevel?: PiRetryTriageThinkingLevel | null;
151
+ piAgentDir: string;
152
+ timeoutMs?: number;
153
+ cwd?: string;
154
+ }): PiRetryTriage;
155
+
148
156
  /**
149
157
  * Factory that builds a pi-specific `executeTask` function suitable for
150
158
  * injection into `AgentRuntime`. The returned function caches the resolved
@@ -524,6 +532,8 @@ declare interface MoltNetToolsConfig {
524
532
  getTaskContext?(): MoltNetTaskContext | null;
525
533
  }
526
534
 
535
+ export declare function normalizeRetryTriageResult(value: unknown): PiRetryTriageResult;
536
+
527
537
  export declare interface PiOtelOptions {
528
538
  /** Agent name for `gen_ai.agent.name` on the root span. */
529
539
  agentName?: string;
@@ -535,6 +545,38 @@ export declare interface PiOtelOptions {
535
545
  spanAttributes?: Record<string, string | number | boolean>;
536
546
  }
537
547
 
548
+ export declare type PiRetryTriage = (input: PiRetryTriageInput) => Promise<PiRetryTriageResult>;
549
+
550
+ export declare type PiRetryTriageConfidence = RetryTriageConfidence;
551
+
552
+ export declare type PiRetryTriageDecision = RetryTriageDecision;
553
+
554
+ export declare interface PiRetryTriageInput {
555
+ task: {
556
+ id: string;
557
+ taskType: string;
558
+ teamId: string;
559
+ input: unknown;
560
+ };
561
+ attemptN: number;
562
+ maxAttempts?: number | null;
563
+ remainingAttempts?: number | null;
564
+ error: unknown;
565
+ recentMessages?: {
566
+ timestamp: string;
567
+ kind: string;
568
+ payload: unknown;
569
+ }[];
570
+ }
571
+
572
+ export declare interface PiRetryTriageResult {
573
+ decision: RetryTriageDecision;
574
+ confidence: RetryTriageConfidence;
575
+ reason: string;
576
+ }
577
+
578
+ export declare type PiRetryTriageThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
579
+
538
580
  export declare interface PiSessionPersistencePlan {
539
581
  sessionDir: string;
540
582
  forkFromSessionPath?: string | null;
@@ -605,6 +647,8 @@ declare interface PiWorkspaceSeedPlan {
605
647
  source: 'producer';
606
648
  }
607
649
 
650
+ export declare function redactRetryTriageSecrets(value: string): string;
651
+
608
652
  export declare function resolveTaskWorktreePath(mainRepo: string, workspaceId: string): string;
609
653
 
610
654
  export declare interface ResumeCommand {
@@ -635,6 +679,10 @@ declare interface ResumeCommandWhen {
635
679
  */
636
680
  export declare function resumeVm(config: VmConfig): Promise<ManagedVm>;
637
681
 
682
+ export declare type RetryTriageConfidence = 'low' | 'medium' | 'high';
683
+
684
+ export declare type RetryTriageDecision = 'retry' | 'do_not_retry';
685
+
638
686
  export declare interface SandboxConfig {
639
687
  /** Snapshot build settings. */
640
688
  snapshot?: {
@@ -853,6 +901,12 @@ declare const TaskOutput: Type.TObject<{
853
901
  message: Type.TString;
854
902
  stack: Type.TOptional<Type.TString>;
855
903
  retryable: Type.TOptional<Type.TBoolean>;
904
+ retry: Type.TOptional<Type.TObject<{
905
+ source: Type.TUnion<[Type.TLiteral<"explicit">, Type.TLiteral<"deterministic">, Type.TLiteral<"attempts_exhausted">, Type.TLiteral<"triage">, Type.TLiteral<"triage_failed">]>;
906
+ decision: Type.TOptional<Type.TUnion<[Type.TLiteral<"retry">, Type.TLiteral<"do_not_retry">]>>;
907
+ confidence: Type.TOptional<Type.TUnion<[Type.TLiteral<"low">, Type.TLiteral<"medium">, Type.TLiteral<"high">]>>;
908
+ reason: Type.TOptional<Type.TString>;
909
+ }>>;
856
910
  }>>;
857
911
  contentSignature: Type.TOptional<Type.TString>;
858
912
  }>;
package/dist/index.js CHANGED
@@ -1159,7 +1159,7 @@ var listDiaryPacks = (options) => (options.client ?? client).get({
1159
1159
  ...options
1160
1160
  });
1161
1161
  /**
1162
- * Create and persist a custom context pack from an explicit entry selection.
1162
+ * Create and persist a custom context pack from an explicit entry selection. Returns 409 if any selected entry is flagged as a prompt-injection risk; the response lists the flagged entries. Set `force: true` to override and persist anyway.
1163
1163
  */
1164
1164
  var createDiaryCustomPack = (options) => (options.client ?? client).post({
1165
1165
  security: [
@@ -2159,7 +2159,7 @@ var completeTask = (options) => (options.client ?? client).post({
2159
2159
  /**
2160
2160
  * Mark an attempt as failed with error details.
2161
2161
  */
2162
- var failTask = (options) => (options.client ?? client).post({
2162
+ var failTaskAttempt = (options) => (options.client ?? client).post({
2163
2163
  security: [
2164
2164
  {
2165
2165
  scheme: "bearer",
@@ -14019,6 +14019,27 @@ var TaskUsage = _Object_({
14019
14019
  $id: "TaskUsage",
14020
14020
  additionalProperties: false
14021
14021
  });
14022
+ var TaskRetryDecision = Union([Literal("retry"), Literal("do_not_retry")]);
14023
+ var TaskRetryConfidence = Union([
14024
+ Literal("low"),
14025
+ Literal("medium"),
14026
+ Literal("high")
14027
+ ]);
14028
+ var TaskRetryInfo = _Object_({
14029
+ source: Union([
14030
+ Literal("explicit"),
14031
+ Literal("deterministic"),
14032
+ Literal("attempts_exhausted"),
14033
+ Literal("triage"),
14034
+ Literal("triage_failed")
14035
+ ]),
14036
+ decision: Optional(TaskRetryDecision),
14037
+ confidence: Optional(TaskRetryConfidence),
14038
+ reason: Optional(String$1())
14039
+ }, {
14040
+ $id: "TaskRetryInfo",
14041
+ additionalProperties: false
14042
+ });
14022
14043
  /**
14023
14044
  * Structured error returned from a failed attempt.
14024
14045
  */
@@ -14026,7 +14047,8 @@ var TaskError = _Object_({
14026
14047
  code: String$1(),
14027
14048
  message: String$1(),
14028
14049
  stack: Optional(String$1()),
14029
- retryable: Optional(Boolean$1())
14050
+ retryable: Optional(Boolean$1()),
14051
+ retry: Optional(TaskRetryInfo)
14030
14052
  }, {
14031
14053
  $id: "TaskError",
14032
14054
  additionalProperties: false
@@ -14992,8 +15014,8 @@ function createTasksNamespace(context) {
14992
15014
  body
14993
15015
  }));
14994
15016
  },
14995
- async fail(id, n, body) {
14996
- return unwrapResult(await failTask({
15017
+ async failAttempt(id, n, body) {
15018
+ return unwrapResult(await failTaskAttempt({
14997
15019
  client,
14998
15020
  auth,
14999
15021
  path: {
@@ -26342,6 +26364,167 @@ function describeToolErrorMessage(result) {
26342
26364
  }
26343
26365
  }
26344
26366
  //#endregion
26367
+ //#region src/runtime/retry-triage.ts
26368
+ var MAX_TRIAGE_JSON_CHARS = 12e3;
26369
+ var MAX_TRIAGE_FIELD_CHARS = 2e3;
26370
+ var REDACTED = "[redacted]";
26371
+ var SECRET_KEY_PATTERN = /(?:api[_-]?key|token|secret|password|passwd|credential|authorization|private[_-]?key|access[_-]?token|refresh[_-]?token)/i;
26372
+ function createPiRetryTriage(options) {
26373
+ return async (input) => {
26374
+ const cwd = options.cwd ?? process.cwd();
26375
+ const capture = createRetryTriageTool();
26376
+ const resourceLoader = new DefaultResourceLoader({
26377
+ cwd,
26378
+ agentDir: options.piAgentDir,
26379
+ appendSystemPrompt: [TRIAGE_SYSTEM_PROMPT],
26380
+ skillsOverride: () => ({
26381
+ skills: [],
26382
+ diagnostics: []
26383
+ })
26384
+ });
26385
+ await resourceLoader.reload();
26386
+ const sessionManager = SessionManager.inMemory(cwd);
26387
+ const created = await createAgentSession({
26388
+ agentDir: options.piAgentDir,
26389
+ cwd,
26390
+ model: options.model,
26391
+ thinkingLevel: options.thinkingLevel ?? void 0,
26392
+ customTools: [capture.tool],
26393
+ sessionManager,
26394
+ resourceLoader
26395
+ });
26396
+ await withTimeout(created.session.prompt(buildTriagePrompt(input)), options.timeoutMs ?? 3e4, () => created.session.abort());
26397
+ const result = capture.getCaptured();
26398
+ if (!result) throw new Error("Retry triage did not submit a decision");
26399
+ return normalizeRetryTriageResult(result);
26400
+ };
26401
+ }
26402
+ function createRetryTriageTool() {
26403
+ let captured = null;
26404
+ return {
26405
+ tool: defineTool({
26406
+ name: "submit_retry_triage",
26407
+ label: "Submit retry triage",
26408
+ description: "Submit the retry decision for a failed MoltNet task attempt.",
26409
+ parameters: {
26410
+ type: "object",
26411
+ additionalProperties: false,
26412
+ required: [
26413
+ "decision",
26414
+ "confidence",
26415
+ "reason"
26416
+ ],
26417
+ properties: {
26418
+ decision: {
26419
+ type: "string",
26420
+ enum: ["retry", "do_not_retry"]
26421
+ },
26422
+ confidence: {
26423
+ type: "string",
26424
+ enum: [
26425
+ "low",
26426
+ "medium",
26427
+ "high"
26428
+ ]
26429
+ },
26430
+ reason: {
26431
+ type: "string",
26432
+ minLength: 1
26433
+ }
26434
+ }
26435
+ },
26436
+ execute(_id, params) {
26437
+ captured = normalizeRetryTriageResult(params);
26438
+ return Promise.resolve({
26439
+ content: [{
26440
+ type: "text",
26441
+ text: "Retry triage captured."
26442
+ }],
26443
+ details: captured,
26444
+ terminate: true
26445
+ });
26446
+ }
26447
+ }),
26448
+ getCaptured: () => captured
26449
+ };
26450
+ }
26451
+ function normalizeRetryTriageResult(value) {
26452
+ const record = value && typeof value === "object" ? value : {};
26453
+ return {
26454
+ decision: record.decision === "retry" ? "retry" : "do_not_retry",
26455
+ confidence: record.confidence === "high" || record.confidence === "medium" ? record.confidence : "low",
26456
+ reason: typeof record.reason === "string" && record.reason.trim() ? record.reason.trim().slice(0, 500) : "retry triage did not provide a reason"
26457
+ };
26458
+ }
26459
+ function buildTriagePrompt(input) {
26460
+ const payload = {
26461
+ task: {
26462
+ id: input.task.id,
26463
+ type: input.task.taskType,
26464
+ teamId: input.task.teamId,
26465
+ input: prepareTriagePayload(input.task.input)
26466
+ },
26467
+ attempt: {
26468
+ attemptN: input.attemptN,
26469
+ maxAttempts: input.maxAttempts ?? null,
26470
+ remainingAttempts: input.remainingAttempts ?? null
26471
+ },
26472
+ error: prepareTriagePayload(input.error),
26473
+ recentMessages: prepareTriagePayload((input.recentMessages ?? []).slice(-12))
26474
+ };
26475
+ return [
26476
+ "Classify whether this failed task attempt should be retried.",
26477
+ "",
26478
+ "Retry only when a fresh attempt can plausibly recover without changing the task input.",
26479
+ "Do not retry for policy, validation, credentials, cancellation, model/config, or task-contract failures.",
26480
+ "Use confidence=low when evidence is weak; low confidence must choose do_not_retry.",
26481
+ "Call submit_retry_triage exactly once.",
26482
+ "",
26483
+ truncateString(JSON.stringify(payload, null, 2), MAX_TRIAGE_JSON_CHARS)
26484
+ ].join("\n");
26485
+ }
26486
+ function prepareTriagePayload(value) {
26487
+ return redactAndTruncate(value, []);
26488
+ }
26489
+ function redactAndTruncate(value, path) {
26490
+ const currentKey = path[path.length - 1] ?? "";
26491
+ if (SECRET_KEY_PATTERN.test(currentKey)) return REDACTED;
26492
+ if (typeof value === "string") return truncateString(redactRetryTriageSecrets(value), MAX_TRIAGE_FIELD_CHARS);
26493
+ if (Array.isArray(value)) return value.map((item, index) => redactAndTruncate(item, [...path, String(index)]));
26494
+ if (value && typeof value === "object") {
26495
+ const entries = Object.entries(value).map(([key, child]) => [key, redactAndTruncate(child, [...path, key])]);
26496
+ return Object.fromEntries(entries);
26497
+ }
26498
+ return value;
26499
+ }
26500
+ function redactRetryTriageSecrets(value) {
26501
+ return value.replace(/((?:bearer|basic)\s+)[a-z0-9._~+/=-]{16,}/gi, `$1${REDACTED}`).replace(/\bgh[pousr]_[a-z0-9_]{20,}\b/gi, REDACTED).replace(/\bsk-[a-z0-9_-]{16,}\b/gi, REDACTED).replace(/\beyJ[a-z0-9_-]{20,}\.[a-z0-9_-]{20,}\.[a-z0-9_-]{20,}\b/gi, REDACTED);
26502
+ }
26503
+ function truncateString(value, maxChars) {
26504
+ if (value.length <= maxChars) return value;
26505
+ return `${value.slice(0, maxChars)}...[truncated ${value.length - maxChars} chars]`;
26506
+ }
26507
+ var TRIAGE_SYSTEM_PROMPT = [
26508
+ "You are MoltNet retry triage.",
26509
+ "You classify one failed execution attempt, not the whole task.",
26510
+ "Return retry only for likely transient/runtime failures or clear evidence a new attempt can recover.",
26511
+ "The agent may have already tried local recovery; do not ask for more work."
26512
+ ].join("\n");
26513
+ async function withTimeout(promise, timeoutMs, onTimeout) {
26514
+ let timeout;
26515
+ const timeoutPromise = new Promise((_, reject) => {
26516
+ timeout = setTimeout(() => {
26517
+ Promise.resolve(onTimeout?.()).catch(() => {});
26518
+ reject(/* @__PURE__ */ new Error(`Retry triage timed out after ${timeoutMs}ms`));
26519
+ }, timeoutMs);
26520
+ });
26521
+ try {
26522
+ return await Promise.race([promise, timeoutPromise]);
26523
+ } finally {
26524
+ if (timeout) clearTimeout(timeout);
26525
+ }
26526
+ }
26527
+ //#endregion
26345
26528
  //#region src/index.ts
26346
26529
  /**
26347
26530
  * @themoltnet/pi-extension — MoltNet pi extension
@@ -26642,4 +26825,4 @@ function moltnetExtension(pi) {
26642
26825
  registerMoltnetReflectCommand(pi, state);
26643
26826
  }
26644
26827
  //#endregion
26645
- export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, resolveTaskWorktreePath, resumeVm, toGuestPath };
26828
+ export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveTaskWorktreePath, resumeVm, toGuestPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.30.0",
3
+ "version": "0.31.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
  "keywords": [
@@ -36,8 +36,8 @@
36
36
  "@earendil-works/gondolin": "^0.9.1",
37
37
  "@opentelemetry/api": "^1.9.0",
38
38
  "typebox": "^1.2.8",
39
- "@themoltnet/agent-runtime": "0.33.0",
40
- "@themoltnet/sdk": "0.116.0"
39
+ "@themoltnet/agent-runtime": "0.33.1",
40
+ "@themoltnet/sdk": "0.117.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": ">=0.74.0",