@junghanacs/entwurf 0.30.1 → 0.32.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/AGENTS.md +4 -2
- package/CHANGELOG.md +194 -0
- package/DELIVERY.md +1 -0
- package/README.md +64 -14
- package/VERIFY.md +36 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/setup-clean-host.md +121 -4
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +7 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +28 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +746 -0
- package/mcp/entwurf-bridge/src/index.ts +7 -7
- package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
- package/package.json +19 -19
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/pi-durable/bootstrap.mjs +404 -0
- package/pi/pi-durable/carrier/LICENSE +21 -0
- package/pi/pi-durable/carrier/harness-setup.js +123 -0
- package/pi/pi-durable/carrier/prompt.js +74 -0
- package/pi/pi-durable/carrier/runtime.js +402 -0
- package/pi/pi-durable/carrier/sessions.js +56 -0
- package/pi/pi-durable/carrier/subagent.js +55 -0
- package/pi/pi-durable/carrier/tui.js +655 -0
- package/pi/pi-durable/carrier-relocation.mjs +160 -0
- package/pi/pi-durable/carrier-resolver.mjs +38 -0
- package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
- package/pi/pi-durable/overlay/upstream-pin.json +108 -0
- package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
- package/pi-extensions/lib/fresh-call-composition.ts +42 -7
- package/pi-extensions/lib/meta-sender-identity.ts +7 -1
- package/pi-extensions/lib/meta-session.ts +28 -2
- package/pi-extensions/lib/mux-fresh-call.ts +32 -2
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
- package/pi-extensions/lib/pi-durable-runtime.ts +318 -0
- package/pi-extensions/meta-bridge-pi-durable.ts +1024 -0
- package/run.sh +624 -184
- package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
- package/scripts/check-fresh-call-dispatch.ts +18 -0
- package/scripts/check-gate-qualification.ts +87 -34
- package/scripts/check-herdr-activation.ts +5 -4
- package/scripts/check-pi-durable-contact.ts +767 -0
- package/scripts/check-pi-durable-receive.ts +1079 -0
- package/scripts/check-pi-durable-send.ts +1821 -0
- package/scripts/emit-pi-durable-carrier.ts +355 -0
- package/scripts/lib/mutation-qualify.ts +21 -45
- package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
- package/scripts/lib/pi-durable-operator-guard.ts +89 -0
- package/scripts/lib/pi-durable-r1.ts +100 -0
- package/scripts/lib/pi-durable-reap.ts +27 -0
- package/scripts/lib/pi-durable-receive.ts +137 -0
- package/scripts/lib/qualification-receipt.ts +7 -11
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/copilot-birth.json +2 -2
- package/scripts/mutants/gate-qualification.json +13 -0
- package/scripts/mutants/meta-hook-session-switch.json +4 -2
- package/scripts/mutants/mux-fresh-call.json +69 -3
- package/scripts/mutants/omp-birth.json +2 -2
- package/scripts/mutants/omp-fresh.json +6 -4
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +723 -0
- package/scripts/mutants/pi-floor.json +24 -2
- package/scripts/pi-durable-contact-host.mjs +105 -0
- package/scripts/pi-durable-receive-host.mjs +127 -0
- package/scripts/pi-durable-send-host.mjs +284 -0
- package/scripts/smoke-herdr-raw-install-live.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
- package/scripts/smoke-setup-verdict.sh +22 -3
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- package/scripts/tsconfig.json +1 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// The pi-durable carrier's ONE relocation grammar (#130), shared by the maintainer emitter
|
|
2
|
+
// (scripts/emit-pi-durable-carrier.ts) and the runtime resolver (./carrier-resolver.mjs), so the
|
|
3
|
+
// rewrite the emitter performs and the resolution the runtime accepts cannot drift apart.
|
|
4
|
+
//
|
|
5
|
+
// The durable app is source-only upstream: published `@earendil-works/pi-coding-agent` ships no
|
|
6
|
+
// `dist/experimental`, and its exports map exposes no deep `dist/` path. The carrier is the pinned
|
|
7
|
+
// upstream app files, type-stripped, with exactly two rewrites:
|
|
8
|
+
//
|
|
9
|
+
// "./<sibling>.ts" → "./<sibling>.js" a carrier file importing another carrier file
|
|
10
|
+
// "../../<path>.ts" → entwurf-pi-dist:<path>.js the app importing a pi-coding-agent internal
|
|
11
|
+
//
|
|
12
|
+
// Every other specifier — `@earendil-works/*`, `proper-lockfile`, `node:*` — is left untouched and
|
|
13
|
+
// resolves by Node's own rules from where the carrier is installed. The reserved scheme is chosen so
|
|
14
|
+
// that a carrier loaded WITHOUT the resolver fails by name (Node refuses an unknown URL scheme) rather
|
|
15
|
+
// than quietly resolving `../../` inside this package. The resolver accepts the scheme only from a
|
|
16
|
+
// declared carrier file and only for a declared target, and maps it to that file under the PHYSICAL
|
|
17
|
+
// root of the pi-coding-agent the carrier itself resolves — never through the exports map.
|
|
18
|
+
//
|
|
19
|
+
// Plain JS on purpose: the resolver is preloaded with `node --import` from an installed package,
|
|
20
|
+
// where Node does not strip types, and it must not depend on the compiled bridge closure.
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
25
|
+
|
|
26
|
+
export const CARRIER_SCHEME = "entwurf-pi-dist:";
|
|
27
|
+
export const CODING_AGENT = "@earendil-works/pi-coding-agent";
|
|
28
|
+
|
|
29
|
+
/** A refusal with a stable name first, as the bootstrap's. */
|
|
30
|
+
function refuse(reason, detail) {
|
|
31
|
+
return Object.assign(new Error(`${reason}: ${detail}`), { reason });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A static import, export-from or side-effect import statement as upstream writes them: double quotes,
|
|
35
|
+
* the statement ending its line. A multi-line `import {` … `} from "x";` matches from its `import`. */
|
|
36
|
+
const STATEMENT = /^(\s*(?:import|export|\})[^;]*?\bfrom\s*")([^"]+)(";[ \t]*)$|^(\s*import\s*")([^"]+)(";[ \t]*)$/gm;
|
|
37
|
+
/** Every place a module specifier can begin — the statements above, and anything they would miss. */
|
|
38
|
+
const ANY_SPECIFIER = /\b(?:from|import)\s*["'(]/g;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Rewrite one type-stripped carrier source. `siblings` are the carrier file names (`runtime.js` …),
|
|
42
|
+
* `targets` the declared pi-coding-agent dist paths (`core/model-runtime.js` …). Throws by name on a
|
|
43
|
+
* relative specifier outside those two rules, a dynamic import, or a specifier the statement grammar
|
|
44
|
+
* did not recognize. Returns the rewritten code and the targets it used.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} file
|
|
47
|
+
* @param {string} code
|
|
48
|
+
* @param {{ siblings: readonly string[], targets: readonly string[] }} declared
|
|
49
|
+
* @returns {{ code: string, used: string[] }}
|
|
50
|
+
*/
|
|
51
|
+
export function relocateCarrierSource(file, code, { siblings, targets }) {
|
|
52
|
+
const used = new Set();
|
|
53
|
+
let statements = 0;
|
|
54
|
+
const rewrite = (spec) => {
|
|
55
|
+
if (spec.startsWith("./")) {
|
|
56
|
+
const name = `${spec.slice(2, -3)}.js`;
|
|
57
|
+
if (!spec.endsWith(".ts") || !siblings.includes(name)) {
|
|
58
|
+
throw refuse("carrier-relocation-unknown-sibling", `${file} imports ${spec}`);
|
|
59
|
+
}
|
|
60
|
+
return `./${name}`;
|
|
61
|
+
}
|
|
62
|
+
if (spec.startsWith("../../")) {
|
|
63
|
+
const target = `${spec.slice(6, -3)}.js`;
|
|
64
|
+
if (!spec.endsWith(".ts") || !targets.includes(target)) {
|
|
65
|
+
throw refuse("carrier-relocation-unknown-target", `${file} imports ${spec}`);
|
|
66
|
+
}
|
|
67
|
+
used.add(target);
|
|
68
|
+
return `${CARRIER_SCHEME}${target}`;
|
|
69
|
+
}
|
|
70
|
+
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith(CARRIER_SCHEME)) {
|
|
71
|
+
throw refuse("carrier-relocation-unknown-relative", `${file} imports ${spec}`);
|
|
72
|
+
}
|
|
73
|
+
return spec;
|
|
74
|
+
};
|
|
75
|
+
const out = code.replace(STATEMENT, (_all, headA, specA, tailA, headB, specB, tailB) => {
|
|
76
|
+
statements++;
|
|
77
|
+
return headA !== undefined ? `${headA}${rewrite(specA)}${tailA}` : `${headB}${rewrite(specB)}${tailB}`;
|
|
78
|
+
});
|
|
79
|
+
const begun = (code.match(ANY_SPECIFIER) ?? []).length;
|
|
80
|
+
if (begun !== statements) {
|
|
81
|
+
throw refuse(
|
|
82
|
+
"carrier-relocation-unrecognized-import",
|
|
83
|
+
`${file}: ${begun} specifier openings, ${statements} recognized statements (a dynamic import, a single-quoted or mid-line import)`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return { code: out, used: [...used].sort() };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The carrier section of the packaged pin, validated. A malformed pin throws (crash, don't warn).
|
|
91
|
+
*
|
|
92
|
+
* @param {URL | string} pinPath
|
|
93
|
+
* @returns {{ files: string[], targets: string[] }}
|
|
94
|
+
*/
|
|
95
|
+
export function readCarrierDeclaration(pinPath) {
|
|
96
|
+
const pin = JSON.parse(readFileSync(pinPath, "utf8"));
|
|
97
|
+
const carrier = pin?.carrier;
|
|
98
|
+
const names = Array.isArray(carrier?.files) ? carrier.files.map((f) => f?.name) : undefined;
|
|
99
|
+
const targets = carrier?.distTargets;
|
|
100
|
+
const plain = (s) => typeof s === "string" && /^[a-z0-9-]+(?:\/[a-z0-9-]+)*\.js$/.test(s);
|
|
101
|
+
if (!names?.every(plain) || names.length === 0 || names.some((n) => n.includes("/"))) {
|
|
102
|
+
throw refuse("pi-durable-carrier-pin-malformed", "carrier.files must name plain carrier .js files");
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(targets) || targets.length === 0 || !targets.every(plain)) {
|
|
105
|
+
throw refuse("pi-durable-carrier-pin-malformed", "carrier.distTargets must list relative dist .js paths");
|
|
106
|
+
}
|
|
107
|
+
return { files: names, targets };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The PHYSICAL `dist/` directory of the pi-coding-agent the carrier resolves, from the URL its root
|
|
112
|
+
* export resolved to (`<root>/dist/index.js`). Refuses by name when that is not the package root.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} rootExportUrl
|
|
115
|
+
*/
|
|
116
|
+
export function codingAgentDistFrom(rootExportUrl) {
|
|
117
|
+
const entry = rootExportUrl.startsWith("file:") ? fileURLToPath(rootExportUrl) : "";
|
|
118
|
+
const dist = path.dirname(entry);
|
|
119
|
+
const root = path.dirname(dist);
|
|
120
|
+
const manifest = path.join(root, "package.json");
|
|
121
|
+
if (path.basename(entry) !== "index.js" || path.basename(dist) !== "dist" || !existsSync(manifest)) {
|
|
122
|
+
throw refuse(
|
|
123
|
+
"pi-durable-carrier-host-root",
|
|
124
|
+
`${CODING_AGENT} resolved to ${rootExportUrl}, not <root>/dist/index.js`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const name = JSON.parse(readFileSync(manifest, "utf8")).name;
|
|
128
|
+
if (name !== CODING_AGENT) {
|
|
129
|
+
throw refuse("pi-durable-carrier-host-root", `${manifest} names ${JSON.stringify(name)}, not ${CODING_AGENT}`);
|
|
130
|
+
}
|
|
131
|
+
return dist;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve one reserved specifier. Only a declared carrier file may ask, only for a declared target,
|
|
136
|
+
* and the target file must exist. Returns the file URL; everything else throws by name.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} specifier begins with CARRIER_SCHEME
|
|
139
|
+
* @param {string | undefined} parentURL
|
|
140
|
+
* @param {{ carrierDirUrl: string, files: readonly string[], targets: readonly string[], dist: () => string }} declared
|
|
141
|
+
*/
|
|
142
|
+
export function resolveCarrierSpecifier(specifier, parentURL, { carrierDirUrl, files, targets, dist }) {
|
|
143
|
+
const parent =
|
|
144
|
+
typeof parentURL === "string" && parentURL.startsWith(carrierDirUrl) ? parentURL.slice(carrierDirUrl.length) : "";
|
|
145
|
+
if (!files.includes(parent)) {
|
|
146
|
+
throw refuse(
|
|
147
|
+
"pi-durable-carrier-specifier-origin",
|
|
148
|
+
`${specifier} asked from ${parentURL ?? "no parent"}, not a carrier file`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const target = specifier.slice(CARRIER_SCHEME.length);
|
|
152
|
+
if (!targets.includes(target)) {
|
|
153
|
+
throw refuse("pi-durable-carrier-specifier-unknown", `${specifier} (from ${parent}) is not a declared target`);
|
|
154
|
+
}
|
|
155
|
+
const file = path.join(dist(), target);
|
|
156
|
+
if (!existsSync(file)) {
|
|
157
|
+
throw refuse("pi-durable-carrier-target-missing", `${specifier} → ${file} does not exist`);
|
|
158
|
+
}
|
|
159
|
+
return pathToFileURL(file).href;
|
|
160
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// The pi-durable carrier's resolver (#130): preloaded as `node --import <this file>` before the
|
|
2
|
+
// packaged bootstrap. It registers ONE synchronous resolve hook that answers only the carrier's
|
|
3
|
+
// reserved entwurf-pi-dist: specifiers (grammar and refusals: ./carrier-relocation.mjs) and hands
|
|
4
|
+
// every other specifier to Node unchanged. It is not a source/TypeScript resolver and it never reads
|
|
5
|
+
// a checkout, a runtime directory or an environment variable: the pi-coding-agent it maps into is the
|
|
6
|
+
// one the carrier itself resolves from where this package is installed.
|
|
7
|
+
|
|
8
|
+
import { registerHooks } from "node:module";
|
|
9
|
+
import {
|
|
10
|
+
CARRIER_SCHEME,
|
|
11
|
+
CODING_AGENT,
|
|
12
|
+
codingAgentDistFrom,
|
|
13
|
+
readCarrierDeclaration,
|
|
14
|
+
resolveCarrierSpecifier,
|
|
15
|
+
} from "./carrier-relocation.mjs";
|
|
16
|
+
|
|
17
|
+
const { files, targets } = readCarrierDeclaration(new URL("./overlay/upstream-pin.json", import.meta.url));
|
|
18
|
+
const carrierDirUrl = new URL("./carrier/", import.meta.url).href;
|
|
19
|
+
let dist;
|
|
20
|
+
|
|
21
|
+
registerHooks({
|
|
22
|
+
resolve(specifier, context, nextResolve) {
|
|
23
|
+
if (!specifier.startsWith(CARRIER_SCHEME)) return nextResolve(specifier, context);
|
|
24
|
+
const url = resolveCarrierSpecifier(specifier, context.parentURL, {
|
|
25
|
+
carrierDirUrl,
|
|
26
|
+
files,
|
|
27
|
+
targets,
|
|
28
|
+
// From a carrier file, as the carrier's own bare imports resolve — never the exports map.
|
|
29
|
+
dist: () => {
|
|
30
|
+
dist ??= codingAgentDistFrom(
|
|
31
|
+
nextResolve(CODING_AGENT, { ...context, parentURL: `${carrierDirUrl}${files[0]}` }).url,
|
|
32
|
+
);
|
|
33
|
+
return dist;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return { url, shortCircuit: true };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
diff --git a/packages/coding-agent/src/experimental/durable/runtime.ts b/packages/coding-agent/src/experimental/durable/runtime.ts
|
|
2
|
+
index fcd6e523..929dbc79 100644
|
|
3
|
+
--- a/packages/coding-agent/src/experimental/durable/runtime.ts
|
|
4
|
+
+++ b/packages/coding-agent/src/experimental/durable/runtime.ts
|
|
5
|
+
@@ -8,7 +8,9 @@ import {
|
|
6
|
+
type ConversationView,
|
|
7
|
+
type Cursor,
|
|
8
|
+
type EntryRecord,
|
|
9
|
+
+ type Extension,
|
|
10
|
+
Harness,
|
|
11
|
+
+ type InputSubmissionDraft,
|
|
12
|
+
type ModelRef,
|
|
13
|
+
ROOT_CONVERSATION_ID,
|
|
14
|
+
type Submission,
|
|
15
|
+
@@ -81,6 +83,10 @@ export interface DurableController {
|
|
16
|
+
export interface OpenDurableOptions {
|
|
17
|
+
readonly cwd?: string;
|
|
18
|
+
readonly continueSession?: boolean;
|
|
19
|
+
+ /** Installed after the built-in extensions and before the Harness opens, so recovered work finds their tools. */
|
|
20
|
+
+ readonly extensions?: readonly Extension[];
|
|
21
|
+
+ /** A new session's explicit model, resolved as pi's `--provider`/`--model`; unresolved, the open fails. */
|
|
22
|
+
+ readonly model?: { readonly provider: string; readonly model: string };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface OpenDurableResult {
|
|
26
|
+
@@ -88,6 +94,8 @@ export interface OpenDurableResult {
|
|
27
|
+
readonly controller: DurableController;
|
|
28
|
+
/** pi's settings, for the TUI's theme and terminal capabilities. */
|
|
29
|
+
readonly settings: SettingsManager;
|
|
30
|
+
+ /** Admits input to the root conversation, whichever one is on screen; resolves at admission, not at the answer. */
|
|
31
|
+
+ submitToRoot(draft: InputSubmissionDraft): Promise<Submission>;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@@ -122,6 +130,9 @@ function titleOf(entry: EntryRecord | undefined): { title?: string } {
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function openDurable(options: OpenDurableOptions = {}): Promise<OpenDurableResult> {
|
|
39
|
+
+ if (options.model !== undefined && options.continueSession) {
|
|
40
|
+
+ throw new Error("An explicit model applies only to a new session, not to a continued one");
|
|
41
|
+
+ }
|
|
42
|
+
const location = await selectSession(options.cwd ?? process.cwd(), options.continueSession ?? false);
|
|
43
|
+
const envs = new ExecutionEnvs(location.cwd);
|
|
44
|
+
let harness: Harness | undefined;
|
|
45
|
+
@@ -132,6 +143,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
|
|
46
|
+
const settings = createHarnessSettings(settingsManager);
|
|
47
|
+
const registry = createCodingRegistry(settingsManager, location.cwd);
|
|
48
|
+
registry.install(Subagent);
|
|
49
|
+
+ for (const extension of options.extensions ?? []) registry.install(extension);
|
|
50
|
+
|
|
51
|
+
const pendingReports: unknown[] = [];
|
|
52
|
+
let report: (error: unknown) => void = (error) => pendingReports.push(error);
|
|
53
|
+
@@ -146,7 +158,29 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
|
|
54
|
+
},
|
|
55
|
+
context,
|
|
56
|
+
);
|
|
57
|
+
- const initial = location.created ? await findInitialAgentModel(settingsManager, modelRuntime) : undefined;
|
|
58
|
+
+ const initial = location.created
|
|
59
|
+
+ ? await findInitialAgentModel(settingsManager, modelRuntime, options.model)
|
|
60
|
+
+ : undefined;
|
|
61
|
+
+ if (options.model !== undefined) {
|
|
62
|
+
+ // pi resolves case-insensitive, partial and unknown ids too (a custom-id fallback with only a warning);
|
|
63
|
+
+ // an explicit model must be exactly one registered catalog model, named by its canonical id.
|
|
64
|
+
+ const asked = options.model;
|
|
65
|
+
+ const ref = initial?.model;
|
|
66
|
+
+ const thinking = initial?.thinkingLevel;
|
|
67
|
+
+ const registered = ref !== undefined && modelRuntime.getModel(ref.provider, ref.modelId) !== undefined;
|
|
68
|
+
+ const named =
|
|
69
|
+
+ ref !== undefined &&
|
|
70
|
+
+ (asked.model === ref.modelId || (thinking !== undefined && asked.model === `${ref.modelId}:${thinking}`));
|
|
71
|
+
+ if (ref === undefined || ref.provider !== asked.provider || !registered || !named) {
|
|
72
|
+
+ const resolved =
|
|
73
|
+
+ ref === undefined
|
|
74
|
+
+ ? "none"
|
|
75
|
+
+ : `${ref.provider}/${ref.modelId}${thinking === undefined ? "" : `:${thinking}`}`;
|
|
76
|
+
+ throw new Error(
|
|
77
|
+
+ `explicit-model-not-exact: asked ${asked.provider}/${asked.model}, resolved ${resolved} (registered=${registered})`,
|
|
78
|
+
+ );
|
|
79
|
+
+ }
|
|
80
|
+
+ }
|
|
81
|
+
const root = await harness.root(context, {
|
|
82
|
+
agent: {
|
|
83
|
+
cwd: location.cwd,
|
|
84
|
+
@@ -342,6 +376,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
|
|
85
|
+
},
|
|
86
|
+
controller,
|
|
87
|
+
settings: settingsManager,
|
|
88
|
+
+ submitToRoot: (draft) => root.submit(draft, context),
|
|
89
|
+
close() {
|
|
90
|
+
closing ??= (async () => {
|
|
91
|
+
unsubscribe();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"comment": "The one upstream commit the pi-durable carrier is emitted from, the patch it applies, the emitted files and the published SDK set they run against. The experimental durable app is source-only upstream (published pi-coding-agent ships no dist/experimental), so Entwurf ships it as a CARRIER: the pinned app files, type-stripped, with the relocation in pi/pi-durable/carrier-relocation.mjs. scripts/emit-pi-durable-carrier.ts is the only writer of the generated fields (blob, sha256, emitter.node) and the only reader of upstream source; an installed package never reads source. Model data rides the published @earendil-works/pi-ai.",
|
|
4
|
+
"repository": "https://github.com/earendil-works/pi",
|
|
5
|
+
"tag": "v1.0.4",
|
|
6
|
+
"commit": "7c10bd4337495ee613f2224843ecdf349b80d1df",
|
|
7
|
+
"patches": ["runtime-contacts.patch"],
|
|
8
|
+
"carrier": {
|
|
9
|
+
"comment": "files: the six app files bootstrap.mjs needs (main.ts is replaced by the bootstrap). distTargets: every pi-coding-agent internal the stripped files import, as paths under the published dist/; the emitter refuses a set that differs and the resolver answers no other.",
|
|
10
|
+
"emitter": {
|
|
11
|
+
"tool": "node:module.stripTypeScriptTypes",
|
|
12
|
+
"mode": "strip",
|
|
13
|
+
"node": "v24.18.1"
|
|
14
|
+
},
|
|
15
|
+
"license": {
|
|
16
|
+
"source": "LICENSE",
|
|
17
|
+
"blob": "b0a8e9b81083294360c69b4ec45d3d39a2b28197",
|
|
18
|
+
"sha256": "0457f5bcec3b3b211605dfb5d1a49042fd638f3686a410fe099c24a25af13c48"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"name": "runtime.js",
|
|
23
|
+
"source": "packages/coding-agent/src/experimental/durable/runtime.ts",
|
|
24
|
+
"patched": true,
|
|
25
|
+
"blob": "fcd6e5239e999ebf17dea0335fb3dc9cd7f7ce45",
|
|
26
|
+
"sha256": "ca0ee10dc904a57d46bb33b37fcc4efe48dfa08b2f0d10e79e7bc22136871f0a"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "tui.js",
|
|
30
|
+
"source": "packages/coding-agent/src/experimental/durable/tui.ts",
|
|
31
|
+
"patched": false,
|
|
32
|
+
"blob": "397b276cbeb1c90e1fbc96c3cc59e6a25f92dad7",
|
|
33
|
+
"sha256": "bef1ea4bd6e8ab150273752a9b94552d8ce672b518ba3d1ed1a209b8405be1b3"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "harness-setup.js",
|
|
37
|
+
"source": "packages/coding-agent/src/experimental/durable/harness-setup.ts",
|
|
38
|
+
"patched": false,
|
|
39
|
+
"blob": "4d6a4be5333ebb8e0f17eac1987f18ea35a91fc7",
|
|
40
|
+
"sha256": "712b630145dc6f5c655a85b01b1e20299c962542e63c5494d2202bb7bf414a15"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "prompt.js",
|
|
44
|
+
"source": "packages/coding-agent/src/experimental/durable/prompt.ts",
|
|
45
|
+
"patched": false,
|
|
46
|
+
"blob": "8e2f5d5273fba58da8b314766956ed62951c3814",
|
|
47
|
+
"sha256": "ff1ff765fa313e5cb1c3d7537c901c23adb0562ee433bd050fd35eb3d892134c"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "sessions.js",
|
|
51
|
+
"source": "packages/coding-agent/src/experimental/durable/sessions.ts",
|
|
52
|
+
"patched": false,
|
|
53
|
+
"blob": "34f9ceeeaeaf874f7478d56115da2e4f726c6a85",
|
|
54
|
+
"sha256": "1bd1a04ece66f5ab3ca22860c64dba9abc848334d11a27a9afe543f1365c7254"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "subagent.js",
|
|
58
|
+
"source": "packages/coding-agent/src/experimental/durable/subagent.ts",
|
|
59
|
+
"patched": false,
|
|
60
|
+
"blob": "e767d54e0bf2908e0f4f41c6933b55c00562eb25",
|
|
61
|
+
"sha256": "8a75d934ef87efbb0bb2006a2141f1ea932560851f1718b991ed947d6efdb54c"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"distTargets": [
|
|
65
|
+
"config.js",
|
|
66
|
+
"core/http-dispatcher.js",
|
|
67
|
+
"core/keybindings.js",
|
|
68
|
+
"core/model-resolver.js",
|
|
69
|
+
"core/model-runtime.js",
|
|
70
|
+
"core/resource-loader.js",
|
|
71
|
+
"core/settings-manager.js",
|
|
72
|
+
"core/skills.js",
|
|
73
|
+
"core/system-prompt.js",
|
|
74
|
+
"core/tools/bash.js",
|
|
75
|
+
"core/tools/edit.js",
|
|
76
|
+
"core/tools/read.js",
|
|
77
|
+
"core/tools/renderers/index.js",
|
|
78
|
+
"core/tools/write.js",
|
|
79
|
+
"modes/interactive/components/assistant-message.js",
|
|
80
|
+
"modes/interactive/components/custom-editor.js",
|
|
81
|
+
"modes/interactive/components/dynamic-border.js",
|
|
82
|
+
"modes/interactive/components/footer.js",
|
|
83
|
+
"modes/interactive/components/keybinding-hints.js",
|
|
84
|
+
"modes/interactive/components/status-indicator.js",
|
|
85
|
+
"modes/interactive/components/tool-execution.js",
|
|
86
|
+
"modes/interactive/components/user-message.js",
|
|
87
|
+
"modes/interactive/theme/theme-controller.js",
|
|
88
|
+
"modes/interactive/theme/theme.js"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"sdk": {
|
|
92
|
+
"comment": "The published library set the carrier runs against, derived from the 1.0.4 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare. direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.0.4 pins). Each is an exact production dependency in package.json; the members' own edges are caret ranges, so the installed physical edges, not semver, are what must equal these versions.",
|
|
93
|
+
"members": {
|
|
94
|
+
"@earendil-works/chord": "1.0.4",
|
|
95
|
+
"@earendil-works/pi-agent-core": "1.0.4",
|
|
96
|
+
"@earendil-works/pi-ai": "1.0.4",
|
|
97
|
+
"@earendil-works/pi-codemode": "1.0.4",
|
|
98
|
+
"@earendil-works/pi-coding-agent": "1.0.4",
|
|
99
|
+
"@earendil-works/pi-durable": "1.0.4",
|
|
100
|
+
"@earendil-works/pi-mcp": "1.0.4",
|
|
101
|
+
"@earendil-works/pi-telemetry": "1.0.4",
|
|
102
|
+
"@earendil-works/pi-tui": "1.0.4"
|
|
103
|
+
},
|
|
104
|
+
"direct": {
|
|
105
|
+
"proper-lockfile": "4.1.2"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -142,7 +142,13 @@ export function receiverMarkerMatchesIdentity(
|
|
|
142
142
|
// previous garden in-process on its `/new` edge (the unarm the claude hook lacked),
|
|
143
143
|
// so the cell this join closes has a different owner there. Admitting it needs its
|
|
144
144
|
// own measurement, not this file's optimism.
|
|
145
|
-
|
|
145
|
+
//
|
|
146
|
+
// `pi-durable-host` is admitted because the join holds by construction: the durable contact
|
|
147
|
+
// writes its sender marker under the host's own pid and arms its watch in that same process
|
|
148
|
+
// (`meta-bridge-pi-durable.ts`), so the receiver marker's `ownerPid` IS the sender-marker key.
|
|
149
|
+
// Inside the scope a host whose sender marker is gone or names another garden is a retired
|
|
150
|
+
// watch, which is the refusal this admission exists to make reachable.
|
|
151
|
+
export const SENDER_JOINED_RECEIVER_OWNER_KINDS: readonly string[] = ["claude-code-cli", "pi-durable-host"];
|
|
146
152
|
|
|
147
153
|
/** Does this watch owner share its pid with the backend's sender marker? */
|
|
148
154
|
export function receiverOwnerKindJoinsSender(ownerKind: string): boolean {
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
import { randomBytes } from "node:crypto";
|
|
26
26
|
|
|
27
27
|
/** The backends this rail can open. Fixed set, not a profile — a further one is a decision,
|
|
28
|
-
* not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C,
|
|
29
|
-
* by #95 after its system birth and app-server rails were measured
|
|
28
|
+
* not a config entry. `copilot` was added by #82 RAIL 9, `omp` by #87 Bundle C, `codex`
|
|
29
|
+
* by #95 after its system birth and app-server rails were measured, and `pi-durable` by #129
|
|
30
|
+
* (the durable app this package carries, through the managed `entwurf pi-durable`). The set is joined to the
|
|
30
31
|
* citizen backends by `check-harness-admission-parity`: a harness that mints records but is
|
|
31
32
|
* missing HERE is not an unwired convenience, it is a release blocker. */
|
|
32
|
-
export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "codex"] as const;
|
|
33
|
+
export const FRESH_CALL_BACKENDS = ["pi", "claude-code", "copilot", "omp", "codex", "pi-durable"] as const;
|
|
33
34
|
export type FreshCallBackend = (typeof FRESH_CALL_BACKENDS)[number];
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -68,6 +69,11 @@ export const FRESH_CALL_CALLBACK_TOOL: Record<FreshCallBackend, string> = {
|
|
|
68
69
|
// Codex's tool process is the operator app-server, not the pane: the no-arg
|
|
69
70
|
// verb refuses there by name. First action stays the delivery verb with args.
|
|
70
71
|
codex: "mcp__entwurf_bridge__entwurf_v2",
|
|
72
|
+
// The durable contact registers the bridge verbs as its OWN tools under their bare names, and the
|
|
73
|
+
// app offers a tool to the model by its registered name (`durable/harness/registry.ts:99`
|
|
74
|
+
// @cd32f77). Measured for `entwurf_v2` on the S+native-H wire (receipt 4012c066); the callback
|
|
75
|
+
// spelling follows the same rule and is owed its first LIVE observation.
|
|
76
|
+
"pi-durable": "entwurf_callback",
|
|
71
77
|
};
|
|
72
78
|
|
|
73
79
|
/** Where a sibling SENDS the task result. Always the delivery verb, never the birth callback. */
|
|
@@ -77,6 +83,7 @@ export const FRESH_CALL_DELIVERY_TOOL: Record<FreshCallBackend, string> = {
|
|
|
77
83
|
copilot: "entwurf-bridge-entwurf_v2",
|
|
78
84
|
omp: "mcp__entwurf_bridge_entwurf_v",
|
|
79
85
|
codex: "mcp__entwurf_bridge__entwurf_v2",
|
|
86
|
+
"pi-durable": "entwurf_v2",
|
|
80
87
|
};
|
|
81
88
|
|
|
82
89
|
/**
|
|
@@ -99,6 +106,7 @@ export const FRESH_CALL_PEERS_TOOL: Record<FreshCallBackend, string> = {
|
|
|
99
106
|
copilot: "entwurf-bridge-entwurf_peers",
|
|
100
107
|
omp: "mcp__entwurf_bridge_entwurf_peers",
|
|
101
108
|
codex: "mcp__entwurf_bridge__entwurf_peers",
|
|
109
|
+
"pi-durable": "entwurf_peers",
|
|
102
110
|
};
|
|
103
111
|
|
|
104
112
|
/**
|
|
@@ -139,13 +147,16 @@ export const FRESH_CALL_TOOL_LOAD_HINT: Record<FreshCallBackend, readonly string
|
|
|
139
147
|
copilot: [],
|
|
140
148
|
omp: [],
|
|
141
149
|
codex: [],
|
|
150
|
+
"pi-durable": [],
|
|
142
151
|
};
|
|
143
152
|
|
|
144
153
|
/**
|
|
145
|
-
* What a launch has to say, in the two shapes the
|
|
146
|
-
* handed
|
|
147
|
-
*
|
|
148
|
-
*
|
|
154
|
+
* What a launch has to say, in the two shapes the six backends need. pi, claude-code, copilot
|
|
155
|
+
* and codex are handed the first-turn PROMPT as an argument; pi-durable is handed that same
|
|
156
|
+
* PROMPT inside the closed `{v,task}` payload on the bootstrap flag, which its packaged bootstrap
|
|
157
|
+
* decodes; omp is handed its own bootstrap PAYLOAD, which its installed extension unpacks. Both
|
|
158
|
+
* are always built, because building one is cheap and a backend switch must never be able to
|
|
159
|
+
* reach a field that was not composed.
|
|
149
160
|
*/
|
|
150
161
|
export interface FreshCallComposition {
|
|
151
162
|
prompt: string;
|
|
@@ -339,6 +350,30 @@ export function composeBackendArgs(
|
|
|
339
350
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
340
351
|
composition.prompt,
|
|
341
352
|
];
|
|
353
|
+
case "pi-durable": {
|
|
354
|
+
// The managed verb (`entwurf pi-durable`) carries the explicit model as pi's own
|
|
355
|
+
// `--provider`/`--model` pair, split at the FIRST `/` (the preflight already refused a model
|
|
356
|
+
// with no provider), the explicit width, and the WHOLE framing as the closed `{v,task}`
|
|
357
|
+
// payload — the durable bootstrap submits it to the root once, after the birth. The
|
|
358
|
+
// caller's address rides the env pair, never this payload.
|
|
359
|
+
const at = model.indexOf("/");
|
|
360
|
+
if (at <= 0 || at === model.length - 1) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`fresh-call composition: pi-durable model ${JSON.stringify(model)} is not <provider>/<model id>`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
return [
|
|
366
|
+
"pi-durable",
|
|
367
|
+
"--provider",
|
|
368
|
+
model.slice(0, at),
|
|
369
|
+
"--model",
|
|
370
|
+
model.slice(at + 1),
|
|
371
|
+
"--width",
|
|
372
|
+
"task-wide",
|
|
373
|
+
`--${OMP_BOOTSTRAP_FLAG}`,
|
|
374
|
+
JSON.stringify({ v: OMP_BOOTSTRAP_VERSION, task: composition.prompt }),
|
|
375
|
+
];
|
|
376
|
+
}
|
|
342
377
|
}
|
|
343
378
|
}
|
|
344
379
|
|
|
@@ -73,7 +73,13 @@ import { type NativePushAdapter, resolveNativePushAdapter } from "./native-push/
|
|
|
73
73
|
* exactly the #46 defect on agy, and copilot joined the list only once its own hook wrote one
|
|
74
74
|
* (#82 RAIL 5b). Membership says a marker may EXIST, never that a reply can land: the reply
|
|
75
75
|
* rail is chosen from `nativePushSupported` at the bridge, not from this list. */
|
|
76
|
-
export const META_SENDER_BACKENDS: readonly MetaBackend[] = [
|
|
76
|
+
export const META_SENDER_BACKENDS: readonly MetaBackend[] = [
|
|
77
|
+
"claude-code",
|
|
78
|
+
"antigravity",
|
|
79
|
+
"copilot",
|
|
80
|
+
"omp",
|
|
81
|
+
"pi-durable",
|
|
82
|
+
];
|
|
77
83
|
|
|
78
84
|
/** A marker that passed BOTH guards, together with the record that vouches for it. */
|
|
79
85
|
export interface TrustedMetaSender {
|
|
@@ -81,7 +81,7 @@ export class MetaRecordError extends Error {
|
|
|
81
81
|
* every write, including its own. `./run.sh doctor-meta-bridge` already flags a
|
|
82
82
|
* stale deployed writer — run it after the change.
|
|
83
83
|
*/
|
|
84
|
-
export const META_BACKENDS = ["claude-code", "antigravity", "codex", "copilot", "omp"] as const;
|
|
84
|
+
export const META_BACKENDS = ["claude-code", "antigravity", "codex", "copilot", "omp", "pi-durable"] as const;
|
|
85
85
|
export type MetaBackend = (typeof META_BACKENDS)[number];
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -222,6 +222,24 @@ export const META_BACKEND_DESCRIPTORS: Record<MetaBackend, MetaBackendDescriptor
|
|
|
222
222
|
deliveryLevel: "D6",
|
|
223
223
|
nativeIdLabel: "sessionId",
|
|
224
224
|
},
|
|
225
|
+
// The long-running durable app (#129). Graded only as far as its RECORDED receipts reach.
|
|
226
|
+
// sessionId the durable session directory name `<13-digit ms>-<uuid>` that
|
|
227
|
+
// `selectSession` creates and `--continue` reopens
|
|
228
|
+
// (`experimental/durable/sessions.ts:18-55` @cd32f77), surfaced as
|
|
229
|
+
// `view.current().session.id`. Not `pi.provider.sessionId`, which a
|
|
230
|
+
// fork changes.
|
|
231
|
+
// D2 receiver armed: the contact mints the record, names the host as sender, and arms
|
|
232
|
+
// a root doorbell (the receiver marker is owned by the host pid as `pi-durable-host`,
|
|
233
|
+
// which the owner join admits). The addressed-enqueue → idle-wake → root-notice → drain
|
|
234
|
+
// chain has ONE recorded receipt, on the scripted-model + native-harness lab cell
|
|
235
|
+
// (`check-pi-durable-receive`, S+H, V not run) — lab evidence, not a vendor-turn D4–D6
|
|
236
|
+
// acceptance, and no sibling-isolation (D3) or LIVE fresh receipt exists yet.
|
|
237
|
+
"pi-durable": {
|
|
238
|
+
backend: "pi-durable",
|
|
239
|
+
wakeMode: "self-fetch",
|
|
240
|
+
deliveryLevel: "D2",
|
|
241
|
+
nativeIdLabel: "sessionId",
|
|
242
|
+
},
|
|
225
243
|
};
|
|
226
244
|
|
|
227
245
|
// ---------------------------------------------------------------------------
|
|
@@ -305,7 +323,15 @@ function isoNow(now: Date): string {
|
|
|
305
323
|
export const META_SCHEMA_VERSION_V3 = 3 as const;
|
|
306
324
|
|
|
307
325
|
/** Every backend admitted by the one V3 record-citizen schema. */
|
|
308
|
-
export const META_CITIZEN_BACKENDS = [
|
|
326
|
+
export const META_CITIZEN_BACKENDS = [
|
|
327
|
+
"claude-code",
|
|
328
|
+
"antigravity",
|
|
329
|
+
"codex",
|
|
330
|
+
"copilot",
|
|
331
|
+
"omp",
|
|
332
|
+
"pi-durable",
|
|
333
|
+
"pi",
|
|
334
|
+
] as const;
|
|
309
335
|
export type MetaCitizenBackend = (typeof META_CITIZEN_BACKENDS)[number];
|
|
310
336
|
|
|
311
337
|
/**
|
|
@@ -143,6 +143,12 @@ import {
|
|
|
143
143
|
} from "./mux-placement.ts";
|
|
144
144
|
import { resolveCodexDefaultSocketPath } from "./native-push/codex-ws-client.ts";
|
|
145
145
|
import { OMP_PREFLIGHT_HINT, type OmpPreflightRejectReason, ompFreshPreflight } from "./omp-fresh-preflight.ts";
|
|
146
|
+
import {
|
|
147
|
+
PI_DURABLE_PREFLIGHT_HINT,
|
|
148
|
+
type PiDurablePreflightRejectReason,
|
|
149
|
+
piDurableFreshPreflight,
|
|
150
|
+
} from "./pi-durable-fresh-preflight.ts";
|
|
151
|
+
import { checkPiDurableRuntime, PI_DURABLE_RUNTIME_HINT, piDurablePackageLayout } from "./pi-durable-runtime.ts";
|
|
146
152
|
import { classifyTmuxSessionName, resolveTmuxSessionId, type TmuxSessionRejectReason } from "./resolve-tmux-session.ts";
|
|
147
153
|
|
|
148
154
|
/**
|
|
@@ -238,6 +244,10 @@ export const FRESH_CALL_RUNTIME: Record<FreshCallBackend, string> = {
|
|
|
238
244
|
copilot: "entwurf",
|
|
239
245
|
omp: "omp",
|
|
240
246
|
codex: "codex",
|
|
247
|
+
// #129/#130: like copilot, a MANAGED verb and never the bare app. The durable app is this
|
|
248
|
+
// package's carrier and runs under the carrier's resolver; `entwurf pi-durable` is the one fixed
|
|
249
|
+
// invocation that verifies the carrier and its SDK set and execs the packaged bootstrap.
|
|
250
|
+
"pi-durable": "entwurf",
|
|
241
251
|
};
|
|
242
252
|
|
|
243
253
|
/** A launch that was refused, or a placement that could not be established. Every value is a
|
|
@@ -251,6 +261,7 @@ export type FreshCallRejectReason =
|
|
|
251
261
|
| TmuxSessionRejectReason
|
|
252
262
|
| CopilotPreflightRejectReason
|
|
253
263
|
| OmpPreflightRejectReason
|
|
264
|
+
| PiDurablePreflightRejectReason
|
|
254
265
|
| CodexPreflightRejectReason
|
|
255
266
|
| CodexCallerPreflightRejectReason
|
|
256
267
|
| CodexCallerSeatRejectReason
|
|
@@ -350,9 +361,9 @@ export type FreshCallResult = { ok: true; receipt: FreshCallReceipt } | { ok: fa
|
|
|
350
361
|
* for. That is sufficient and not a compromise: every reader of the carrier trims and tests
|
|
351
362
|
* truthiness (`index.ts:212-217`), so empty and absent are the same answer by construction.
|
|
352
363
|
*
|
|
353
|
-
* It is applied to all
|
|
364
|
+
* It is applied to all six backends because the leak is a property of tmux, not of a vendor. A
|
|
354
365
|
* scrub only on the backend whose measurement surfaced it would encode the claim that the other
|
|
355
|
-
*
|
|
366
|
+
* five are immune, which is false. It costs the legitimate case nothing: a carrier is only ever
|
|
356
367
|
* authoritative when the process that owns it exported it ITSELF, and a fresh `pi` sibling does
|
|
357
368
|
* exactly that after this argv has run. This is a fixed seam and deliberately NOT a general env
|
|
358
369
|
* carrier — an arbitrary `-e` passthrough would hand callers the environment-shaping power this
|
|
@@ -506,6 +517,23 @@ export function freshCall(
|
|
|
506
517
|
const missing = ompFreshPreflight(env);
|
|
507
518
|
if (missing) return { ok: false, reason: missing };
|
|
508
519
|
}
|
|
520
|
+
if (params.backend === "pi-durable") {
|
|
521
|
+
// The framed first input is computed here only to MEASURE it against the bootstrap payload
|
|
522
|
+
// cap; the same pure function composes it again below. The carrier is verified from THIS
|
|
523
|
+
// process's package closure, before anything is placed. The window runs whatever `entwurf`
|
|
524
|
+
// PATH names, and the managed verb re-verifies its own package before it execs anything, so
|
|
525
|
+
// the window never runs an unverified carrier — but when the two package closures differ, this
|
|
526
|
+
// verdict and the window's can disagree, and the window is left showing its own refusal.
|
|
527
|
+
const layout = piDurablePackageLayout();
|
|
528
|
+
const runtime = layout === null ? null : checkPiDurableRuntime(layout);
|
|
529
|
+
const missing = piDurableFreshPreflight({
|
|
530
|
+
model,
|
|
531
|
+
firstInput: buildFreshCallPrompt({ backend: params.backend, task, callerGardenId, nonce }),
|
|
532
|
+
layout,
|
|
533
|
+
runtime: runtime === null || runtime.ok ? null : runtime.reason,
|
|
534
|
+
});
|
|
535
|
+
if (missing) return { ok: false, reason: missing };
|
|
536
|
+
}
|
|
509
537
|
const inspected = inspectPlacement(env);
|
|
510
538
|
if (!inspected.ok) return { ok: false, reason: inspected.reason };
|
|
511
539
|
const placement = inspected.placement;
|
|
@@ -626,6 +654,8 @@ const REJECT_HINT: Record<FreshCallRejectReason, string> = {
|
|
|
626
654
|
// the sentence an operator reads cannot drift away from the predicate that produced it.
|
|
627
655
|
...COPILOT_PREFLIGHT_HINT,
|
|
628
656
|
...OMP_PREFLIGHT_HINT,
|
|
657
|
+
...PI_DURABLE_RUNTIME_HINT,
|
|
658
|
+
...PI_DURABLE_PREFLIGHT_HINT,
|
|
629
659
|
...CODEX_PREFLIGHT_HINT,
|
|
630
660
|
...CODEX_CALLER_PREFLIGHT_HINT,
|
|
631
661
|
...CODEX_CALLER_SEAT_HINT,
|