@themoltnet/pi-extension 0.20.2 → 0.22.1
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 +3 -1
- package/dist/index.js +285 -103
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -521,7 +521,7 @@ export declare interface PiTaskExecutionPlan {
|
|
|
521
521
|
sessionPersistence?: PiSessionPersistencePlan | null;
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
export declare type PiTaskExecutionPlanFactory = (claimedTask: ClaimedTask) => PiTaskExecutionPlan | null;
|
|
524
|
+
export declare type PiTaskExecutionPlanFactory = (claimedTask: ClaimedTask) => Promise<PiTaskExecutionPlan | null> | PiTaskExecutionPlan | null;
|
|
525
525
|
|
|
526
526
|
declare interface PiWorkspaceAttachmentPlan {
|
|
527
527
|
mountPath: string;
|
|
@@ -685,6 +685,8 @@ export declare type SubagentToolParameters = Static<typeof SubagentToolParameter
|
|
|
685
685
|
declare const Task: TObject< {
|
|
686
686
|
id: TString;
|
|
687
687
|
taskType: TString;
|
|
688
|
+
title: TUnion<[TString, TNull]>;
|
|
689
|
+
tags: TArray<TString>;
|
|
688
690
|
teamId: TString;
|
|
689
691
|
diaryId: TUnion<[TString, TNull]>;
|
|
690
692
|
outputKind: TUnion<[TLiteral<"artifact">, TLiteral<"judgment">]>;
|
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import { Type, getModel } from "@earendil-works/pi-ai";
|
|
|
11
11
|
import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, loadGuestAssets } from "@earendil-works/gondolin";
|
|
12
12
|
import { parseEnv } from "node:util";
|
|
13
13
|
import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
14
|
-
import { Value } from "@sinclair/typebox/value";
|
|
15
14
|
import { FormatRegistry, Type as Type$1 } from "@sinclair/typebox";
|
|
15
|
+
import { Value } from "@sinclair/typebox/value";
|
|
16
16
|
//#region \0rolldown/runtime.js
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -8817,90 +8817,6 @@ async function resolvePersistentSessionManager(args) {
|
|
|
8817
8817
|
return SessionManager.continueRecent(args.cwd, args.sessionDir);
|
|
8818
8818
|
}
|
|
8819
8819
|
//#endregion
|
|
8820
|
-
//#region ../agent-runtime/src/context-bindings.ts
|
|
8821
|
-
var PROMPT_SEPARATOR = "\n\n---\n\n";
|
|
8822
|
-
/**
|
|
8823
|
-
* Resolve `task.input.context[]` into delivered side-effects (skills
|
|
8824
|
-
* persisted via `deliver.skill`) and prompt fragments
|
|
8825
|
-
* (`systemPromptPrefix`, `userInlineSuffix`) the caller weaves into the
|
|
8826
|
-
* built prompt.
|
|
8827
|
-
*
|
|
8828
|
-
* Per-binding semantics (V1):
|
|
8829
|
-
* - `skill` → `deliver.skill({ slug, content })` once per ref.
|
|
8830
|
-
* Slug collisions on distinct contents are
|
|
8831
|
-
* refused loudly.
|
|
8832
|
-
* - `context_inline`→ persist raw bytes via `deliver.contextFile(...)`
|
|
8833
|
-
* and inject them into the prompt in an explicit,
|
|
8834
|
-
* named block. Intended for eval/context experiments
|
|
8835
|
-
* where the content must be in the model context
|
|
8836
|
-
* window, not merely discoverable as a skill.
|
|
8837
|
-
* - `prompt_prefix` → content appended to `systemPromptPrefix` with
|
|
8838
|
-
* the canonical `\n\n---\n\n` separator (in
|
|
8839
|
-
* declared order).
|
|
8840
|
-
* - `user_inline` → content appended to `userInlineSuffix` in
|
|
8841
|
-
* declared order, same separator.
|
|
8842
|
-
*
|
|
8843
|
-
* No fetching, no hashing — bytes are inlined in `ContextRef.content`,
|
|
8844
|
-
* and the task's `inputCid` already pins the entire input. The proposer
|
|
8845
|
-
* chose these bytes; the resolver just dispatches them.
|
|
8846
|
-
*
|
|
8847
|
-
* The function is pure with respect to its arguments: file writes are
|
|
8848
|
-
* confined to the injected `deliver` callback, which makes the
|
|
8849
|
-
* resolver trivial to test.
|
|
8850
|
-
*/
|
|
8851
|
-
async function resolveTaskContext(args) {
|
|
8852
|
-
const promptParts = [];
|
|
8853
|
-
const userParts = [];
|
|
8854
|
-
const injected = [];
|
|
8855
|
-
const usedSlugs = /* @__PURE__ */ new Map();
|
|
8856
|
-
for (const ref of args.context) {
|
|
8857
|
-
if (ref.binding === "skill") {
|
|
8858
|
-
const prior = usedSlugs.get(ref.slug);
|
|
8859
|
-
if (prior !== void 0) {
|
|
8860
|
-
if (prior !== ref.content) throw new Error(`slug collision on '${ref.slug}': two skill entries share the same slug but have different content`);
|
|
8861
|
-
injected.push(ref);
|
|
8862
|
-
continue;
|
|
8863
|
-
}
|
|
8864
|
-
usedSlugs.set(ref.slug, ref.content);
|
|
8865
|
-
await args.deliver.skill({
|
|
8866
|
-
slug: ref.slug,
|
|
8867
|
-
content: ref.content
|
|
8868
|
-
});
|
|
8869
|
-
} else if (ref.binding === "context_inline") {
|
|
8870
|
-
await args.deliver.contextFile({
|
|
8871
|
-
slug: ref.slug,
|
|
8872
|
-
content: ref.content,
|
|
8873
|
-
suggestedFileName: `${ref.slug}.md`
|
|
8874
|
-
});
|
|
8875
|
-
promptParts.push(formatInlineContextBlock(ref.slug, ref.content));
|
|
8876
|
-
} else if (ref.binding === "prompt_prefix") promptParts.push(ref.content);
|
|
8877
|
-
else userParts.push(ref.content);
|
|
8878
|
-
injected.push(ref);
|
|
8879
|
-
}
|
|
8880
|
-
return {
|
|
8881
|
-
injected,
|
|
8882
|
-
systemPromptPrefix: promptParts.join(PROMPT_SEPARATOR),
|
|
8883
|
-
userInlineSuffix: userParts.join(PROMPT_SEPARATOR)
|
|
8884
|
-
};
|
|
8885
|
-
}
|
|
8886
|
-
function formatInlineContextBlock(slug, content) {
|
|
8887
|
-
return [
|
|
8888
|
-
"### Injected Task Context",
|
|
8889
|
-
"",
|
|
8890
|
-
`Context id: \`${slug}\``,
|
|
8891
|
-
"The following raw context was supplied by the task creator. Treat it",
|
|
8892
|
-
"as task-relevant background that may override generic coding instincts",
|
|
8893
|
-
"when it contains repo- or workflow-specific constraints.",
|
|
8894
|
-
"The same content is also materialized in the workspace as",
|
|
8895
|
-
"`/workspace/context-pack.md` and mirrored in `AGENTS.md` for",
|
|
8896
|
-
"repo-context discovery.",
|
|
8897
|
-
"",
|
|
8898
|
-
"<context>",
|
|
8899
|
-
content,
|
|
8900
|
-
"</context>"
|
|
8901
|
-
].join("\n");
|
|
8902
|
-
}
|
|
8903
|
-
//#endregion
|
|
8904
8820
|
//#region ../tasks/src/formats.ts
|
|
8905
8821
|
/**
|
|
8906
8822
|
* Register TypeBox string formats used across Task / TaskOutput / task-type
|
|
@@ -9386,6 +9302,14 @@ var FreeformExecutionOptions = Type$1.Object({ workspace: Type$1.Optional(Type$1
|
|
|
9386
9302
|
$id: "FreeformExecutionOptions",
|
|
9387
9303
|
additionalProperties: false
|
|
9388
9304
|
});
|
|
9305
|
+
var FreeformContinueFrom = Type$1.Object({
|
|
9306
|
+
taskId: Type$1.String({ format: "uuid" }),
|
|
9307
|
+
attemptN: Type$1.Integer({ minimum: 1 }),
|
|
9308
|
+
mode: Type$1.Optional(Type$1.Union([Type$1.Literal("extend"), Type$1.Literal("fork")]))
|
|
9309
|
+
}, {
|
|
9310
|
+
$id: "FreeformContinueFrom",
|
|
9311
|
+
additionalProperties: false
|
|
9312
|
+
});
|
|
9389
9313
|
var FreeformTaskTypeProposal = Type$1.Object({
|
|
9390
9314
|
name: Type$1.String({ minLength: 1 }),
|
|
9391
9315
|
rationale: Type$1.String({ minLength: 1 }),
|
|
@@ -9396,14 +9320,14 @@ var FreeformTaskTypeProposal = Type$1.Object({
|
|
|
9396
9320
|
additionalProperties: false
|
|
9397
9321
|
});
|
|
9398
9322
|
var FreeformInput = Type$1.Object({
|
|
9399
|
-
title: Type$1.Optional(Type$1.String({ minLength: 1 })),
|
|
9400
9323
|
brief: Type$1.String({ minLength: 1 }),
|
|
9401
9324
|
expectedOutput: Type$1.Optional(Type$1.String({ minLength: 1 })),
|
|
9402
9325
|
constraints: Type$1.Optional(Type$1.Array(Type$1.String({ minLength: 1 }), { maxItems: 20 })),
|
|
9403
9326
|
suggestedTaskType: Type$1.Optional(Type$1.String({ minLength: 1 })),
|
|
9404
9327
|
successCriteria: Type$1.Optional(SuccessCriteria),
|
|
9405
9328
|
context: Type$1.Optional(TaskContext),
|
|
9406
|
-
execution: Type$1.Optional(FreeformExecutionOptions)
|
|
9329
|
+
execution: Type$1.Optional(FreeformExecutionOptions),
|
|
9330
|
+
continueFrom: Type$1.Optional(FreeformContinueFrom)
|
|
9407
9331
|
}, {
|
|
9408
9332
|
$id: "FreeformInput",
|
|
9409
9333
|
additionalProperties: false
|
|
@@ -9429,6 +9353,84 @@ var FreeformOutput = Type$1.Object({
|
|
|
9429
9353
|
$id: "FreeformOutput",
|
|
9430
9354
|
additionalProperties: false
|
|
9431
9355
|
});
|
|
9356
|
+
/**
|
|
9357
|
+
* Server-side preflight for `freeform` task-create. Runs after the
|
|
9358
|
+
* sync TypeBox check passes and only kicks in when
|
|
9359
|
+
* `input.continueFrom` is set — i.e. the proposer is asking to
|
|
9360
|
+
* resume a prior freeform attempt's warm slot (#1287).
|
|
9361
|
+
*
|
|
9362
|
+
* Failure modes, in evaluation order:
|
|
9363
|
+
* 1. `freeform.sourceTaskNotFound` — source task id does not resolve
|
|
9364
|
+
* (does not exist OR caller can't read it; we don't distinguish).
|
|
9365
|
+
* 2. `freeform.sourceTaskTypeNotSupported` — source isn't `freeform`.
|
|
9366
|
+
* v1 only supports freeform → freeform continuation.
|
|
9367
|
+
* 3. `freeform.sourceAttemptNotCompleted` — named attempt is missing
|
|
9368
|
+
* or not in `completed` state; warm continuation only makes sense
|
|
9369
|
+
* once the parent has produced a terminal output.
|
|
9370
|
+
* 4. `freeform.forkModeNotImplemented` — `mode: 'fork'` is the wire
|
|
9371
|
+
* surface for copy-on-write continuation tracked in #1293; v1
|
|
9372
|
+
* rejects it server-side so daemons never have to branch.
|
|
9373
|
+
* 5. `freeform.executionWorkspaceNotInheritable` — caller set
|
|
9374
|
+
* `execution.workspace` together with `continueFrom`. Workspace
|
|
9375
|
+
* mode for a continuation is inherited from the parent slot
|
|
9376
|
+
* (`maybeAttachWarmSlotContext` forces `dedicated_worktree` +
|
|
9377
|
+
* the parent's worktreeBranch), so any caller-supplied override
|
|
9378
|
+
* is silently dropped at the daemon plan stage. Reject explicitly
|
|
9379
|
+
* so misconfiguration surfaces at create time.
|
|
9380
|
+
* 6. `freeform.sourceNotResumeEligible` — `daemonState` is null or
|
|
9381
|
+
* `slotResumableUntil` is null. Older completions (pre-#1287) and
|
|
9382
|
+
* daemons that opt out fall here.
|
|
9383
|
+
* 7. `freeform.sourceResumeExpired` — `slotResumableUntil` is in the
|
|
9384
|
+
* past; the warm slot's TTL has elapsed and no daemon is
|
|
9385
|
+
* guaranteed to still hold it.
|
|
9386
|
+
*
|
|
9387
|
+
* Returns on the first failure (no "report all six") — the checks
|
|
9388
|
+
* are sequential preconditions, later ones presume earlier ones hold.
|
|
9389
|
+
*/
|
|
9390
|
+
async function validateFreeformInputAsync(input, ctx) {
|
|
9391
|
+
const cf = input.continueFrom;
|
|
9392
|
+
if (!cf) return [];
|
|
9393
|
+
const source = await ctx.resolveTask(cf.taskId);
|
|
9394
|
+
if (!source) return [{
|
|
9395
|
+
field: "input/continueFrom/taskId",
|
|
9396
|
+
message: `Source task ${cf.taskId} does not resolve to a task you can read`,
|
|
9397
|
+
code: "freeform.sourceTaskNotFound"
|
|
9398
|
+
}];
|
|
9399
|
+
if (source.taskType !== "freeform") return [{
|
|
9400
|
+
field: "input/continueFrom/taskId",
|
|
9401
|
+
message: `Source task type '${source.taskType}' is not continuable; only freeform → freeform is supported in v1`,
|
|
9402
|
+
code: "freeform.sourceTaskTypeNotSupported"
|
|
9403
|
+
}];
|
|
9404
|
+
if (cf.mode === "fork") return [{
|
|
9405
|
+
field: "input/continueFrom/mode",
|
|
9406
|
+
message: "fork mode not yet implemented; see https://github.com/getlarge/themoltnet/issues/1293",
|
|
9407
|
+
code: "freeform.forkModeNotImplemented"
|
|
9408
|
+
}];
|
|
9409
|
+
if (input.execution?.workspace) return [{
|
|
9410
|
+
field: "input/execution/workspace",
|
|
9411
|
+
message: "execution.workspace is inherited from the parent slot when continueFrom is set; omit it",
|
|
9412
|
+
code: "freeform.executionWorkspaceNotInheritable"
|
|
9413
|
+
}];
|
|
9414
|
+
if (ctx.deferReadinessChecks) return [];
|
|
9415
|
+
const attempt = (await ctx.listAttempts(cf.taskId)).find((a) => a.attemptN === cf.attemptN);
|
|
9416
|
+
if (!attempt || attempt.status !== "completed") return [{
|
|
9417
|
+
field: "input/continueFrom/attemptN",
|
|
9418
|
+
message: `Source attempt ${cf.attemptN} on task ${cf.taskId} is not in 'completed' state`,
|
|
9419
|
+
code: "freeform.sourceAttemptNotCompleted"
|
|
9420
|
+
}];
|
|
9421
|
+
if (!attempt.daemonState || attempt.daemonState.slotResumableUntil === null) return [{
|
|
9422
|
+
field: "input/continueFrom",
|
|
9423
|
+
message: "Source attempt did not report continuation eligibility (older completion or daemon opted out)",
|
|
9424
|
+
code: "freeform.sourceNotResumeEligible"
|
|
9425
|
+
}];
|
|
9426
|
+
const expiresAt = new Date(attempt.daemonState.slotResumableUntil).getTime();
|
|
9427
|
+
if (Number.isNaN(expiresAt) || expiresAt <= Date.now()) return [{
|
|
9428
|
+
field: "input/continueFrom",
|
|
9429
|
+
message: `Source attempt's warm slot expired at ${attempt.daemonState.slotResumableUntil} (reported at ${attempt.daemonState.reportedAt})`,
|
|
9430
|
+
code: "freeform.sourceResumeExpired"
|
|
9431
|
+
}];
|
|
9432
|
+
return [];
|
|
9433
|
+
}
|
|
9432
9434
|
//#endregion
|
|
9433
9435
|
//#region ../tasks/src/task-types/fulfill-brief.ts
|
|
9434
9436
|
/**
|
|
@@ -9441,7 +9443,6 @@ var FreeformOutput = Type$1.Object({
|
|
|
9441
9443
|
var FULFILL_BRIEF_TYPE = "fulfill_brief";
|
|
9442
9444
|
var FulfillBriefInput = Type$1.Object({
|
|
9443
9445
|
brief: Type$1.String({ minLength: 1 }),
|
|
9444
|
-
title: Type$1.Optional(Type$1.String()),
|
|
9445
9446
|
successCriteria: Type$1.Optional(SuccessCriteria),
|
|
9446
9447
|
seedFiles: Type$1.Optional(Type$1.Array(Type$1.String())),
|
|
9447
9448
|
scopeHint: Type$1.Optional(Type$1.String())
|
|
@@ -9999,7 +10000,8 @@ var BUILT_IN_TASK_TYPES = {
|
|
|
9999
10000
|
sessionScope: "correlation",
|
|
10000
10001
|
acceptsInputWorkspaceOverride: true,
|
|
10001
10002
|
requiresReferences: false,
|
|
10002
|
-
validateOutput: requireVerificationWhenCriteriaPresent
|
|
10003
|
+
validateOutput: requireVerificationWhenCriteriaPresent,
|
|
10004
|
+
validateInputAsync: validateFreeformInputAsync
|
|
10003
10005
|
},
|
|
10004
10006
|
[FULFILL_BRIEF_TYPE]: {
|
|
10005
10007
|
name: FULFILL_BRIEF_TYPE,
|
|
@@ -10224,6 +10226,25 @@ var Cid = Type$1.String({ minLength: 1 });
|
|
|
10224
10226
|
var IsoTimestamp = Type$1.String({ format: "date-time" });
|
|
10225
10227
|
var MAX_CLAIM_CONDITION_BRANCHES = 8;
|
|
10226
10228
|
var MAX_CLAIM_CONDITION_STATUSES = 8;
|
|
10229
|
+
/**
|
|
10230
|
+
* Daemon-asserted runtime state stamped onto a `TaskAttemptSummary` at
|
|
10231
|
+
* attempt-completion time. The server persists this block verbatim and
|
|
10232
|
+
* reads `slotResumableUntil` for `tasks_continue` create-time
|
|
10233
|
+
* eligibility; the daemon-side claim-affinity filter is the runtime
|
|
10234
|
+
* truth. The block carries its own `reportedAt` so consumers can reason
|
|
10235
|
+
* about staleness without reading documentation. All daemon-asserted
|
|
10236
|
+
* state lives here — top-level attempt fields stay server-authoritative.
|
|
10237
|
+
*
|
|
10238
|
+
* Adding new fields requires explicit design review (intentional
|
|
10239
|
+
* boundary; see docs/superpowers/specs/2026-06-04-tasks-continue-design.md).
|
|
10240
|
+
*/
|
|
10241
|
+
var DaemonState = Type$1.Object({
|
|
10242
|
+
reportedAt: IsoTimestamp,
|
|
10243
|
+
slotResumableUntil: Type$1.Union([IsoTimestamp, Type$1.Null()])
|
|
10244
|
+
}, {
|
|
10245
|
+
$id: "DaemonState",
|
|
10246
|
+
additionalProperties: false
|
|
10247
|
+
});
|
|
10227
10248
|
var ClaimCondition = Type$1.Recursive((Self) => Type$1.Union([
|
|
10228
10249
|
Type$1.Object({
|
|
10229
10250
|
op: Type$1.Literal("all"),
|
|
@@ -10320,6 +10341,8 @@ Type$1.Object({
|
|
|
10320
10341
|
Type$1.Object({
|
|
10321
10342
|
id: Uuid,
|
|
10322
10343
|
taskType: Type$1.String({ minLength: 1 }),
|
|
10344
|
+
title: Type$1.Union([Type$1.String(), Type$1.Null()]),
|
|
10345
|
+
tags: Type$1.Array(Type$1.String()),
|
|
10323
10346
|
teamId: Uuid,
|
|
10324
10347
|
diaryId: Type$1.Union([Uuid, Type$1.Null()]),
|
|
10325
10348
|
outputKind: OutputKind,
|
|
@@ -10372,7 +10395,8 @@ Type$1.Object({
|
|
|
10372
10395
|
error: Type$1.Union([TaskError, Type$1.Null()]),
|
|
10373
10396
|
usage: Type$1.Union([TaskUsage, Type$1.Null()]),
|
|
10374
10397
|
contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
|
|
10375
|
-
signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()])
|
|
10398
|
+
signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
|
|
10399
|
+
daemonState: Type$1.Union([DaemonState, Type$1.Null()])
|
|
10376
10400
|
}, {
|
|
10377
10401
|
$id: "TaskAttempt",
|
|
10378
10402
|
additionalProperties: false
|
|
@@ -10425,6 +10449,90 @@ Type$1.Object({
|
|
|
10425
10449
|
additionalProperties: false
|
|
10426
10450
|
});
|
|
10427
10451
|
//#endregion
|
|
10452
|
+
//#region ../agent-runtime/src/context-bindings.ts
|
|
10453
|
+
var PROMPT_SEPARATOR = "\n\n---\n\n";
|
|
10454
|
+
/**
|
|
10455
|
+
* Resolve `task.input.context[]` into delivered side-effects (skills
|
|
10456
|
+
* persisted via `deliver.skill`) and prompt fragments
|
|
10457
|
+
* (`systemPromptPrefix`, `userInlineSuffix`) the caller weaves into the
|
|
10458
|
+
* built prompt.
|
|
10459
|
+
*
|
|
10460
|
+
* Per-binding semantics (V1):
|
|
10461
|
+
* - `skill` → `deliver.skill({ slug, content })` once per ref.
|
|
10462
|
+
* Slug collisions on distinct contents are
|
|
10463
|
+
* refused loudly.
|
|
10464
|
+
* - `context_inline`→ persist raw bytes via `deliver.contextFile(...)`
|
|
10465
|
+
* and inject them into the prompt in an explicit,
|
|
10466
|
+
* named block. Intended for eval/context experiments
|
|
10467
|
+
* where the content must be in the model context
|
|
10468
|
+
* window, not merely discoverable as a skill.
|
|
10469
|
+
* - `prompt_prefix` → content appended to `systemPromptPrefix` with
|
|
10470
|
+
* the canonical `\n\n---\n\n` separator (in
|
|
10471
|
+
* declared order).
|
|
10472
|
+
* - `user_inline` → content appended to `userInlineSuffix` in
|
|
10473
|
+
* declared order, same separator.
|
|
10474
|
+
*
|
|
10475
|
+
* No fetching, no hashing — bytes are inlined in `ContextRef.content`,
|
|
10476
|
+
* and the task's `inputCid` already pins the entire input. The proposer
|
|
10477
|
+
* chose these bytes; the resolver just dispatches them.
|
|
10478
|
+
*
|
|
10479
|
+
* The function is pure with respect to its arguments: file writes are
|
|
10480
|
+
* confined to the injected `deliver` callback, which makes the
|
|
10481
|
+
* resolver trivial to test.
|
|
10482
|
+
*/
|
|
10483
|
+
async function resolveTaskContext(args) {
|
|
10484
|
+
const promptParts = [];
|
|
10485
|
+
const userParts = [];
|
|
10486
|
+
const injected = [];
|
|
10487
|
+
const usedSlugs = /* @__PURE__ */ new Map();
|
|
10488
|
+
for (const ref of args.context) {
|
|
10489
|
+
if (ref.binding === "skill") {
|
|
10490
|
+
const prior = usedSlugs.get(ref.slug);
|
|
10491
|
+
if (prior !== void 0) {
|
|
10492
|
+
if (prior !== ref.content) throw new Error(`slug collision on '${ref.slug}': two skill entries share the same slug but have different content`);
|
|
10493
|
+
injected.push(ref);
|
|
10494
|
+
continue;
|
|
10495
|
+
}
|
|
10496
|
+
usedSlugs.set(ref.slug, ref.content);
|
|
10497
|
+
await args.deliver.skill({
|
|
10498
|
+
slug: ref.slug,
|
|
10499
|
+
content: ref.content
|
|
10500
|
+
});
|
|
10501
|
+
} else if (ref.binding === "context_inline") {
|
|
10502
|
+
await args.deliver.contextFile({
|
|
10503
|
+
slug: ref.slug,
|
|
10504
|
+
content: ref.content,
|
|
10505
|
+
suggestedFileName: `${ref.slug}.md`
|
|
10506
|
+
});
|
|
10507
|
+
promptParts.push(formatInlineContextBlock(ref.slug, ref.content));
|
|
10508
|
+
} else if (ref.binding === "prompt_prefix") promptParts.push(ref.content);
|
|
10509
|
+
else userParts.push(ref.content);
|
|
10510
|
+
injected.push(ref);
|
|
10511
|
+
}
|
|
10512
|
+
return {
|
|
10513
|
+
injected,
|
|
10514
|
+
systemPromptPrefix: promptParts.join(PROMPT_SEPARATOR),
|
|
10515
|
+
userInlineSuffix: userParts.join(PROMPT_SEPARATOR)
|
|
10516
|
+
};
|
|
10517
|
+
}
|
|
10518
|
+
function formatInlineContextBlock(slug, content) {
|
|
10519
|
+
return [
|
|
10520
|
+
"### Injected Task Context",
|
|
10521
|
+
"",
|
|
10522
|
+
`Context id: \`${slug}\``,
|
|
10523
|
+
"The following raw context was supplied by the task creator. Treat it",
|
|
10524
|
+
"as task-relevant background that may override generic coding instincts",
|
|
10525
|
+
"when it contains repo- or workflow-specific constraints.",
|
|
10526
|
+
"The same content is also materialized in the workspace as",
|
|
10527
|
+
"`/workspace/context-pack.md` and mirrored in `AGENTS.md` for",
|
|
10528
|
+
"repo-context discovery.",
|
|
10529
|
+
"",
|
|
10530
|
+
"<context>",
|
|
10531
|
+
content,
|
|
10532
|
+
"</context>"
|
|
10533
|
+
].join("\n");
|
|
10534
|
+
}
|
|
10535
|
+
//#endregion
|
|
10428
10536
|
//#region ../agent-runtime/src/output-tools.ts
|
|
10429
10537
|
/**
|
|
10430
10538
|
* Submit-output tool contract.
|
|
@@ -10935,6 +11043,23 @@ function buildCuratePackUserPrompt(input, ctx) {
|
|
|
10935
11043
|
}
|
|
10936
11044
|
//#endregion
|
|
10937
11045
|
//#region ../agent-runtime/src/prompts/freeform.ts
|
|
11046
|
+
var INLINE_ARTIFACT_THRESHOLD = 16 * 1024;
|
|
11047
|
+
var TOTAL_INLINE_BUDGET = 32 * 1024;
|
|
11048
|
+
function buildPriorContextSection(priorContext) {
|
|
11049
|
+
if (!priorContext) return null;
|
|
11050
|
+
const lines = ["# Prior context", ""];
|
|
11051
|
+
if (priorContext.summary) lines.push("## Summary", priorContext.summary, "");
|
|
11052
|
+
let inlineUsed = priorContext.summary?.length ?? 0;
|
|
11053
|
+
priorContext.artifacts?.forEach((art, i) => {
|
|
11054
|
+
const bodyLen = art.body?.length ?? 0;
|
|
11055
|
+
if (art.body && bodyLen < INLINE_ARTIFACT_THRESHOLD && inlineUsed + bodyLen < TOTAL_INLINE_BUDGET) {
|
|
11056
|
+
lines.push(`## Artifact ${i}: ${art.title} (${art.kind})`, art.body, "");
|
|
11057
|
+
inlineUsed += bodyLen;
|
|
11058
|
+
} else lines.push(`## Artifact ${i} (pointer): kind: ${art.kind}, title: ${art.title}`, "(body omitted; use tasks_get to retrieve full content)", "");
|
|
11059
|
+
});
|
|
11060
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
11061
|
+
return lines.join("\n");
|
|
11062
|
+
}
|
|
10938
11063
|
function buildFreeformUserPrompt(input, ctx) {
|
|
10939
11064
|
const header = [
|
|
10940
11065
|
"# Freeform Task Agent",
|
|
@@ -10956,18 +11081,12 @@ function buildFreeformUserPrompt(input, ctx) {
|
|
|
10956
11081
|
"4. If the request reveals a recurring task shape, include a",
|
|
10957
11082
|
" `proposedTaskType` in the final output with a concise rationale."
|
|
10958
11083
|
].join("\n");
|
|
10959
|
-
|
|
11084
|
+
const sections = [
|
|
10960
11085
|
{
|
|
10961
11086
|
id: "freeform.header",
|
|
10962
11087
|
source: "header",
|
|
10963
11088
|
body: header
|
|
10964
11089
|
},
|
|
10965
|
-
{
|
|
10966
|
-
id: "freeform.title",
|
|
10967
|
-
source: "task_input",
|
|
10968
|
-
header: "Title",
|
|
10969
|
-
body: input.title ?? ""
|
|
10970
|
-
},
|
|
10971
11090
|
{
|
|
10972
11091
|
id: "freeform.brief",
|
|
10973
11092
|
source: "task_input",
|
|
@@ -11020,7 +11139,14 @@ function buildFreeformUserPrompt(input, ctx) {
|
|
|
11020
11139
|
].join("\n")
|
|
11021
11140
|
})
|
|
11022
11141
|
}
|
|
11023
|
-
]
|
|
11142
|
+
];
|
|
11143
|
+
const priorContextBody = buildPriorContextSection(ctx.priorContext);
|
|
11144
|
+
if (priorContextBody) sections.splice(sections.findIndex((s) => s.id === "freeform.workflow") + 1, 0, {
|
|
11145
|
+
id: "freeform.prior_context",
|
|
11146
|
+
source: "task_input",
|
|
11147
|
+
body: priorContextBody
|
|
11148
|
+
});
|
|
11149
|
+
return assembleTaskPrompt("freeform", sections);
|
|
11024
11150
|
}
|
|
11025
11151
|
//#endregion
|
|
11026
11152
|
//#region ../agent-runtime/src/prompts/fulfill-brief.ts
|
|
@@ -11032,7 +11158,7 @@ function buildFreeformUserPrompt(input, ctx) {
|
|
|
11032
11158
|
* is told to inspect them itself.
|
|
11033
11159
|
*/
|
|
11034
11160
|
function buildFulfillBriefUserPrompt(input, ctx) {
|
|
11035
|
-
const { brief,
|
|
11161
|
+
const { brief, seedFiles, scopeHint } = input;
|
|
11036
11162
|
const header = [
|
|
11037
11163
|
"# Fulfill Brief Agent",
|
|
11038
11164
|
"",
|
|
@@ -11042,7 +11168,7 @@ function buildFulfillBriefUserPrompt(input, ctx) {
|
|
|
11042
11168
|
"invariants for this task: identity, gh authentication, diary discipline,",
|
|
11043
11169
|
"and the accountable-commit shape. Follow it for every commit.",
|
|
11044
11170
|
"",
|
|
11045
|
-
|
|
11171
|
+
"## Task: Fulfill brief",
|
|
11046
11172
|
"",
|
|
11047
11173
|
`Task id: \`${ctx.taskId}\``
|
|
11048
11174
|
].join("\n");
|
|
@@ -11781,7 +11907,10 @@ function buildTaskUserPrompt(task, ctx) {
|
|
|
11781
11907
|
const errors = [...Value.Errors(FreeformInput, task.input)];
|
|
11782
11908
|
throw new Error(`freeform input failed validation: ${JSON.stringify(errors.slice(0, 3))}`);
|
|
11783
11909
|
}
|
|
11784
|
-
return buildFreeformUserPrompt(task.input, {
|
|
11910
|
+
return buildFreeformUserPrompt(task.input, {
|
|
11911
|
+
taskId: ctx.taskId,
|
|
11912
|
+
priorContext: ctx.priorContext
|
|
11913
|
+
});
|
|
11785
11914
|
case FULFILL_BRIEF_TYPE:
|
|
11786
11915
|
if (!Value.Check(FulfillBriefInput, task.input)) {
|
|
11787
11916
|
const errors = [...Value.Errors(FulfillBriefInput, task.input)];
|
|
@@ -15474,6 +15603,30 @@ function clip(s, max) {
|
|
|
15474
15603
|
return s.length > max ? s.slice(0, max) : s;
|
|
15475
15604
|
}
|
|
15476
15605
|
//#endregion
|
|
15606
|
+
//#region src/runtime/resolve-prior-context.ts
|
|
15607
|
+
/**
|
|
15608
|
+
* Fetch the named attempt's output and project it into the prompt's
|
|
15609
|
+
* priorContext shape. Returns `null` when the attempt cannot be located
|
|
15610
|
+
* or its output is missing/malformed — the prompt builder treats that
|
|
15611
|
+
* as "no prior context", which is harmless.
|
|
15612
|
+
*/
|
|
15613
|
+
async function resolvePriorContext(agent, continueFrom) {
|
|
15614
|
+
const attempt = (await agent.tasks.listAttempts(continueFrom.taskId)).find((a) => a.attemptN === continueFrom.attemptN);
|
|
15615
|
+
if (!attempt || !attempt.output) return null;
|
|
15616
|
+
const output = attempt.output;
|
|
15617
|
+
const summary = typeof output.summary === "string" ? output.summary : void 0;
|
|
15618
|
+
const artifacts = Array.isArray(output.artifacts) ? output.artifacts.filter((a) => !!a && typeof a.kind === "string" && typeof a.title === "string").map((a) => ({
|
|
15619
|
+
kind: a.kind,
|
|
15620
|
+
title: a.title,
|
|
15621
|
+
body: a.body
|
|
15622
|
+
})) : void 0;
|
|
15623
|
+
if (!summary && (!artifacts || artifacts.length === 0)) return null;
|
|
15624
|
+
return {
|
|
15625
|
+
summary,
|
|
15626
|
+
artifacts
|
|
15627
|
+
};
|
|
15628
|
+
}
|
|
15629
|
+
//#endregion
|
|
15477
15630
|
//#region src/runtime/runtime-instructor.ts
|
|
15478
15631
|
/**
|
|
15479
15632
|
* Build the daemon-controlled invariant prose injected into the system prompt
|
|
@@ -16249,7 +16402,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16249
16402
|
const attemptN = claimedTask.attemptN;
|
|
16250
16403
|
const startTime = Date.now();
|
|
16251
16404
|
const requestedMountPath = opts.mountPath ?? process.cwd();
|
|
16252
|
-
const executionPlan = opts.makeExecutionPlan?.(claimedTask) ?? null;
|
|
16405
|
+
const executionPlan = await opts.makeExecutionPlan?.(claimedTask) ?? null;
|
|
16253
16406
|
let workspace = null;
|
|
16254
16407
|
let mountPath = requestedMountPath;
|
|
16255
16408
|
let cwdPath = requestedMountPath;
|
|
@@ -16387,6 +16540,34 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16387
16540
|
workspaceMode: activeWorkspace.mode,
|
|
16388
16541
|
workspaceBranch: activeWorkspace.branch
|
|
16389
16542
|
});
|
|
16543
|
+
let resolvedPriorContext;
|
|
16544
|
+
const continueFrom = task.input?.continueFrom;
|
|
16545
|
+
if (task.taskType === "freeform" && continueFrom) try {
|
|
16546
|
+
const resolved = await resolvePriorContext(await connect({ configDir: managed.agentDir }), continueFrom);
|
|
16547
|
+
if (resolved) {
|
|
16548
|
+
resolvedPriorContext = resolved;
|
|
16549
|
+
await emit("info", {
|
|
16550
|
+
event: "prior_context_resolved",
|
|
16551
|
+
sourceTaskId: continueFrom.taskId,
|
|
16552
|
+
sourceAttemptN: continueFrom.attemptN,
|
|
16553
|
+
hasSummary: !!resolved.summary,
|
|
16554
|
+
artifactCount: resolved.artifacts?.length ?? 0
|
|
16555
|
+
});
|
|
16556
|
+
} else await emit("info", {
|
|
16557
|
+
event: "prior_context_empty",
|
|
16558
|
+
sourceTaskId: continueFrom.taskId,
|
|
16559
|
+
sourceAttemptN: continueFrom.attemptN
|
|
16560
|
+
});
|
|
16561
|
+
} catch (err) {
|
|
16562
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16563
|
+
await emit("info", {
|
|
16564
|
+
event: "prior_context_resolve_failed",
|
|
16565
|
+
severity: "warn",
|
|
16566
|
+
sourceTaskId: continueFrom.taskId,
|
|
16567
|
+
sourceAttemptN: continueFrom.attemptN,
|
|
16568
|
+
message
|
|
16569
|
+
});
|
|
16570
|
+
}
|
|
16390
16571
|
let taskPrompt;
|
|
16391
16572
|
try {
|
|
16392
16573
|
const assembled = buildTaskUserPrompt(task, {
|
|
@@ -16398,7 +16579,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
16398
16579
|
attached: executionPlan?.workspaceAttachment !== void 0 || executionPlan?.workspaceSeed?.source === "producer",
|
|
16399
16580
|
source: executionPlan?.workspaceSeed?.source === "producer" ? "producer_copy" : executionPlan?.workspaceAttachment !== void 0 ? "producer_attachment" : void 0
|
|
16400
16581
|
},
|
|
16401
|
-
extras: opts.promptExtras
|
|
16582
|
+
extras: opts.promptExtras,
|
|
16583
|
+
priorContext: resolvedPriorContext
|
|
16402
16584
|
});
|
|
16403
16585
|
taskPrompt = assembled.text;
|
|
16404
16586
|
await emit("info", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
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.9.1",
|
|
32
32
|
"@opentelemetry/api": "^1.9.0",
|
|
33
33
|
"@sinclair/typebox": "^0.34.0",
|
|
34
|
-
"@themoltnet/
|
|
35
|
-
"@themoltnet/
|
|
34
|
+
"@themoltnet/sdk": "0.106.0",
|
|
35
|
+
"@themoltnet/agent-runtime": "0.22.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"vite": "^8.0.0",
|
|
57
57
|
"vite-plugin-dts": "^4.5.4",
|
|
58
58
|
"vitest": "^3.0.0",
|
|
59
|
-
"@moltnet/crypto-service": "0.1.0"
|
|
59
|
+
"@moltnet/crypto-service": "0.1.0",
|
|
60
|
+
"@moltnet/tasks": "0.1.0"
|
|
60
61
|
},
|
|
61
62
|
"engines": {
|
|
62
63
|
"node": ">=22"
|