@tt-a1i/openpi 0.5.0 → 0.6.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/README.md +18 -10
- package/SETUP.md +8 -2
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/bin/openpi.js +25 -15
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +8 -1
- package/extensions/background-terminals/src/manager.ts +3 -5
- package/extensions/background-terminals/src/result-delivery.ts +43 -23
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +5 -1
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +45 -1
- package/extensions/plan-mode/index.ts +75 -4
- package/extensions/setup/index.ts +15 -3
- package/extensions/shared/child-session.ts +25 -5
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/setup-config.ts +10 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/subagents/index.ts +44 -4
- package/extensions/subagents/src/backends/pi.ts +76 -5
- package/extensions/subagents/src/domain.ts +16 -1
- package/extensions/subagents/src/manager.ts +5 -0
- package/extensions/subagents/src/prompt.ts +17 -3
- package/extensions/subagents/src/result-artifact.ts +32 -0
- package/extensions/subagents/src/result-delivery.ts +33 -14
- package/extensions/ui-customization/footer.ts +16 -5
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +25 -2
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/completion-projection.ts +3 -1
- package/extensions/workflows/dashboard.ts +8 -0
- package/extensions/workflows/index.ts +13 -0
- package/extensions/workflows/model.ts +5 -1
- package/extensions/workflows/prompt.ts +4 -10
- package/extensions/workflows/result-delivery.ts +96 -22
- package/extensions/workflows/retention.ts +6 -0
- package/extensions/workflows/runner.ts +6 -71
- package/package.json +7 -7
- package/skills/subagents/REFERENCE.md +3 -2
- package/skills/subagents/SKILL.md +1 -0
- package/skills/workflows/REFERENCE.md +3 -3
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +3 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/web-host.ts +330 -50
- package/web/protocol/types.ts +5 -0
- package/web/runtime/pi-runtime.ts +240 -25
- package/web/runtime/types.ts +32 -1
- package/web/ui/app.js +343 -41
- package/web/ui/index.html +3 -0
- package/web/ui/styles.css +119 -37
|
@@ -69,6 +69,8 @@ export interface SpawnTask {
|
|
|
69
69
|
readonly tools?: readonly string[];
|
|
70
70
|
/** Agent type that supplied the above, for the session label. */
|
|
71
71
|
readonly agentTypeName?: string;
|
|
72
|
+
/** Optional JSON Schema for one terminating, validated child result. */
|
|
73
|
+
readonly outputSchema?: unknown;
|
|
72
74
|
/**
|
|
73
75
|
* Isolated git worktree this child runs in, created by the tool layer. The
|
|
74
76
|
* backend only reclaims it when the session scope closes; it does not know
|
|
@@ -142,7 +144,11 @@ export interface QueuedMessage {
|
|
|
142
144
|
// --- Events ------------------------------------------------------------------
|
|
143
145
|
|
|
144
146
|
export type RunOutcome =
|
|
145
|
-
| {
|
|
147
|
+
| {
|
|
148
|
+
readonly _tag: "Completed";
|
|
149
|
+
readonly finalText: string;
|
|
150
|
+
readonly structuredResult?: StructuredSubagentResult;
|
|
151
|
+
}
|
|
146
152
|
| {
|
|
147
153
|
readonly _tag: "Failed";
|
|
148
154
|
readonly errorText: string;
|
|
@@ -232,10 +238,19 @@ export interface SubagentSnapshot {
|
|
|
232
238
|
readonly queued: ReadonlyArray<QueuedMessage>;
|
|
233
239
|
/** Final text of the most recent completed run (v1 `finalOutput`). */
|
|
234
240
|
readonly finalText: string;
|
|
241
|
+
/** Present only when this run supplied and satisfied output_schema. */
|
|
242
|
+
readonly structuredResult?: StructuredSubagentResult;
|
|
235
243
|
/** Count of finalized assistant messages (for subagent_check). */
|
|
236
244
|
readonly turns: number;
|
|
237
245
|
}
|
|
238
246
|
|
|
247
|
+
export interface StructuredSubagentResult {
|
|
248
|
+
readonly value: unknown;
|
|
249
|
+
readonly json: string;
|
|
250
|
+
readonly byteLength: number;
|
|
251
|
+
readonly artifactPath: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
239
254
|
/** Final text, or the live streaming buffer while a run is active (v1 `latestOutput`). */
|
|
240
255
|
export function latestText(snap: SubagentSnapshot) {
|
|
241
256
|
const live = snap.liveAssistant?.text.trim();
|
|
@@ -123,6 +123,7 @@ interface MutableSnapshot {
|
|
|
123
123
|
liveTools: LiveToolState[];
|
|
124
124
|
queued: SubagentSnapshot["queued"];
|
|
125
125
|
finalText: string;
|
|
126
|
+
structuredResult?: SubagentSnapshot["structuredResult"];
|
|
126
127
|
turns: number;
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -363,6 +364,7 @@ const makeManager = (config: SubagentManagerConfig = {}) =>
|
|
|
363
364
|
s.outcome = "completed";
|
|
364
365
|
s.errorText = undefined;
|
|
365
366
|
s.finalText = outcome.finalText.slice(0, FINAL_TEXT_MAX_LENGTH);
|
|
367
|
+
s.structuredResult = outcome.structuredResult;
|
|
366
368
|
break;
|
|
367
369
|
case "Failed":
|
|
368
370
|
s.status = "error";
|
|
@@ -373,6 +375,7 @@ const makeManager = (config: SubagentManagerConfig = {}) =>
|
|
|
373
375
|
0,
|
|
374
376
|
FINAL_TEXT_MAX_LENGTH,
|
|
375
377
|
);
|
|
378
|
+
s.structuredResult = undefined;
|
|
376
379
|
break;
|
|
377
380
|
case "Interrupted":
|
|
378
381
|
s.status = "error";
|
|
@@ -382,6 +385,7 @@ const makeManager = (config: SubagentManagerConfig = {}) =>
|
|
|
382
385
|
0,
|
|
383
386
|
FINAL_TEXT_MAX_LENGTH,
|
|
384
387
|
);
|
|
388
|
+
s.structuredResult = undefined;
|
|
385
389
|
break;
|
|
386
390
|
}
|
|
387
391
|
s.liveAssistant = undefined;
|
|
@@ -446,6 +450,7 @@ const makeManager = (config: SubagentManagerConfig = {}) =>
|
|
|
446
450
|
s.outcome = undefined;
|
|
447
451
|
s.settledAt = undefined;
|
|
448
452
|
s.errorText = undefined;
|
|
453
|
+
s.structuredResult = undefined;
|
|
449
454
|
armWatchdog(entry);
|
|
450
455
|
break;
|
|
451
456
|
case "RunSettled":
|
|
@@ -17,8 +17,8 @@ export const SUBAGENT_SCHEMA_BUDGETS = Object.freeze({
|
|
|
17
17
|
|
|
18
18
|
/** Describes subagent_spawn, including the fixed concurrency cap. */
|
|
19
19
|
export const SUBAGENT_SPAWN_TOOL_DESCRIPTION =
|
|
20
|
-
"Spawn a background
|
|
21
|
-
`Max ${MAX_RUNNING} subagents can
|
|
20
|
+
"Spawn a background Pi subagent with isolated context and child-safe tools. Returns immediately; its result arrives automatically. It cannot see this chat, ask the user, or orchestrate. Use trusted directories. " +
|
|
21
|
+
`Max ${MAX_RUNNING} subagents can run at once.`;
|
|
22
22
|
|
|
23
23
|
/** UTF-8 bounded, whitespace-normalized text for the parent-facing roster. */
|
|
24
24
|
function boundedPurpose(description: string) {
|
|
@@ -152,6 +152,7 @@ export const SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS = {
|
|
|
152
152
|
'Optional "provider/model-id" or current-provider model override. Omit to use the preset, configured role, or parent default. Never guess a model name.',
|
|
153
153
|
reasoningEffort:
|
|
154
154
|
"Optional child thinking level. Honor the user's requested level. Otherwise choose a level supported by the resolved child model based on the selected role and task difficulty. An explicit value overrides a role default.",
|
|
155
|
+
outputSchema: "Optional result JSON Schema.",
|
|
155
156
|
};
|
|
156
157
|
|
|
157
158
|
/** The exact name/description/wire-schema source used by registration/tests. */
|
|
@@ -193,6 +194,15 @@ export function createSubagentSpawnToolSurface(
|
|
|
193
194
|
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.reasoningEffort,
|
|
194
195
|
}),
|
|
195
196
|
),
|
|
197
|
+
output_schema: Type.Optional(
|
|
198
|
+
Type.Object(
|
|
199
|
+
{},
|
|
200
|
+
{
|
|
201
|
+
additionalProperties: true,
|
|
202
|
+
description: SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS.outputSchema,
|
|
203
|
+
},
|
|
204
|
+
),
|
|
205
|
+
),
|
|
196
206
|
}),
|
|
197
207
|
};
|
|
198
208
|
}
|
|
@@ -207,6 +217,7 @@ export function buildSubagentSpawnResult(options: {
|
|
|
207
217
|
agentTypeName?: string;
|
|
208
218
|
tools?: readonly string[];
|
|
209
219
|
worktreeBranch?: string;
|
|
220
|
+
structured?: boolean;
|
|
210
221
|
}) {
|
|
211
222
|
const typeNote = options.agentTypeName
|
|
212
223
|
? ` Agent type "${options.agentTypeName}" applied.`
|
|
@@ -226,8 +237,11 @@ export function buildSubagentSpawnResult(options: {
|
|
|
226
237
|
const worktreeNote = options.worktreeBranch
|
|
227
238
|
? ` Isolated in its own worktree on branch "${options.worktreeBranch}" — its edits are invisible here until you merge that branch. The checkout stays available for later send/review and is reclaimed on Session retirement only when bounded inspection proves it empty.`
|
|
228
239
|
: "";
|
|
240
|
+
const structuredNote = options.structured
|
|
241
|
+
? " This run must finish with the requested validated structured result."
|
|
242
|
+
: "";
|
|
229
243
|
return (
|
|
230
|
-
`Spawned subagent ${options.id} "${options.title}" (${options.harness}: ${options.modelLabel}, ${options.cwd}).${typeNote}${toolNote}${worktreeNote}\n` +
|
|
244
|
+
`Spawned subagent ${options.id} "${options.title}" (${options.harness}: ${options.modelLabel}, ${options.cwd}).${typeNote}${toolNote}${worktreeNote}${structuredNote}\n` +
|
|
231
245
|
`It runs in the background — keep working on independent work. If none remains in an interactive session, briefly tell the user it is still running and end your turn; its result is delivered automatically and you are automatically re-invoked when it finishes. Do not poll or call subagent_wait merely because a later step depends on it. ` +
|
|
232
246
|
`Use subagent_wait(ids: ["${options.id}"]) only if the user explicitly asked you to keep the current response open for this result, or a non-interactive automation must return it in the same invocation; subagent_cancel stops it, subagent_check peeks at a running one, subagent_list shows all.`
|
|
233
247
|
);
|
|
@@ -77,6 +77,38 @@ export function persistResultArtifact(agentDir: string, content: string) {
|
|
|
77
77
|
return artifactPath;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** Persist one complete validated structured value under a JSON identity. */
|
|
81
|
+
export function persistStructuredResultArtifact(
|
|
82
|
+
agentDir: string,
|
|
83
|
+
content: string,
|
|
84
|
+
) {
|
|
85
|
+
let directory = path.resolve(agentDir);
|
|
86
|
+
for (const segment of RESULT_ARTIFACT_DIR) {
|
|
87
|
+
directory = ensureDirectory(directory, segment);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const digest = createHash("sha256").update(content).digest("hex");
|
|
91
|
+
const artifactPath = path.join(directory, `${digest}.json`);
|
|
92
|
+
try {
|
|
93
|
+
writeFileSync(artifactPath, content, {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
flag: "wx",
|
|
96
|
+
mode: 0o600,
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
100
|
+
const stat = lstatSync(artifactPath);
|
|
101
|
+
if (
|
|
102
|
+
!stat.isFile() ||
|
|
103
|
+
stat.isSymbolicLink() ||
|
|
104
|
+
readFileSync(artifactPath, "utf8") !== content
|
|
105
|
+
) {
|
|
106
|
+
throw new Error(`Structured result artifact collision: ${artifactPath}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return artifactPath;
|
|
110
|
+
}
|
|
111
|
+
|
|
80
112
|
/**
|
|
81
113
|
* Build the single model-visible projection used by automatic delivery and
|
|
82
114
|
* explicit waits. Short answers pass through byte-for-byte. Long answers keep
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { ConsumableResultDeliveryQueue } from "../../shared/result-delivery.ts";
|
|
2
|
+
import {
|
|
3
|
+
type CompletionOwner,
|
|
4
|
+
createCompletionInbox,
|
|
5
|
+
} from "../../shared/completion-inbox.ts";
|
|
2
6
|
|
|
3
7
|
export interface SubagentResultDeliveryOptions<T> {
|
|
4
8
|
/** True only when the parent has no run or queued continuation in flight. */
|
|
5
9
|
readonly isIdle: () => boolean;
|
|
6
10
|
/** Deliver one drained batch and wake the parent. */
|
|
7
11
|
readonly deliver: (results: readonly T[]) => void;
|
|
12
|
+
/** Current Pi Session transcript owner. */
|
|
13
|
+
readonly owner?: () => CompletionOwner | undefined;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
/**
|
|
@@ -22,50 +28,63 @@ export interface SubagentResultDeliveryOptions<T> {
|
|
|
22
28
|
* The parent boundary wakes even if an earlier extension handler has already
|
|
23
29
|
* started another turn: Pi queues the follow-up into that active run.
|
|
24
30
|
*
|
|
25
|
-
* The
|
|
26
|
-
* is delivered, and whichever path
|
|
31
|
+
* The shared inbox is the one-shot gate: `subagent_wait` may consume a result
|
|
32
|
+
* before it is delivered, and whichever path claims first prevents duplicate
|
|
33
|
+
* delivery.
|
|
27
34
|
*/
|
|
28
35
|
export function createSubagentResultDelivery<T extends { id: string }>(
|
|
29
36
|
options: SubagentResultDeliveryOptions<T>,
|
|
30
37
|
) {
|
|
31
|
-
const
|
|
38
|
+
const inbox = createCompletionInbox<T>();
|
|
39
|
+
const owner = options.owner ?? (() => ({ sessionId: "test", epoch: 0 }));
|
|
32
40
|
|
|
33
41
|
const flush = () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
const envelopes = inbox.claim(owner());
|
|
43
|
+
if (envelopes.length === 0) return;
|
|
44
|
+
const results = envelopes.map((envelope) => envelope.payload);
|
|
37
45
|
try {
|
|
38
46
|
options.deliver(results);
|
|
47
|
+
inbox.acknowledge(envelopes.map((envelope) => envelope.deliveryId));
|
|
39
48
|
} catch (error) {
|
|
40
49
|
// A synchronous session teardown may reject append/send. Preserve the
|
|
41
50
|
// original batch ahead of anything deferred re-entrantly while delivery
|
|
42
51
|
// ran, so a later boundary can retry without loss or reordering.
|
|
43
|
-
|
|
44
|
-
pending.clear();
|
|
45
|
-
for (const result of results) pending.set(result.id, result);
|
|
46
|
-
for (const result of current) pending.set(result.id, result);
|
|
52
|
+
inbox.retry(envelopes, owner());
|
|
47
53
|
throw error;
|
|
48
54
|
}
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
const queue = {
|
|
52
58
|
defer(result: T) {
|
|
53
|
-
|
|
59
|
+
const currentOwner = owner();
|
|
60
|
+
inbox.defer(
|
|
61
|
+
{
|
|
62
|
+
deliveryId: `subagent:${result.id}`,
|
|
63
|
+
owner: currentOwner ?? { sessionId: "unowned", epoch: 0 },
|
|
64
|
+
producer: "subagent",
|
|
65
|
+
producerId: result.id,
|
|
66
|
+
terminalRef: { kind: "subagent-snapshot", id: result.id },
|
|
67
|
+
wake: "follow-up",
|
|
68
|
+
payload: result,
|
|
69
|
+
},
|
|
70
|
+
currentOwner,
|
|
71
|
+
);
|
|
54
72
|
if (options.isIdle()) flush();
|
|
55
73
|
},
|
|
56
74
|
consume(ids: Iterable<string>) {
|
|
57
|
-
|
|
75
|
+
inbox.consume("subagent", ids);
|
|
58
76
|
},
|
|
59
77
|
/** Flush at the authoritative parent boundary. */
|
|
60
78
|
parentSettled() {
|
|
61
79
|
flush();
|
|
62
80
|
},
|
|
63
81
|
clear() {
|
|
64
|
-
|
|
82
|
+
inbox.clear();
|
|
65
83
|
},
|
|
66
84
|
size() {
|
|
67
|
-
return
|
|
85
|
+
return inbox.size();
|
|
68
86
|
},
|
|
87
|
+
inspectDeadLetters: inbox.inspectDeadLetters,
|
|
69
88
|
};
|
|
70
89
|
return queue satisfies ConsumableResultDeliveryQueue<T>;
|
|
71
90
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
3
3
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import {
|
|
5
5
|
getCapabilities,
|
|
@@ -117,10 +117,21 @@ export function formatTokens(tokens: number) {
|
|
|
117
117
|
return `${(tokens / 1_000_000).toFixed(1)}m`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
export function formatDirectory(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
export function formatDirectory(
|
|
121
|
+
cwd: string,
|
|
122
|
+
home = homedir(),
|
|
123
|
+
pathModule = process.platform === "win32" ? win32 : posix,
|
|
124
|
+
) {
|
|
125
|
+
const relativePath = pathModule.relative(home, cwd);
|
|
126
|
+
const outsideHome =
|
|
127
|
+
relativePath === ".." ||
|
|
128
|
+
relativePath.startsWith(`..${pathModule.sep}`) ||
|
|
129
|
+
pathModule.isAbsolute(relativePath);
|
|
130
|
+
const display = outsideHome
|
|
131
|
+
? cwd
|
|
132
|
+
: relativePath
|
|
133
|
+
? `~/${relativePath.replaceAll(pathModule.sep, "/")}`
|
|
134
|
+
: "~";
|
|
124
135
|
return sanitizeTerminalLabel(display);
|
|
125
136
|
}
|
|
126
137
|
|
|
@@ -40,8 +40,35 @@ type Segment =
|
|
|
40
40
|
| { kind: "prose"; lines: string[] }
|
|
41
41
|
| { kind: "code"; open: string; content: string[]; close: string };
|
|
42
42
|
|
|
43
|
-
const FENCE_OPEN = /^ {0,3}`{3,}/;
|
|
44
|
-
|
|
43
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse an opening code fence (CommonMark §4.5): which character it uses and
|
|
47
|
+
* how long it is. A backtick fence's info string may not contain backticks.
|
|
48
|
+
*/
|
|
49
|
+
function openFence(line: string) {
|
|
50
|
+
const match = FENCE_OPEN.exec(line);
|
|
51
|
+
if (!match) return undefined;
|
|
52
|
+
const fence = match[1];
|
|
53
|
+
if (fence[0] === "`" && line.slice(match[0].length).includes("`")) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return { char: fence[0], length: fence.length };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A closing fence must use the same character as the opening fence and be at
|
|
61
|
+
* least as long: a ``` line does not close a ```` block, and backticks never
|
|
62
|
+
* close a tilde block.
|
|
63
|
+
*/
|
|
64
|
+
function isCloseFence(line: string, open: { char: string; length: number }) {
|
|
65
|
+
const match = /^ {0,3}(`{3,}|~{3,})[ \t]*\r?$/.exec(line);
|
|
66
|
+
return (
|
|
67
|
+
match !== null &&
|
|
68
|
+
match[1][0] === open.char &&
|
|
69
|
+
match[1].length >= open.length
|
|
70
|
+
);
|
|
71
|
+
}
|
|
45
72
|
|
|
46
73
|
function countLines(markdown: string) {
|
|
47
74
|
const parts = markdown.split("\n");
|
|
@@ -60,7 +87,8 @@ function parseSegments(lines: string[]): Segment[] {
|
|
|
60
87
|
let prose: string[] = [];
|
|
61
88
|
let i = 0;
|
|
62
89
|
while (i < lines.length) {
|
|
63
|
-
|
|
90
|
+
const fence = openFence(lines[i]);
|
|
91
|
+
if (!fence) {
|
|
64
92
|
prose.push(lines[i]);
|
|
65
93
|
i += 1;
|
|
66
94
|
continue;
|
|
@@ -74,13 +102,21 @@ function parseSegments(lines: string[]): Segment[] {
|
|
|
74
102
|
let close: string | undefined;
|
|
75
103
|
let j = i + 1;
|
|
76
104
|
while (j < lines.length && close === undefined) {
|
|
77
|
-
if (
|
|
105
|
+
if (isCloseFence(lines[j], fence)) close = lines[j];
|
|
78
106
|
else content.push(lines[j]);
|
|
79
107
|
j += 1;
|
|
80
108
|
}
|
|
81
109
|
if (close === undefined) {
|
|
82
|
-
// Unterminated fence:
|
|
83
|
-
|
|
110
|
+
// Unterminated fence: the block runs to the end of the message. Keep it
|
|
111
|
+
// as a code block with a synthesized closing fence so a folded preview
|
|
112
|
+
// never leaks an unclosed fence into the TUI.
|
|
113
|
+
segments.push({
|
|
114
|
+
kind: "code",
|
|
115
|
+
open,
|
|
116
|
+
content,
|
|
117
|
+
close: fence.char.repeat(fence.length),
|
|
118
|
+
});
|
|
119
|
+
return segments;
|
|
84
120
|
}
|
|
85
121
|
segments.push({ kind: "code", open, content, close });
|
|
86
122
|
i = j;
|
package/extensions/web/index.ts
CHANGED
|
@@ -5,6 +5,11 @@ import type {
|
|
|
5
5
|
ExtensionAPI,
|
|
6
6
|
ExtensionCommandContext,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
missingPiCodingAgentDiagnostic,
|
|
10
|
+
PI_CODING_AGENT_ENTRY_ENV,
|
|
11
|
+
resolvePiCodingAgentEntry,
|
|
12
|
+
} from "../../web/host/pi-coding-agent-entry.ts";
|
|
8
13
|
|
|
9
14
|
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
10
15
|
|
|
@@ -26,12 +31,20 @@ interface SpawnWebOptions {
|
|
|
26
31
|
stdio: "inherit";
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function webProcessEnvironment(
|
|
34
|
+
function webProcessEnvironment(
|
|
35
|
+
cwd: string,
|
|
36
|
+
piCodingAgentEntry: string | undefined,
|
|
37
|
+
) {
|
|
30
38
|
const environment: NodeJS.ProcessEnv = { ...process.env, PWD: cwd };
|
|
31
39
|
delete environment.OLDPWD;
|
|
32
40
|
delete environment.INIT_CWD;
|
|
33
41
|
delete environment.PI_SESSION_ID;
|
|
34
42
|
delete environment.PI_SESSION_FILE;
|
|
43
|
+
if (piCodingAgentEntry) {
|
|
44
|
+
environment[PI_CODING_AGENT_ENTRY_ENV] = piCodingAgentEntry;
|
|
45
|
+
} else {
|
|
46
|
+
delete environment[PI_CODING_AGENT_ENTRY_ENV];
|
|
47
|
+
}
|
|
35
48
|
return environment;
|
|
36
49
|
}
|
|
37
50
|
|
|
@@ -40,6 +53,7 @@ export interface WebCommandDependencies {
|
|
|
40
53
|
spawn(command: string, args: string[], options: SpawnWebOptions): WebProcess;
|
|
41
54
|
clearTerminal(): void;
|
|
42
55
|
holdParentSigint(): () => void;
|
|
56
|
+
resolvePiCodingAgentEntry(): string | undefined;
|
|
43
57
|
shutdownTimeoutMs: number;
|
|
44
58
|
}
|
|
45
59
|
|
|
@@ -65,6 +79,8 @@ const defaultDependencies: WebCommandDependencies = {
|
|
|
65
79
|
process.on("SIGINT", keepPiAlive);
|
|
66
80
|
return () => process.removeListener("SIGINT", keepPiAlive);
|
|
67
81
|
},
|
|
82
|
+
resolvePiCodingAgentEntry: () =>
|
|
83
|
+
resolvePiCodingAgentEntry({ source: "host" }),
|
|
68
84
|
shutdownTimeoutMs: DEFAULT_SHUTDOWN_TIMEOUT_MS,
|
|
69
85
|
};
|
|
70
86
|
|
|
@@ -95,6 +111,7 @@ function runWebInForeground(
|
|
|
95
111
|
dependencies: WebCommandDependencies,
|
|
96
112
|
setActive: (active: ActiveWebProcess | undefined) => void,
|
|
97
113
|
isShuttingDown: () => boolean,
|
|
114
|
+
piCodingAgentEntry: string,
|
|
98
115
|
) {
|
|
99
116
|
return ctx.ui.custom<WebExit>((tui, _theme, _keybindings, done) => {
|
|
100
117
|
let finished = false;
|
|
@@ -128,7 +145,7 @@ function runWebInForeground(
|
|
|
128
145
|
[dependencies.entrypoint, "web", "--no-workspace"],
|
|
129
146
|
{
|
|
130
147
|
cwd: childCwd,
|
|
131
|
-
env: webProcessEnvironment(childCwd),
|
|
148
|
+
env: webProcessEnvironment(childCwd, piCodingAgentEntry),
|
|
132
149
|
shell: false,
|
|
133
150
|
stdio: "inherit",
|
|
134
151
|
},
|
|
@@ -191,6 +208,11 @@ export default function web(
|
|
|
191
208
|
ctx.ui.notify("OpenPI Web Workbench is already running.", "warning");
|
|
192
209
|
return;
|
|
193
210
|
}
|
|
211
|
+
const piCodingAgentEntry = dependencies.resolvePiCodingAgentEntry();
|
|
212
|
+
if (!piCodingAgentEntry) {
|
|
213
|
+
ctx.ui.notify(missingPiCodingAgentDiagnostic(), "error");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
194
216
|
|
|
195
217
|
running = true;
|
|
196
218
|
try {
|
|
@@ -201,6 +223,7 @@ export default function web(
|
|
|
201
223
|
active = next;
|
|
202
224
|
},
|
|
203
225
|
() => shuttingDown,
|
|
226
|
+
piCodingAgentEntry,
|
|
204
227
|
);
|
|
205
228
|
if (shuttingDown) return;
|
|
206
229
|
if (result.kind === "error") {
|
|
@@ -22,6 +22,25 @@ export interface AcceptanceLedger {
|
|
|
22
22
|
readonly status: "accepted" | "rejected" | "missing" | "malformed";
|
|
23
23
|
readonly criteria: readonly AcceptanceCriterionResult[];
|
|
24
24
|
readonly errors: readonly string[];
|
|
25
|
+
/** Child-authored judgment retained only for migration; never a runtime fact. */
|
|
26
|
+
readonly authority?: "model-self-attestation";
|
|
27
|
+
readonly deprecated?: {
|
|
28
|
+
readonly since: "0.5";
|
|
29
|
+
readonly removal: "1.0";
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ACCEPTANCE_DEPRECATION_WARNING =
|
|
34
|
+
"acceptance is deprecated since OpenPI 0.5 and will be removed in 1.0; it is model self-attestation, not runtime-verified evidence, and does not determine ok";
|
|
35
|
+
|
|
36
|
+
function ledger(
|
|
37
|
+
value: Omit<AcceptanceLedger, "authority" | "deprecated">,
|
|
38
|
+
): AcceptanceLedger {
|
|
39
|
+
return {
|
|
40
|
+
...value,
|
|
41
|
+
authority: "model-self-attestation",
|
|
42
|
+
deprecated: { since: "0.5", removal: "1.0" },
|
|
43
|
+
};
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -169,6 +188,12 @@ export function isAcceptanceLedger(value: unknown): value is AcceptanceLedger {
|
|
|
169
188
|
value.status === "rejected" ||
|
|
170
189
|
value.status === "missing" ||
|
|
171
190
|
value.status === "malformed") &&
|
|
191
|
+
(value.authority === undefined ||
|
|
192
|
+
value.authority === "model-self-attestation") &&
|
|
193
|
+
(value.deprecated === undefined ||
|
|
194
|
+
(record(value.deprecated) &&
|
|
195
|
+
value.deprecated.since === "0.5" &&
|
|
196
|
+
value.deprecated.removal === "1.0")) &&
|
|
172
197
|
value.errors.every((error) => typeof error === "string") &&
|
|
173
198
|
value.criteria.every(
|
|
174
199
|
(criterion) =>
|
|
@@ -187,7 +212,7 @@ export function acceptanceInstruction(contract: AcceptanceContract) {
|
|
|
187
212
|
`- ${criterion.id}: ${criterion.description}${criterion.requiredEvidence?.length ? `; required evidence labels: ${criterion.requiredEvidence.join(", ")}` : ""}`,
|
|
188
213
|
);
|
|
189
214
|
return [
|
|
190
|
-
"
|
|
215
|
+
"Deprecated compatibility protocol: this acceptance ledger is your own model self-attestation, not runtime-verified evidence, and it does not determine execution success.",
|
|
191
216
|
"Include an `acceptance.criteria` array in structured_output with exactly these ids. Mark rejected when the criterion is not demonstrated. Evidence entries must be concise labels or concrete references; do not invent evidence.",
|
|
192
217
|
...criteria,
|
|
193
218
|
].join("\n");
|
|
@@ -198,19 +223,19 @@ export function evaluateAcceptance(
|
|
|
198
223
|
structured: unknown,
|
|
199
224
|
): AcceptanceLedger {
|
|
200
225
|
if (!record(structured) || !record(structured.acceptance)) {
|
|
201
|
-
return {
|
|
226
|
+
return ledger({
|
|
202
227
|
status: "missing",
|
|
203
228
|
criteria: [],
|
|
204
229
|
errors: ["structured result omitted acceptance"],
|
|
205
|
-
};
|
|
230
|
+
});
|
|
206
231
|
}
|
|
207
232
|
const rawCriteria = structured.acceptance.criteria;
|
|
208
233
|
if (!Array.isArray(rawCriteria)) {
|
|
209
|
-
return {
|
|
234
|
+
return ledger({
|
|
210
235
|
status: "malformed",
|
|
211
236
|
criteria: [],
|
|
212
237
|
errors: ["acceptance.criteria is not an array"],
|
|
213
|
-
};
|
|
238
|
+
});
|
|
214
239
|
}
|
|
215
240
|
const errors: string[] = [];
|
|
216
241
|
const byId = new Map<string, AcceptanceCriterionResult>();
|
|
@@ -265,14 +290,15 @@ export function evaluateAcceptance(
|
|
|
265
290
|
errors.push(`unexpected acceptance criterion "${id}"`);
|
|
266
291
|
}
|
|
267
292
|
}
|
|
268
|
-
if (errors.length)
|
|
269
|
-
|
|
293
|
+
if (errors.length)
|
|
294
|
+
return ledger({ status: "malformed", criteria: results, errors });
|
|
295
|
+
return ledger({
|
|
270
296
|
status: results.every((result) => result.status === "accepted")
|
|
271
297
|
? "accepted"
|
|
272
298
|
: "rejected",
|
|
273
299
|
criteria: results,
|
|
274
300
|
errors: [],
|
|
275
|
-
};
|
|
301
|
+
});
|
|
276
302
|
}
|
|
277
303
|
|
|
278
304
|
export function applyAcceptance(options: {
|
|
@@ -284,15 +310,13 @@ export function applyAcceptance(options: {
|
|
|
284
310
|
const ledger = options.contract
|
|
285
311
|
? evaluateAcceptance(options.contract, options.structured)
|
|
286
312
|
: undefined;
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
:
|
|
295
|
-
|
|
296
|
-
: (acceptanceError ?? "Agent failed");
|
|
297
|
-
return { ok, ...(ledger ? { ledger } : {}), ...(error ? { error } : {}) };
|
|
313
|
+
const ok = options.agentOk;
|
|
314
|
+
const error = ok ? undefined : (options.agentError ?? "Agent failed");
|
|
315
|
+
return {
|
|
316
|
+
ok,
|
|
317
|
+
...(ledger
|
|
318
|
+
? { ledger, acceptanceWarning: ACCEPTANCE_DEPRECATION_WARNING }
|
|
319
|
+
: {}),
|
|
320
|
+
...(error ? { error } : {}),
|
|
321
|
+
};
|
|
298
322
|
}
|
|
@@ -230,7 +230,9 @@ function buildOperatorReport(
|
|
|
230
230
|
: "running";
|
|
231
231
|
lines.push(
|
|
232
232
|
`- [${agent.label}]${agent.phase ? ` (${agent.phase})` : ""} ${state}` +
|
|
233
|
-
(agent.acceptance
|
|
233
|
+
(agent.acceptance
|
|
234
|
+
? ` · deprecated model self-attestation ${agent.acceptance.status}`
|
|
235
|
+
: "") +
|
|
234
236
|
(agent.error ? ` — ${agent.error}` : ""),
|
|
235
237
|
);
|
|
236
238
|
}
|
|
@@ -220,6 +220,14 @@ function normalizeDelivery(value: unknown): WorkflowDetails["delivery"] {
|
|
|
220
220
|
: 0;
|
|
221
221
|
return {
|
|
222
222
|
id: sanitizeLine(record.id, 256),
|
|
223
|
+
...(typeof record.ownerSessionId === "string" && record.ownerSessionId
|
|
224
|
+
? { ownerSessionId: sanitizeLine(record.ownerSessionId, 256) }
|
|
225
|
+
: {}),
|
|
226
|
+
...(typeof record.ownerEpoch === "number" &&
|
|
227
|
+
Number.isSafeInteger(record.ownerEpoch) &&
|
|
228
|
+
record.ownerEpoch >= 0
|
|
229
|
+
? { ownerEpoch: record.ownerEpoch }
|
|
230
|
+
: {}),
|
|
223
231
|
state,
|
|
224
232
|
attempts,
|
|
225
233
|
updatedAt,
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import { fitNavigationSides } from "../shared/below-editor-navigation.ts";
|
|
56
56
|
import { waitBounded } from "../shared/child-session.ts";
|
|
57
57
|
import { contextPercent } from "../shared/context-utilization.ts";
|
|
58
|
+
import { completionOwnerFor } from "../shared/completion-inbox.ts";
|
|
58
59
|
import {
|
|
59
60
|
registerEditorLayer,
|
|
60
61
|
removeEditorLayer,
|
|
@@ -537,6 +538,8 @@ interface ScriptAgentResult {
|
|
|
537
538
|
/** Opaque same-run handle for bounded downstream handoff. */
|
|
538
539
|
ref?: string;
|
|
539
540
|
acceptance?: AgentRecord["acceptance"];
|
|
541
|
+
/** Present only for the deprecated model self-attestation compatibility path. */
|
|
542
|
+
acceptanceWarning?: string;
|
|
540
543
|
error?: string;
|
|
541
544
|
}
|
|
542
545
|
|
|
@@ -857,6 +860,8 @@ export default function workflows(
|
|
|
857
860
|
};
|
|
858
861
|
const resultDelivery = createWorkflowResultDelivery({
|
|
859
862
|
isIdle: () => lastContext?.isIdle() ?? false,
|
|
863
|
+
owner: () =>
|
|
864
|
+
lastContext ? completionOwnerFor(lastContext.sessionManager) : undefined,
|
|
860
865
|
persist: (details) => {
|
|
861
866
|
if (!details.delivery)
|
|
862
867
|
throw new Error("Workflow delivery identity is missing");
|
|
@@ -1276,6 +1281,8 @@ export default function workflows(
|
|
|
1276
1281
|
agents: [],
|
|
1277
1282
|
delivery: {
|
|
1278
1283
|
id: `workflow:${runId}:terminal`,
|
|
1284
|
+
ownerSessionId: completionOwnerFor(ctx.sessionManager).sessionId,
|
|
1285
|
+
ownerEpoch: completionOwnerFor(ctx.sessionManager).epoch,
|
|
1279
1286
|
state: launchMode === "inline" ? "held-for-inline" : "none",
|
|
1280
1287
|
attempts: 0,
|
|
1281
1288
|
updatedAt: now,
|
|
@@ -1880,6 +1887,9 @@ export default function workflows(
|
|
|
1880
1887
|
: {}),
|
|
1881
1888
|
...(ref ? { ref } : {}),
|
|
1882
1889
|
...(record.acceptance ? { acceptance: record.acceptance } : {}),
|
|
1890
|
+
...(judged.acceptanceWarning
|
|
1891
|
+
? { acceptanceWarning: judged.acceptanceWarning }
|
|
1892
|
+
: {}),
|
|
1883
1893
|
};
|
|
1884
1894
|
}
|
|
1885
1895
|
|
|
@@ -2131,6 +2141,9 @@ export default function workflows(
|
|
|
2131
2141
|
: {}),
|
|
2132
2142
|
...(ref ? { ref } : {}),
|
|
2133
2143
|
...(acceptance ? { acceptance } : {}),
|
|
2144
|
+
...(judged.acceptanceWarning
|
|
2145
|
+
? { acceptanceWarning: judged.acceptanceWarning }
|
|
2146
|
+
: {}),
|
|
2134
2147
|
...(record.error !== undefined ? { error: record.error } : {}),
|
|
2135
2148
|
};
|
|
2136
2149
|
} finally {
|