@tryinget/pi-agent-registry 0.3.1 → 0.3.2
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/README.md +124 -14
- package/docs/engineering.local.md +16 -7
- package/docs/project/2026-08-27-agent-registry.md +86 -12
- package/extensions/pi-agent-registry.ts +3 -0
- package/extensions/standing-agent-spawn.ts +85 -0
- package/package.json +8 -6
- package/src/visible-launch-admission.ts +69 -0
- package/src/visible-launch-bootstrap.ts +100 -0
- package/src/visible-launch-compose.ts +273 -0
- package/src/visible-launch-contract.ts +165 -0
- package/src/visible-launch-inputs.ts +57 -0
- package/src/visible-launch-receipt.ts +276 -0
- package/src/visible-launch-transport.ts +77 -0
- package/src/visible-launch.ts +482 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// summary: exact-task read-only visible admission composition; transport stays little-helpers-owned.
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { AkAuthorizationError, authorizeExactTask, readAkTask } from "./dispatch-authorization.ts";
|
|
4
|
+
import { DISPATCH_CHILD_PROVENANCE_ENV } from "./dispatch-contract.ts";
|
|
5
|
+
import { canonicalJsonString, sha256Hex } from "./dispatch-receipt.ts";
|
|
6
|
+
import { captureFleetGitSnapshot, resolveGitRepoRoot } from "./fleet-git-snapshot.ts";
|
|
7
|
+
import { reserveVisibleLaunchPair } from "./visible-launch-admission.ts";
|
|
8
|
+
import { resolveTrustedVisibleLaunchBootstrap } from "./visible-launch-bootstrap.ts";
|
|
9
|
+
import {
|
|
10
|
+
checkParentPeerTarget,
|
|
11
|
+
composeStandingAgentArgv,
|
|
12
|
+
composeStandingAgentModelArgs,
|
|
13
|
+
composeStandingAgentSpawnPrompt,
|
|
14
|
+
createStandingAgentRunId,
|
|
15
|
+
manifestToolsAreLaunchEligible,
|
|
16
|
+
redactArgvForReceipt,
|
|
17
|
+
standingAgentTitle,
|
|
18
|
+
systemPromptWithinArgvBound,
|
|
19
|
+
} from "./visible-launch-compose.ts";
|
|
20
|
+
import {
|
|
21
|
+
type StandingAgentSpawnDeps,
|
|
22
|
+
type StandingAgentSpawnFailure,
|
|
23
|
+
type StandingAgentSpawnOutcome,
|
|
24
|
+
type StandingAgentSpawnRequest,
|
|
25
|
+
VISIBLE_LAUNCH_CHILD_PROVENANCE_ENV,
|
|
26
|
+
VISIBLE_LAUNCH_PHASE,
|
|
27
|
+
type VisibleLaunchCtx,
|
|
28
|
+
type VisibleLaunchFailureReason,
|
|
29
|
+
type VisibleLaunchTransport,
|
|
30
|
+
} from "./visible-launch-contract.ts";
|
|
31
|
+
import { verifyVisibleLaunchInputs } from "./visible-launch-inputs.ts";
|
|
32
|
+
import {
|
|
33
|
+
buildVisibleLaunchReceiptInput,
|
|
34
|
+
writeImmutableVisibleLaunchReceipt,
|
|
35
|
+
} from "./visible-launch-receipt.ts";
|
|
36
|
+
import {
|
|
37
|
+
createVisibleLaunchDispatchGuard,
|
|
38
|
+
loadVisibleLaunchTransport,
|
|
39
|
+
} from "./visible-launch-transport.ts";
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
StandingAgentSpawnDeps,
|
|
43
|
+
StandingAgentSpawnOutcome,
|
|
44
|
+
VisibleLaunchCtx,
|
|
45
|
+
} from "./visible-launch-contract.ts";
|
|
46
|
+
|
|
47
|
+
/** Admission is NOT session-start proof, ACK evidence, task consumption/completion or claimant authentication. */
|
|
48
|
+
export async function spawnStandingAgentVisible(
|
|
49
|
+
request: StandingAgentSpawnRequest,
|
|
50
|
+
deps: StandingAgentSpawnDeps,
|
|
51
|
+
ctx: VisibleLaunchCtx,
|
|
52
|
+
signal?: AbortSignal,
|
|
53
|
+
): Promise<StandingAgentSpawnOutcome> {
|
|
54
|
+
const fail = (
|
|
55
|
+
reason: VisibleLaunchFailureReason,
|
|
56
|
+
message: string,
|
|
57
|
+
extra: Partial<
|
|
58
|
+
Pick<
|
|
59
|
+
StandingAgentSpawnFailure,
|
|
60
|
+
"effectDisposition" | "spawnAttempted" | "receipt" | "receiptPath" | "runId"
|
|
61
|
+
>
|
|
62
|
+
> = {},
|
|
63
|
+
): StandingAgentSpawnFailure => ({
|
|
64
|
+
ok: false,
|
|
65
|
+
phase: VISIBLE_LAUNCH_PHASE,
|
|
66
|
+
reason,
|
|
67
|
+
message,
|
|
68
|
+
effectDisposition: "confirmed_no_effects",
|
|
69
|
+
spawnAttempted: false,
|
|
70
|
+
...extra,
|
|
71
|
+
});
|
|
72
|
+
if (
|
|
73
|
+
!request ||
|
|
74
|
+
typeof request.agent !== "string" ||
|
|
75
|
+
!/^[a-z][a-z0-9-]{0,63}$/u.test(request.agent) ||
|
|
76
|
+
!Number.isSafeInteger(request.task) ||
|
|
77
|
+
request.task <= 0 ||
|
|
78
|
+
typeof request.objective !== "string" ||
|
|
79
|
+
!request.objective.trim() ||
|
|
80
|
+
Buffer.byteLength(request.objective, "utf8") > 32 * 1024 ||
|
|
81
|
+
!systemPromptWithinArgvBound(request.objective) ||
|
|
82
|
+
(request.cwd !== undefined &&
|
|
83
|
+
(typeof request.cwd !== "string" ||
|
|
84
|
+
!request.cwd.trim() ||
|
|
85
|
+
!systemPromptWithinArgvBound(request.cwd))) ||
|
|
86
|
+
(request.parentPeerTarget !== undefined && typeof request.parentPeerTarget !== "string") ||
|
|
87
|
+
(request.reportBack !== undefined &&
|
|
88
|
+
!["intercom", "manual", "none"].includes(request.reportBack))
|
|
89
|
+
) {
|
|
90
|
+
return fail(
|
|
91
|
+
"invalid_request",
|
|
92
|
+
"Require safe agent, exact positive AK task, and nonblank bounded UTF-8 read-only objective (32 KiB maximum).",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (signal?.aborted) return fail("cancelled", "Cancelled before launch admission.");
|
|
96
|
+
const reportBack = request.reportBack ?? "intercom";
|
|
97
|
+
const target = checkParentPeerTarget(request.parentPeerTarget);
|
|
98
|
+
if (reportBack === "intercom" && !target.ok)
|
|
99
|
+
return fail(
|
|
100
|
+
"invalid_parent_peer_target",
|
|
101
|
+
"Intercom report-back requires the exact controller session id.",
|
|
102
|
+
);
|
|
103
|
+
const parentPeerTarget = reportBack === "intercom" && target.ok ? target.target : undefined;
|
|
104
|
+
if (
|
|
105
|
+
process.env[VISIBLE_LAUNCH_CHILD_PROVENANCE_ENV] ||
|
|
106
|
+
process.env[DISPATCH_CHILD_PROVENANCE_ENV]
|
|
107
|
+
) {
|
|
108
|
+
return fail(
|
|
109
|
+
"recursive_launch",
|
|
110
|
+
"Standing-agent children cannot launch another standing agent.",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const transport =
|
|
114
|
+
deps.transport === null ? undefined : (deps.transport ?? (await loadVisibleLaunchTransport()));
|
|
115
|
+
if (!transport)
|
|
116
|
+
return fail(
|
|
117
|
+
"visible_transport_unavailable",
|
|
118
|
+
"Version-1 little-helpers standing-agent transport is unavailable; fail closed.",
|
|
119
|
+
);
|
|
120
|
+
const cached = deps.registry.get(request.agent);
|
|
121
|
+
if (!cached) return fail("unknown_agent", "Agent is not registered.");
|
|
122
|
+
const manifest = structuredClone(cached);
|
|
123
|
+
if (!manifestToolsAreLaunchEligible(manifest))
|
|
124
|
+
return fail(
|
|
125
|
+
"agent_not_read_only",
|
|
126
|
+
"Declared tools must be a nonempty subset of read,bash; read-only posture is advisory, not a sandbox.",
|
|
127
|
+
);
|
|
128
|
+
if (manifest.extensions.length)
|
|
129
|
+
return fail(
|
|
130
|
+
"manifest_extensions_unapproved",
|
|
131
|
+
"Manifest extensions are not approved in this phase.",
|
|
132
|
+
);
|
|
133
|
+
const parentRoot = await resolveGitRepoRoot(ctx.cwd)
|
|
134
|
+
.then((path) => realpath(path))
|
|
135
|
+
.catch(() => undefined);
|
|
136
|
+
const parentSnapshot = parentRoot
|
|
137
|
+
? await captureFleetGitSnapshot(parentRoot).catch(() => undefined)
|
|
138
|
+
: undefined;
|
|
139
|
+
if (!parentRoot || !parentSnapshot)
|
|
140
|
+
return fail(
|
|
141
|
+
"parent_repo_unobservable",
|
|
142
|
+
"Origin must be one observable Git repository for exact-task authorization.",
|
|
143
|
+
);
|
|
144
|
+
const cwd = await realpath(request.cwd ?? parentRoot).catch(() => undefined);
|
|
145
|
+
const childRoot = cwd
|
|
146
|
+
? await resolveGitRepoRoot(cwd)
|
|
147
|
+
.then((path) => realpath(path))
|
|
148
|
+
.catch(() => undefined)
|
|
149
|
+
: undefined;
|
|
150
|
+
if (!cwd || childRoot !== parentRoot)
|
|
151
|
+
return fail(
|
|
152
|
+
"task_repo_mismatch",
|
|
153
|
+
"Child cwd must remain in the exact task's origin repository.",
|
|
154
|
+
);
|
|
155
|
+
let task: Awaited<ReturnType<typeof readAkTask>>;
|
|
156
|
+
try {
|
|
157
|
+
task = await readAkTask(request.task, { akBinary: deps.akBinary });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return fail(
|
|
160
|
+
error instanceof AkAuthorizationError ? error.code : "ak_unavailable",
|
|
161
|
+
"Exact AK task authorization is unavailable.",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const authorization = authorizeExactTask(task, parentRoot);
|
|
165
|
+
if (!authorization.ok)
|
|
166
|
+
return fail(authorization.code, "Exact task requires a live claim in the origin repository.");
|
|
167
|
+
const agentSnapshot = await captureFleetGitSnapshot(manifest.root).catch(() => undefined);
|
|
168
|
+
if (!agentSnapshot) return fail("agent_repo_drift", "Agent repository could not be captured.");
|
|
169
|
+
if (agentSnapshot.status !== "clean_observed")
|
|
170
|
+
return fail(
|
|
171
|
+
"agent_repo_dirty",
|
|
172
|
+
"Agent repository is dirty; committed launch inputs cannot be proven.",
|
|
173
|
+
);
|
|
174
|
+
if (!(await verifyVisibleLaunchInputs(manifest, deps.registry, agentSnapshot)))
|
|
175
|
+
return fail("agent_repo_drift", "Cached, committed and current agent inputs differ.");
|
|
176
|
+
const committedManifest = await agentSnapshot.readFile("agent.json");
|
|
177
|
+
const committedPrompt = await agentSnapshot.readFile(manifest.system_prompt_file);
|
|
178
|
+
if (!committedManifest || !committedPrompt)
|
|
179
|
+
return fail("agent_repo_drift", "Committed launch inputs unavailable.");
|
|
180
|
+
let launch: Awaited<ReturnType<StandingAgentSpawnDeps["registry"]["resolve"]>>;
|
|
181
|
+
try {
|
|
182
|
+
launch = await deps.registry.resolve(request.agent);
|
|
183
|
+
} catch {
|
|
184
|
+
return fail("agent_resolution_failed", "Agent composition failed; no launch attempted.");
|
|
185
|
+
}
|
|
186
|
+
const cleanup = () => launch.cleanup().catch(() => undefined);
|
|
187
|
+
const reject = async (reason: VisibleLaunchFailureReason, message: string, runId?: string) => {
|
|
188
|
+
await cleanup();
|
|
189
|
+
return fail(reason, message, runId ? { runId } : {});
|
|
190
|
+
};
|
|
191
|
+
const modelArgs = composeStandingAgentModelArgs({ launch, controllerModel: ctx.model });
|
|
192
|
+
const model = modelArgs[modelArgs.indexOf("--model") + 1];
|
|
193
|
+
const bootstrap = modelArgs.includes("--model")
|
|
194
|
+
? await (deps.resolveTrustedBootstrap ?? resolveTrustedVisibleLaunchBootstrap)(model).catch(
|
|
195
|
+
() => undefined,
|
|
196
|
+
)
|
|
197
|
+
: undefined;
|
|
198
|
+
if (!bootstrap || !bootstrap.extensions.length || !bootstrap.bindings.length)
|
|
199
|
+
return reject(
|
|
200
|
+
"bootstrap_unavailable",
|
|
201
|
+
"Trusted ACK/presence/provider bootstrap unavailable. Owner approval required; ambient extensions are never inherited.",
|
|
202
|
+
);
|
|
203
|
+
const runId = createStandingAgentRunId();
|
|
204
|
+
const objective = request.objective.trim();
|
|
205
|
+
const prompt = composeStandingAgentSpawnPrompt({
|
|
206
|
+
manifest,
|
|
207
|
+
runId,
|
|
208
|
+
task: task.id,
|
|
209
|
+
reportBack,
|
|
210
|
+
parentPeerTarget,
|
|
211
|
+
objective,
|
|
212
|
+
cwd,
|
|
213
|
+
});
|
|
214
|
+
const argv = composeStandingAgentArgv({
|
|
215
|
+
launch,
|
|
216
|
+
prompt,
|
|
217
|
+
trustedExtensions: bootstrap.extensions,
|
|
218
|
+
});
|
|
219
|
+
const title = standingAgentTitle(manifest);
|
|
220
|
+
const childProvenanceEnv = {
|
|
221
|
+
[VISIBLE_LAUNCH_CHILD_PROVENANCE_ENV]: `${runId}:ak-${task.id}:${manifest.name}`,
|
|
222
|
+
};
|
|
223
|
+
const composedArgs = [
|
|
224
|
+
"pi",
|
|
225
|
+
...modelArgs,
|
|
226
|
+
...argv.extraPiArgs,
|
|
227
|
+
prompt,
|
|
228
|
+
cwd,
|
|
229
|
+
title,
|
|
230
|
+
...Object.entries(childProvenanceEnv).map(([key, value]) => `${key}=${value}`),
|
|
231
|
+
];
|
|
232
|
+
if (
|
|
233
|
+
composedArgs.length > 900 ||
|
|
234
|
+
!composedArgs.every(systemPromptWithinArgvBound) ||
|
|
235
|
+
composedArgs.reduce((n, value) => n + Buffer.byteLength(value, "utf8") + 1, 0) > 240 * 1024
|
|
236
|
+
) {
|
|
237
|
+
return reject(
|
|
238
|
+
"invalid_argv",
|
|
239
|
+
"Composed arguments violate UTF-8/NUL or transport byte bounds.",
|
|
240
|
+
runId,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
const composedArgvSha256 = sha256Hex(canonicalJsonString(composedArgs));
|
|
244
|
+
if (!(await verifyVisibleLaunchInputs(manifest, deps.registry, agentSnapshot, launch)))
|
|
245
|
+
return reject(
|
|
246
|
+
"agent_repo_drift",
|
|
247
|
+
"Resolved launch differs from immutable agent inputs.",
|
|
248
|
+
runId,
|
|
249
|
+
);
|
|
250
|
+
if (signal?.aborted) return reject("cancelled", "Cancelled before reservation.", runId);
|
|
251
|
+
try {
|
|
252
|
+
const reservation = await reserveVisibleLaunchPair(
|
|
253
|
+
{
|
|
254
|
+
agent: manifest.name,
|
|
255
|
+
task: task.id,
|
|
256
|
+
runId,
|
|
257
|
+
requestSha256: sha256Hex(
|
|
258
|
+
canonicalJsonString({
|
|
259
|
+
task,
|
|
260
|
+
composedArgvSha256,
|
|
261
|
+
manifestSha256: committedManifest.sha256,
|
|
262
|
+
bootstrap: bootstrap.bindings,
|
|
263
|
+
}),
|
|
264
|
+
),
|
|
265
|
+
},
|
|
266
|
+
deps.receiptsDir,
|
|
267
|
+
);
|
|
268
|
+
if (!reservation.reserved)
|
|
269
|
+
return reject(
|
|
270
|
+
"launch_already_reserved",
|
|
271
|
+
"This agent/task pair is reserved or admitted. No automatic retry; explicit owner disposition required.",
|
|
272
|
+
reservation.runId,
|
|
273
|
+
);
|
|
274
|
+
} catch {
|
|
275
|
+
return reject(
|
|
276
|
+
"reservation_failed",
|
|
277
|
+
"Could not durably reserve agent/task pair; no launch attempted.",
|
|
278
|
+
runId,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
// Last awaited gates before transport. The reservation deliberately survives cancellation/drift/crashes.
|
|
282
|
+
let latestTask: typeof task | undefined;
|
|
283
|
+
try {
|
|
284
|
+
latestTask = await readAkTask(request.task, { akBinary: deps.akBinary });
|
|
285
|
+
} catch {
|
|
286
|
+
/* fail closed */
|
|
287
|
+
}
|
|
288
|
+
if (
|
|
289
|
+
!latestTask ||
|
|
290
|
+
canonicalJsonString(latestTask) !== canonicalJsonString(task) ||
|
|
291
|
+
!authorizeExactTask(latestTask, parentRoot).ok
|
|
292
|
+
) {
|
|
293
|
+
return reject(
|
|
294
|
+
"ak_unavailable",
|
|
295
|
+
"Exact task claim changed or became unverifiable before transport.",
|
|
296
|
+
runId,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const [preInputs, preBootstrap] = await Promise.all([
|
|
300
|
+
verifyVisibleLaunchInputs(manifest, deps.registry, agentSnapshot, launch),
|
|
301
|
+
bootstrap.verify().catch(() => false),
|
|
302
|
+
]);
|
|
303
|
+
const [preAgent, preOrigin] = await Promise.all([
|
|
304
|
+
agentSnapshot.finish().catch(() => undefined),
|
|
305
|
+
parentSnapshot.finish().catch(() => undefined),
|
|
306
|
+
]);
|
|
307
|
+
if (
|
|
308
|
+
!preAgent?.stable ||
|
|
309
|
+
!preOrigin?.stable ||
|
|
310
|
+
!preInputs ||
|
|
311
|
+
!preBootstrap ||
|
|
312
|
+
!authorizeExactTask(task, parentRoot).ok
|
|
313
|
+
)
|
|
314
|
+
return reject(
|
|
315
|
+
"agent_repo_drift",
|
|
316
|
+
"Launch inputs or origin drifted immediately before transport; reservation retained.",
|
|
317
|
+
runId,
|
|
318
|
+
);
|
|
319
|
+
if (signal?.aborted)
|
|
320
|
+
return reject(
|
|
321
|
+
"cancelled",
|
|
322
|
+
"Cancelled immediately before transport; reservation retained.",
|
|
323
|
+
runId,
|
|
324
|
+
);
|
|
325
|
+
let transportResult: Awaited<ReturnType<VisibleLaunchTransport["launchPiQuestSession"]>>;
|
|
326
|
+
try {
|
|
327
|
+
transportResult = await transport.launchPiQuestSession({
|
|
328
|
+
// Additive guarded transport fields; production loader rejects older version-1 modules.
|
|
329
|
+
...createVisibleLaunchDispatchGuard(task, parentRoot, deps.akBinary),
|
|
330
|
+
pi: deps.pi,
|
|
331
|
+
ctx: { cwd: ctx.cwd },
|
|
332
|
+
options: {},
|
|
333
|
+
defaultPiBin: "pi",
|
|
334
|
+
prompt,
|
|
335
|
+
titlePrompt: title,
|
|
336
|
+
titlePrefix: "Standing",
|
|
337
|
+
cwd,
|
|
338
|
+
modelArgs,
|
|
339
|
+
extraPiArgs: argv.extraPiArgs,
|
|
340
|
+
childProvenanceEnv,
|
|
341
|
+
signal,
|
|
342
|
+
});
|
|
343
|
+
} catch {
|
|
344
|
+
// A thrown transport can already have admitted a child. Keep skills and the reservation.
|
|
345
|
+
transportResult = {
|
|
346
|
+
ok: false,
|
|
347
|
+
effectDisposition: "effect_indeterminate",
|
|
348
|
+
launchMode: "unknown",
|
|
349
|
+
sessionMode: "clean",
|
|
350
|
+
cwd,
|
|
351
|
+
titleBase: title,
|
|
352
|
+
promptSummary: "",
|
|
353
|
+
failure: "transport_threw",
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const disposition = transportResult?.effectDisposition;
|
|
357
|
+
const effectDisposition: StandingAgentSpawnFailure["effectDisposition"] =
|
|
358
|
+
transportResult?.ok === true && disposition === "settled"
|
|
359
|
+
? "settled"
|
|
360
|
+
: transportResult?.ok === false && disposition === "confirmed_no_effects"
|
|
361
|
+
? "confirmed_no_effects"
|
|
362
|
+
: "effect_indeterminate";
|
|
363
|
+
const admitted = transportResult?.ok === true && effectDisposition === "settled";
|
|
364
|
+
const noEffects = transportResult?.ok === false && effectDisposition === "confirmed_no_effects";
|
|
365
|
+
if (noEffects) await cleanup();
|
|
366
|
+
const [postInputs, postBootstrap] = await Promise.all([
|
|
367
|
+
verifyVisibleLaunchInputs(manifest, deps.registry, agentSnapshot, launch),
|
|
368
|
+
bootstrap.verify().catch(() => false),
|
|
369
|
+
]);
|
|
370
|
+
const [postAgent, postOrigin] = await Promise.all([
|
|
371
|
+
agentSnapshot.finish().catch(() => undefined),
|
|
372
|
+
parentSnapshot.finish().catch(() => undefined),
|
|
373
|
+
]);
|
|
374
|
+
const stable =
|
|
375
|
+
postAgent?.stable === true && postOrigin?.stable === true && postInputs && postBootstrap;
|
|
376
|
+
const receiptInput = buildVisibleLaunchReceiptInput({
|
|
377
|
+
agent: {
|
|
378
|
+
name: manifest.name,
|
|
379
|
+
...(manifest.role ? { role: manifest.role } : {}),
|
|
380
|
+
...(manifest.creation_task ? { creation_task: manifest.creation_task } : {}),
|
|
381
|
+
declaredTools: manifest.tools,
|
|
382
|
+
effectiveTools: argv.effectiveTools,
|
|
383
|
+
thinking: launch.thinking,
|
|
384
|
+
model: launch.model,
|
|
385
|
+
...(manifest.skills?.profile ? { skillProfile: manifest.skills.profile } : {}),
|
|
386
|
+
loadedSkills: launch.loadedSkills,
|
|
387
|
+
manifestSha256: committedManifest.sha256,
|
|
388
|
+
manifestBlobOid: committedManifest.blobOid,
|
|
389
|
+
systemPromptSha256: committedPrompt.sha256,
|
|
390
|
+
systemPromptBlobOid: committedPrompt.blobOid,
|
|
391
|
+
composedSystemPromptSha256: argv.systemPromptSha256,
|
|
392
|
+
agentRepo: {
|
|
393
|
+
commit: agentSnapshot.commit,
|
|
394
|
+
treeOid: agentSnapshot.treeOid,
|
|
395
|
+
statusSha256: agentSnapshot.statusSha256,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
task,
|
|
399
|
+
bootstrap: bootstrap.bindings,
|
|
400
|
+
observation: {
|
|
401
|
+
agentRevisionStable: postAgent?.stable === true,
|
|
402
|
+
originRevisionStable: postOrigin?.stable === true,
|
|
403
|
+
inputsStable: postInputs,
|
|
404
|
+
bootstrapStable: postBootstrap,
|
|
405
|
+
parentRepoRoot: parentRoot,
|
|
406
|
+
parentCommit: parentSnapshot.commit,
|
|
407
|
+
parentStatusSha256: parentSnapshot.statusSha256,
|
|
408
|
+
boundary:
|
|
409
|
+
"Bounded launch-window HEAD/worktree observations only, not lifetime read-only proof. Ignored files, .git internals, outside surfaces and modify-and-restore intervals are unobserved. No child startup, ACK or task completion evidence is inferred.",
|
|
410
|
+
},
|
|
411
|
+
launch: {
|
|
412
|
+
runId,
|
|
413
|
+
sessionMode: "clean",
|
|
414
|
+
objective,
|
|
415
|
+
objectiveSha256: sha256Hex(objective),
|
|
416
|
+
mutationPolicy: "read_only",
|
|
417
|
+
composedArgvSha256,
|
|
418
|
+
admission: admitted ? "transport_admitted" : noEffects ? "not_admitted" : "unproven",
|
|
419
|
+
sessionStarted: "unproven",
|
|
420
|
+
ack: "unproven",
|
|
421
|
+
taskCompletion: "unproven",
|
|
422
|
+
reportBack,
|
|
423
|
+
...(parentPeerTarget ? { parentPeerTarget } : {}),
|
|
424
|
+
cwd,
|
|
425
|
+
title,
|
|
426
|
+
promptSha256: argv.promptSha256,
|
|
427
|
+
argvFlags: redactArgvForReceipt(argv.extraPiArgs),
|
|
428
|
+
argvCount: argv.extraPiArgs.length,
|
|
429
|
+
skillDirCount: launch.skillDirs.length,
|
|
430
|
+
},
|
|
431
|
+
transport: {
|
|
432
|
+
owner: "pi-little-helpers",
|
|
433
|
+
launchMode: ["tab", "window"].includes(transportResult?.launchMode)
|
|
434
|
+
? transportResult.launchMode
|
|
435
|
+
: "unknown",
|
|
436
|
+
effectDisposition,
|
|
437
|
+
ok: admitted,
|
|
438
|
+
...(!admitted ? { failure: "transport_not_proven_admitted" } : {}),
|
|
439
|
+
},
|
|
440
|
+
recordedAt: new Date().toISOString(),
|
|
441
|
+
});
|
|
442
|
+
let written: Awaited<ReturnType<typeof writeImmutableVisibleLaunchReceipt>>;
|
|
443
|
+
try {
|
|
444
|
+
written = await (deps.writeReceipt ?? writeImmutableVisibleLaunchReceipt)(receiptInput, {
|
|
445
|
+
dir: deps.receiptsDir,
|
|
446
|
+
});
|
|
447
|
+
} catch {
|
|
448
|
+
return fail(
|
|
449
|
+
"receipt_write_failed",
|
|
450
|
+
"Transport observation retained, but receipt publication failed. Supervise runId; never retry automatically.",
|
|
451
|
+
{ effectDisposition, spawnAttempted: true, runId },
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const observed = {
|
|
455
|
+
effectDisposition,
|
|
456
|
+
spawnAttempted: true,
|
|
457
|
+
runId,
|
|
458
|
+
receipt: written.receipt,
|
|
459
|
+
receiptPath: written.receiptPath,
|
|
460
|
+
};
|
|
461
|
+
if (!stable)
|
|
462
|
+
return fail(
|
|
463
|
+
"launch_indeterminate",
|
|
464
|
+
"Post-transport input/origin drift: clean observation unproven. Transport admission is recorded separately; supervise runId.",
|
|
465
|
+
{ ...observed, effectDisposition: "effect_indeterminate" },
|
|
466
|
+
);
|
|
467
|
+
if (!admitted)
|
|
468
|
+
return fail(
|
|
469
|
+
noEffects ? "launch_failed" : "launch_indeterminate",
|
|
470
|
+
"Transport did not prove admission; reservation and receipt retained. No automatic retry.",
|
|
471
|
+
observed,
|
|
472
|
+
);
|
|
473
|
+
return {
|
|
474
|
+
ok: true,
|
|
475
|
+
phase: VISIBLE_LAUNCH_PHASE,
|
|
476
|
+
admission: "transport_admitted",
|
|
477
|
+
receipt: written.receipt,
|
|
478
|
+
receiptPath: written.receiptPath,
|
|
479
|
+
runId,
|
|
480
|
+
launchMode: written.receipt.transport.launchMode,
|
|
481
|
+
};
|
|
482
|
+
}
|