@tt-a1i/openpi 0.4.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 +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- 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 +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Minimum lifecycle contract shared by deferred result queues. */
|
|
2
|
+
export interface ResultDeliveryQueue<T> {
|
|
3
|
+
defer(result: T): void;
|
|
4
|
+
size(): number;
|
|
5
|
+
clear(): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** One-shot results are removed by either an explicit consumer or delivery. */
|
|
9
|
+
export interface ConsumableResultDeliveryQueue<T, TId = string>
|
|
10
|
+
extends ResultDeliveryQueue<T> {
|
|
11
|
+
consume(ids: Iterable<TId>): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Stable identity carried by a durable, at-least-once result. */
|
|
15
|
+
export interface DurableResultEnvelope {
|
|
16
|
+
deliveryId: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Per-result acknowledgement for a durable delivery attempt. */
|
|
20
|
+
export interface DurableResultDeliveryReceipt {
|
|
21
|
+
deliveryId: string;
|
|
22
|
+
delivered: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Durable queues can reconstruct pending ownership after restart and retry it
|
|
28
|
+
* at an explicit lifecycle boundary without changing the stable delivery id.
|
|
29
|
+
*/
|
|
30
|
+
export interface DurableResultDeliveryQueue<T extends DurableResultEnvelope>
|
|
31
|
+
extends ResultDeliveryQueue<T> {
|
|
32
|
+
restore(result: T): boolean;
|
|
33
|
+
retryPending(): Promise<void>;
|
|
34
|
+
}
|
|
@@ -63,6 +63,9 @@ export type FooterLines = readonly (readonly FooterLayoutItem[])[];
|
|
|
63
63
|
export const DETAIL_DISPLAYS = ["full", "compact"] as const;
|
|
64
64
|
export type DetailDisplay = (typeof DETAIL_DISPLAYS)[number];
|
|
65
65
|
|
|
66
|
+
export const WEB_THEMES = ["system", "light", "dark"] as const;
|
|
67
|
+
export type WebTheme = (typeof WEB_THEMES)[number];
|
|
68
|
+
|
|
66
69
|
export const CAPABILITY_DISCOVERY_MODES = ["explicit", "adaptive"] as const;
|
|
67
70
|
export type CapabilityDiscoveryMode =
|
|
68
71
|
(typeof CAPABILITY_DISCOVERY_MODES)[number];
|
|
@@ -72,6 +75,22 @@ export const DEFAULT_FOOTER_LINES: FooterLines = [
|
|
|
72
75
|
["model", "context", "flex", "git", "pr", "cwd"],
|
|
73
76
|
];
|
|
74
77
|
|
|
78
|
+
/** Stable skeleton for the legacy flat-selection adapter. */
|
|
79
|
+
const FLAT_FOOTER_ITEM_LINES: FooterLines = [
|
|
80
|
+
[
|
|
81
|
+
"model",
|
|
82
|
+
"thinking",
|
|
83
|
+
"context",
|
|
84
|
+
"cache",
|
|
85
|
+
"cost",
|
|
86
|
+
"throughput",
|
|
87
|
+
"flex",
|
|
88
|
+
"git",
|
|
89
|
+
"pr",
|
|
90
|
+
"cwd",
|
|
91
|
+
],
|
|
92
|
+
];
|
|
93
|
+
|
|
75
94
|
/** The persisted canonical default before model/context moved to the left. */
|
|
76
95
|
const LEGACY_DEFAULT_FOOTER_LINES: FooterLines = [
|
|
77
96
|
["cwd", "git", "pr", "flex", "model", "context"],
|
|
@@ -133,12 +152,11 @@ export interface MyPiSetupConfig {
|
|
|
133
152
|
readonly maxAgentCalls: number;
|
|
134
153
|
};
|
|
135
154
|
readonly ui: {
|
|
155
|
+
readonly webTheme: WebTheme;
|
|
136
156
|
readonly showHeader: boolean;
|
|
137
157
|
readonly customFooter: boolean;
|
|
138
158
|
readonly footerStyle: FooterStyle;
|
|
139
|
-
/** Canonical footer layout. `footerItems` is always derived from this. */
|
|
140
159
|
readonly footerLines: FooterLines;
|
|
141
|
-
readonly footerItems: readonly FooterItem[];
|
|
142
160
|
readonly subagentResultDisplay: DetailDisplay;
|
|
143
161
|
readonly bashToolDisplay: DetailDisplay;
|
|
144
162
|
readonly fileMutationDisplay: DetailDisplay;
|
|
@@ -165,12 +183,12 @@ export const DEFAULT_SETUP_CONFIG: MyPiSetupConfig = {
|
|
|
165
183
|
maxAgentCalls: DEFAULT_WORKFLOW_MAX_AGENT_CALLS,
|
|
166
184
|
},
|
|
167
185
|
ui: {
|
|
186
|
+
webTheme: "system",
|
|
168
187
|
showHeader: false,
|
|
169
188
|
customFooter: true,
|
|
170
189
|
footerStyle: DEFAULT_FOOTER_STYLE,
|
|
171
190
|
footerLines: DEFAULT_FOOTER_LINES,
|
|
172
|
-
|
|
173
|
-
subagentResultDisplay: "full",
|
|
191
|
+
subagentResultDisplay: "compact",
|
|
174
192
|
bashToolDisplay: "compact",
|
|
175
193
|
fileMutationDisplay: "compact",
|
|
176
194
|
},
|
|
@@ -213,6 +231,9 @@ const isCapabilityDiscoveryMode = (
|
|
|
213
231
|
typeof value === "string" &&
|
|
214
232
|
CAPABILITY_DISCOVERY_MODES.includes(value as CapabilityDiscoveryMode);
|
|
215
233
|
|
|
234
|
+
const isWebTheme = (value: unknown): value is WebTheme =>
|
|
235
|
+
typeof value === "string" && WEB_THEMES.includes(value as WebTheme);
|
|
236
|
+
|
|
216
237
|
export function flattenFooterItems(lines: FooterLines): readonly FooterItem[] {
|
|
217
238
|
const items: FooterItem[] = [];
|
|
218
239
|
const seen = new Set<FooterItem>();
|
|
@@ -261,13 +282,13 @@ export function normalizeFooterLines(value: unknown): FooterLines {
|
|
|
261
282
|
return lines.length > 0 ? lines : DEFAULT_FOOTER_LINES;
|
|
262
283
|
}
|
|
263
284
|
|
|
264
|
-
/** Map a flat item list onto the
|
|
285
|
+
/** Map a flat item list onto the stable one-line skeleton (legacy compat). */
|
|
265
286
|
export function footerLinesFromItems(
|
|
266
287
|
items: readonly FooterItem[],
|
|
267
288
|
): FooterLines {
|
|
268
289
|
const selected = new Set(items);
|
|
269
290
|
return normalizeFooterLines(
|
|
270
|
-
|
|
291
|
+
FLAT_FOOTER_ITEM_LINES.map((line) =>
|
|
271
292
|
line.filter((item) => item === "flex" || selected.has(item)),
|
|
272
293
|
),
|
|
273
294
|
);
|
|
@@ -335,7 +356,6 @@ export function applyFooterConfig(
|
|
|
335
356
|
return {
|
|
336
357
|
footerStyle: style,
|
|
337
358
|
footerLines: normalized,
|
|
338
|
-
footerItems: flattenFooterItems(normalized),
|
|
339
359
|
};
|
|
340
360
|
}
|
|
341
361
|
|
|
@@ -358,7 +378,7 @@ function sameFooterLines(left: FooterLines, right: FooterLines) {
|
|
|
358
378
|
|
|
359
379
|
function parseUiFooter(
|
|
360
380
|
ui: Record<string, unknown>,
|
|
361
|
-
): Pick<MyPiSetupConfig["ui"], "footerStyle" | "footerLines"
|
|
381
|
+
): Pick<MyPiSetupConfig["ui"], "footerStyle" | "footerLines"> {
|
|
362
382
|
const style = isFooterStyle(ui.footerStyle)
|
|
363
383
|
? ui.footerStyle
|
|
364
384
|
: DEFAULT_FOOTER_STYLE;
|
|
@@ -371,7 +391,6 @@ function parseUiFooter(
|
|
|
371
391
|
return {
|
|
372
392
|
footerStyle: style,
|
|
373
393
|
footerLines: lines,
|
|
374
|
-
footerItems: flattenFooterItems(lines),
|
|
375
394
|
};
|
|
376
395
|
}
|
|
377
396
|
|
|
@@ -382,14 +401,12 @@ function parseUiFooter(
|
|
|
382
401
|
return {
|
|
383
402
|
footerStyle: style,
|
|
384
403
|
footerLines: lines,
|
|
385
|
-
footerItems: flattenFooterItems(lines),
|
|
386
404
|
};
|
|
387
405
|
}
|
|
388
406
|
|
|
389
407
|
return {
|
|
390
408
|
footerStyle: style,
|
|
391
409
|
footerLines: DEFAULT_FOOTER_LINES,
|
|
392
|
-
footerItems: DEFAULT_FOOTER_ITEMS,
|
|
393
410
|
};
|
|
394
411
|
}
|
|
395
412
|
|
|
@@ -476,6 +493,7 @@ export function parseSetupConfig(value: unknown): MyPiSetupConfig {
|
|
|
476
493
|
),
|
|
477
494
|
},
|
|
478
495
|
ui: {
|
|
496
|
+
webTheme: isWebTheme(ui.webTheme) ? ui.webTheme : "system",
|
|
479
497
|
showHeader: typeof ui.showHeader === "boolean" ? ui.showHeader : false,
|
|
480
498
|
customFooter:
|
|
481
499
|
typeof ui.customFooter === "boolean" ? ui.customFooter : true,
|
|
@@ -484,7 +502,7 @@ export function parseSetupConfig(value: unknown): MyPiSetupConfig {
|
|
|
484
502
|
ui.subagentResultDisplay as DetailDisplay,
|
|
485
503
|
)
|
|
486
504
|
? (ui.subagentResultDisplay as DetailDisplay)
|
|
487
|
-
: "
|
|
505
|
+
: "compact",
|
|
488
506
|
bashToolDisplay: DETAIL_DISPLAYS.includes(
|
|
489
507
|
ui.bashToolDisplay as DetailDisplay,
|
|
490
508
|
)
|
|
@@ -504,9 +522,9 @@ export function parseSetupConfig(value: unknown): MyPiSetupConfig {
|
|
|
504
522
|
/** An empty or invalid value disables the post-edit command (the safe default). */
|
|
505
523
|
function parsePostEditCommand(value: unknown) {
|
|
506
524
|
if (!isRecord(value)) return "";
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
525
|
+
if (typeof value.command !== "string") return "";
|
|
526
|
+
const command = value.command.trim();
|
|
527
|
+
return command.length <= POST_EDIT_COMMAND_MAX_CHARS ? command : "";
|
|
510
528
|
}
|
|
511
529
|
|
|
512
530
|
export function hasSavedSetupConfig() {
|
|
@@ -561,6 +579,9 @@ function replacedFields(
|
|
|
561
579
|
) {
|
|
562
580
|
paths.push("summaries → suggestions");
|
|
563
581
|
}
|
|
582
|
+
if (path === "ui" && "footerItems" in raw && "footerLines" in normalized) {
|
|
583
|
+
paths.push("ui.footerItems");
|
|
584
|
+
}
|
|
564
585
|
for (const [key, value] of Object.entries(normalized)) {
|
|
565
586
|
if (!(key in raw)) continue;
|
|
566
587
|
const here = path ? `${path}.${key}` : key;
|
|
@@ -627,24 +648,55 @@ const sameOwner = (left: LockOwner, right: LockOwner) =>
|
|
|
627
648
|
const sameFile = (left: Stats, right: Stats) =>
|
|
628
649
|
left.dev === right.dev && left.ino === right.ino;
|
|
629
650
|
|
|
651
|
+
const parsePowerShellProcessStartedAt = (stdout: string) => {
|
|
652
|
+
const startedAt = Number(stdout.trim());
|
|
653
|
+
return isPositiveInteger(startedAt) ? startedAt : undefined;
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const parsePsProcessStartedAt = (stdout: string) => {
|
|
657
|
+
const startedAt = Date.parse(stdout.trim());
|
|
658
|
+
return Number.isFinite(startedAt) ? startedAt : undefined;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
export function processStartedAtQuery(
|
|
662
|
+
pid: number,
|
|
663
|
+
platform = process.platform,
|
|
664
|
+
) {
|
|
665
|
+
if (platform === "win32") {
|
|
666
|
+
const script = [
|
|
667
|
+
"$ErrorActionPreference='Stop'",
|
|
668
|
+
`$p=Get-Process -Id ${pid}`,
|
|
669
|
+
"[Console]::Out.Write(([DateTimeOffset]$p.StartTime).ToUnixTimeMilliseconds())",
|
|
670
|
+
].join("; ");
|
|
671
|
+
return {
|
|
672
|
+
command: "powershell.exe",
|
|
673
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", script],
|
|
674
|
+
env: undefined,
|
|
675
|
+
parseOutput: parsePowerShellProcessStartedAt,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
command: "ps",
|
|
680
|
+
args: ["-o", "lstart=", "-p", String(pid)],
|
|
681
|
+
env: { LC_ALL: "C" },
|
|
682
|
+
parseOutput: parsePsProcessStartedAt,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
630
686
|
const queryProcessStartedAt = (pid: number) =>
|
|
631
687
|
new Promise<number | undefined>((resolve) => {
|
|
632
|
-
|
|
633
|
-
resolve(undefined);
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
688
|
+
const query = processStartedAtQuery(pid);
|
|
636
689
|
try {
|
|
637
690
|
execFile(
|
|
638
|
-
|
|
639
|
-
|
|
691
|
+
query.command,
|
|
692
|
+
query.args,
|
|
640
693
|
{
|
|
641
694
|
encoding: "utf8",
|
|
642
|
-
env: { ...process.env,
|
|
695
|
+
env: query.env ? { ...process.env, ...query.env } : process.env,
|
|
643
696
|
timeout: 1_000,
|
|
644
697
|
},
|
|
645
698
|
(error, stdout) => {
|
|
646
|
-
|
|
647
|
-
resolve(Number.isFinite(startedAt) ? startedAt : undefined);
|
|
699
|
+
resolve(error ? undefined : query.parseOutput(stdout));
|
|
648
700
|
},
|
|
649
701
|
);
|
|
650
702
|
} catch {
|
|
@@ -938,13 +990,15 @@ async function withSetupConfigLock<A>(action: () => Promise<A>) {
|
|
|
938
990
|
}
|
|
939
991
|
|
|
940
992
|
async function writeSetupConfig(config: MyPiSetupConfig) {
|
|
993
|
+
const canonical = parseSetupConfig(config);
|
|
941
994
|
const tempPath = `${SETUP_CONFIG_PATH}.${process.pid}.${randomUUID()}.tmp`;
|
|
942
995
|
try {
|
|
943
|
-
await writeFile(tempPath, `${JSON.stringify(
|
|
996
|
+
await writeFile(tempPath, `${JSON.stringify(canonical, null, 2)}\n`, {
|
|
944
997
|
encoding: "utf8",
|
|
945
998
|
mode: 0o600,
|
|
946
999
|
});
|
|
947
1000
|
await rename(tempPath, SETUP_CONFIG_PATH);
|
|
1001
|
+
return canonical;
|
|
948
1002
|
} catch (error) {
|
|
949
1003
|
await unlink(tempPath).catch(() => undefined);
|
|
950
1004
|
throw error;
|
|
@@ -958,10 +1012,7 @@ export async function saveSetupConfig(config: MyPiSetupConfig) {
|
|
|
958
1012
|
});
|
|
959
1013
|
}
|
|
960
1014
|
|
|
961
|
-
export function formatSetupConfig(
|
|
962
|
-
config = loadSetupConfig(),
|
|
963
|
-
integrationLines: readonly string[] = [],
|
|
964
|
-
) {
|
|
1015
|
+
export function formatSetupConfig(config = loadSetupConfig()) {
|
|
965
1016
|
const suggestionModel = config.suggestions.model;
|
|
966
1017
|
const suggestions =
|
|
967
1018
|
!config.suggestions.enabled || !suggestionModel
|
|
@@ -974,13 +1025,12 @@ export function formatSetupConfig(
|
|
|
974
1025
|
`Capability discovery: ${config.capabilities.discovery}`,
|
|
975
1026
|
suggestions,
|
|
976
1027
|
`Workflows: ${config.workflows.concurrency} concurrent agents · ${config.workflows.maxAgentCalls} total calls`,
|
|
977
|
-
`UI: large header ${config.ui.showHeader ? "on" : "off"} · custom footer ${footer}`,
|
|
1028
|
+
`UI: Web theme ${config.ui.webTheme} · large header ${config.ui.showHeader ? "on" : "off"} · custom footer ${footer}`,
|
|
978
1029
|
`Subagent results: ${config.ui.subagentResultDisplay === "full" ? "full by default" : "compact status summary (Ctrl+O expands full output)"}`,
|
|
979
1030
|
`Bash operations: ${config.ui.bashToolDisplay === "full" ? "expanded by default" : "one-line activity summary (Ctrl+O restores native evidence)"}`,
|
|
980
1031
|
`Write/Edit operations: ${config.ui.fileMutationDisplay === "full" ? "expanded by default" : "one-line activity summary (Ctrl+O restores native evidence)"}`,
|
|
981
|
-
`Post-edit command: ${config.postEdit.command ?
|
|
1032
|
+
`Post-edit command: ${config.postEdit.command ? "configured" : "off"}`,
|
|
982
1033
|
`Agent role models (Subagents + Workflows): ${SUBAGENT_ROLE_NAMES.map((role) => `${role} ${config.subagents.roleModels[role] ? `${config.subagents.roleModels[role].provider}/${config.subagents.roleModels[role].model}` : "inherit"}`).join(" · ")}`,
|
|
983
|
-
...integrationLines,
|
|
984
1034
|
].join("\n");
|
|
985
1035
|
}
|
|
986
1036
|
|
|
@@ -998,8 +1048,7 @@ export async function updateSetupConfig(
|
|
|
998
1048
|
return withSetupConfigLock(async () => {
|
|
999
1049
|
const raw = readDocumentForWrite();
|
|
1000
1050
|
const current = parseSetupConfig(raw);
|
|
1001
|
-
const config = mutate(current);
|
|
1002
|
-
await writeSetupConfig(config);
|
|
1051
|
+
const config = await writeSetupConfig(mutate(current));
|
|
1003
1052
|
return { config, replaced: replacedFields(raw, current) };
|
|
1004
1053
|
});
|
|
1005
1054
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
export const OPENPI_SETUP_EPISODE_CHANNEL = "openpi:setup-episode";
|
|
3
3
|
|
|
4
4
|
export interface OpenPiSetupEpisodeState {
|
|
5
|
-
/** True
|
|
5
|
+
/** True only while the owned setup writer is active for a delivered request. */
|
|
6
6
|
readonly active: boolean;
|
|
7
7
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTool,
|
|
3
|
+
type ToolDefinition,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { type TSchema, Type } from "typebox";
|
|
6
|
+
|
|
7
|
+
export const STRUCTURED_OUTPUT_SYSTEM_INSTRUCTION =
|
|
8
|
+
"When your task is complete, call the `structured_output` tool exactly once as your final action, with fields matching the required schema. Do not write any other text after it.";
|
|
9
|
+
|
|
10
|
+
export const STRUCTURED_OUTPUT_TOOL_DESCRIPTION =
|
|
11
|
+
"Return your final result as structured data matching the required schema. Call this exactly once, as your last action; do not write any other text after it.";
|
|
12
|
+
|
|
13
|
+
export const STRUCTURED_RESULT_LIMITS = Object.freeze({
|
|
14
|
+
schemaDepth: 24,
|
|
15
|
+
schemaNodes: 10_000,
|
|
16
|
+
resultBytes: 2 * 1024 * 1024,
|
|
17
|
+
resultDepth: 24,
|
|
18
|
+
resultNodes: 100_000,
|
|
19
|
+
resultStringBytes: 1024 * 1024,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export interface EncodedStructuredResult {
|
|
23
|
+
readonly value: unknown;
|
|
24
|
+
readonly json: string;
|
|
25
|
+
readonly byteLength: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function safeRecordKey(key: string) {
|
|
29
|
+
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Preserve the caller's full JSON Schema instead of lossy keyword conversion. */
|
|
33
|
+
export function jsonSchemaToTypebox(schema: unknown): TSchema {
|
|
34
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
35
|
+
throw new Error("structured output schema must be a bounded JSON object");
|
|
36
|
+
}
|
|
37
|
+
const seen = new WeakSet<object>();
|
|
38
|
+
let nodes = 0;
|
|
39
|
+
const validate = (current: unknown, depth: number): boolean => {
|
|
40
|
+
if (
|
|
41
|
+
++nodes > STRUCTURED_RESULT_LIMITS.schemaNodes ||
|
|
42
|
+
depth > STRUCTURED_RESULT_LIMITS.schemaDepth
|
|
43
|
+
) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (
|
|
47
|
+
current === null ||
|
|
48
|
+
typeof current === "string" ||
|
|
49
|
+
typeof current === "boolean"
|
|
50
|
+
) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (typeof current === "number") return Number.isFinite(current);
|
|
54
|
+
if (Array.isArray(current)) {
|
|
55
|
+
return current.every((item) => validate(item, depth + 1));
|
|
56
|
+
}
|
|
57
|
+
if (typeof current !== "object" || seen.has(current)) return false;
|
|
58
|
+
seen.add(current);
|
|
59
|
+
return Object.keys(current).every(
|
|
60
|
+
(key) =>
|
|
61
|
+
safeRecordKey(key) &&
|
|
62
|
+
validate((current as Record<string, unknown>)[key], depth + 1),
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
if (!validate(schema, 0)) {
|
|
66
|
+
throw new Error("structured output schema must be a bounded JSON object");
|
|
67
|
+
}
|
|
68
|
+
return Type.Unsafe(schema);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Encode one complete JSON result or fail before any truncated artifact exists. */
|
|
72
|
+
export function encodeStructuredResult(
|
|
73
|
+
value: unknown,
|
|
74
|
+
): EncodedStructuredResult {
|
|
75
|
+
const seen = new WeakSet<object>();
|
|
76
|
+
let nodes = 0;
|
|
77
|
+
const validate = (current: unknown, depth: number): void => {
|
|
78
|
+
if (++nodes > STRUCTURED_RESULT_LIMITS.resultNodes) {
|
|
79
|
+
throw new Error("structured result exceeds the node limit");
|
|
80
|
+
}
|
|
81
|
+
if (depth > STRUCTURED_RESULT_LIMITS.resultDepth) {
|
|
82
|
+
throw new Error("structured result exceeds the depth limit");
|
|
83
|
+
}
|
|
84
|
+
if (typeof current === "string") {
|
|
85
|
+
if (
|
|
86
|
+
Buffer.byteLength(current, "utf8") >
|
|
87
|
+
STRUCTURED_RESULT_LIMITS.resultStringBytes
|
|
88
|
+
) {
|
|
89
|
+
throw new Error("structured result contains an oversized string");
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
current === null ||
|
|
95
|
+
typeof current === "boolean" ||
|
|
96
|
+
(typeof current === "number" && Number.isFinite(current))
|
|
97
|
+
) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (typeof current !== "object" || seen.has(current)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"structured result must contain only acyclic JSON values",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
seen.add(current);
|
|
106
|
+
if (Array.isArray(current)) {
|
|
107
|
+
for (const item of current) validate(item, depth + 1);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const [key, item] of Object.entries(current)) {
|
|
111
|
+
if (!safeRecordKey(key)) {
|
|
112
|
+
throw new Error("structured result contains an unsafe object key");
|
|
113
|
+
}
|
|
114
|
+
validate(item, depth + 1);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
validate(value, 0);
|
|
118
|
+
const json = JSON.stringify(value);
|
|
119
|
+
const byteLength = Buffer.byteLength(json, "utf8");
|
|
120
|
+
if (byteLength > STRUCTURED_RESULT_LIMITS.resultBytes) {
|
|
121
|
+
throw new Error("structured result exceeds the total byte limit");
|
|
122
|
+
}
|
|
123
|
+
return { value, json, byteLength };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One-shot terminating child tool shared by Workflow and Direct Subagent. */
|
|
127
|
+
export function createStructuredOutputTool(
|
|
128
|
+
schema: unknown,
|
|
129
|
+
capture: (value: unknown) => void,
|
|
130
|
+
): ToolDefinition {
|
|
131
|
+
return defineTool({
|
|
132
|
+
name: "structured_output",
|
|
133
|
+
label: "Structured Output",
|
|
134
|
+
description: STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
|
|
135
|
+
parameters: jsonSchemaToTypebox(schema),
|
|
136
|
+
async execute(_toolCallId, params) {
|
|
137
|
+
capture(params);
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: "Recorded structured result." }],
|
|
140
|
+
details: params,
|
|
141
|
+
terminate: true,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function childToolsWithStructuredOutput(
|
|
148
|
+
tools: readonly string[] | undefined,
|
|
149
|
+
structured: boolean,
|
|
150
|
+
) {
|
|
151
|
+
return tools
|
|
152
|
+
? [...new Set([...tools, ...(structured ? ["structured_output"] : [])])]
|
|
153
|
+
: undefined;
|
|
154
|
+
}
|
|
@@ -1,35 +1,122 @@
|
|
|
1
|
-
// OSC may end in BEL or ST. DCS/SOS/PM/APC end only in ST; treating BEL as
|
|
2
|
-
// their terminator would expose the rest of a still-hidden payload. Both
|
|
3
|
-
// patterns consume an unterminated string through the bounded input's end.
|
|
4
|
-
// eslint-disable-next-line no-control-regex
|
|
5
|
-
const OSC_PATTERN =
|
|
6
|
-
/(?:\u001b\]|\u009d)(?:[^\u0007\u001b\u009c]|\u001b(?!\\))*(?:\u0007|\u001b\\|\u009c|$)/g;
|
|
7
|
-
// eslint-disable-next-line no-control-regex
|
|
8
|
-
const ST_STRING_PATTERN =
|
|
9
|
-
/(?:\u001b[PX^_]|[\u0090\u0098\u009e\u009f])(?:[^\u001b\u009c]|\u001b(?!\\))*(?:\u001b\\|\u009c|$)/g;
|
|
10
|
-
// eslint-disable-next-line no-control-regex
|
|
11
|
-
const CSI_PATTERN = /(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]/g;
|
|
12
|
-
// Remaining two-byte/charset escape forms (for example ESC ( 0).
|
|
13
|
-
// eslint-disable-next-line no-control-regex
|
|
14
|
-
const ESCAPE_PATTERN = /\u001b(?:[()][0-2A-Z]|[ -/]*[@-~])/g;
|
|
15
1
|
// Invisible Unicode format controls can reorder or disguise untrusted text.
|
|
16
2
|
// Keep only ZWNJ/ZWJ (U+200C/U+200D), which carry legitimate shaping and emoji
|
|
17
3
|
// semantics; variation selectors are marks rather than Cf and remain intact.
|
|
18
|
-
const
|
|
4
|
+
const UNSAFE_FORMAT_CHARACTER = /\p{Cf}/u;
|
|
19
5
|
// eslint-disable-next-line no-control-regex
|
|
20
6
|
const TERMINAL_CONTROL_PATTERN =
|
|
21
7
|
/[\u0000-\u001f\u007f-\u009f]|(?:(?![\u200c\u200d])\p{Cf})/u;
|
|
22
8
|
|
|
9
|
+
type SanitizerState =
|
|
10
|
+
| "text"
|
|
11
|
+
| "escape"
|
|
12
|
+
| "escape_intermediate"
|
|
13
|
+
| "csi"
|
|
14
|
+
| "osc"
|
|
15
|
+
| "st_string";
|
|
16
|
+
|
|
17
|
+
/** Stateful terminal-text projection for streams whose retained head can move. */
|
|
18
|
+
export class TerminalTextSanitizer {
|
|
19
|
+
private state: SanitizerState = "text";
|
|
20
|
+
private stringEscape = false;
|
|
21
|
+
|
|
22
|
+
push(text: string) {
|
|
23
|
+
let safe = "";
|
|
24
|
+
for (const character of text) {
|
|
25
|
+
const code = character.codePointAt(0)!;
|
|
26
|
+
|
|
27
|
+
if (this.state === "osc" || this.state === "st_string") {
|
|
28
|
+
if (character === "\u009c") {
|
|
29
|
+
this.state = "text";
|
|
30
|
+
this.stringEscape = false;
|
|
31
|
+
} else if (this.state === "osc" && character === "\u0007") {
|
|
32
|
+
this.state = "text";
|
|
33
|
+
this.stringEscape = false;
|
|
34
|
+
} else if (this.stringEscape && character === "\\") {
|
|
35
|
+
this.state = "text";
|
|
36
|
+
this.stringEscape = false;
|
|
37
|
+
} else {
|
|
38
|
+
this.stringEscape = character === "\u001b";
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.state === "csi") {
|
|
44
|
+
if (code >= 0x40 && code <= 0x7e) this.state = "text";
|
|
45
|
+
else if (code < 0x20 || code > 0x3f) {
|
|
46
|
+
this.state = "text";
|
|
47
|
+
safe += this.safeCharacter(character, code);
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.state === "escape" || this.state === "escape_intermediate") {
|
|
53
|
+
if (this.state === "escape") {
|
|
54
|
+
if (character === "[") {
|
|
55
|
+
this.state = "csi";
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (character === "]") {
|
|
59
|
+
this.state = "osc";
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
character === "P" ||
|
|
64
|
+
character === "X" ||
|
|
65
|
+
character === "^" ||
|
|
66
|
+
character === "_"
|
|
67
|
+
) {
|
|
68
|
+
this.state = "st_string";
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (code >= 0x20 && code <= 0x2f) {
|
|
73
|
+
this.state = "escape_intermediate";
|
|
74
|
+
} else if (code >= 0x30 && code <= 0x7e) {
|
|
75
|
+
this.state = "text";
|
|
76
|
+
} else {
|
|
77
|
+
this.state = "text";
|
|
78
|
+
safe += this.safeCharacter(character, code);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (character === "\u001b") {
|
|
84
|
+
this.state = "escape";
|
|
85
|
+
} else if (character === "\u009b") {
|
|
86
|
+
this.state = "csi";
|
|
87
|
+
} else if (character === "\u009d") {
|
|
88
|
+
this.state = "osc";
|
|
89
|
+
} else if (
|
|
90
|
+
character === "\u0090" ||
|
|
91
|
+
character === "\u0098" ||
|
|
92
|
+
character === "\u009e" ||
|
|
93
|
+
character === "\u009f"
|
|
94
|
+
) {
|
|
95
|
+
this.state = "st_string";
|
|
96
|
+
} else {
|
|
97
|
+
safe += this.safeCharacter(character, code);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return safe;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private safeCharacter(character: string, code: number) {
|
|
104
|
+
if (character === "\t") return " ";
|
|
105
|
+
if (
|
|
106
|
+
character === "\n" ||
|
|
107
|
+
character === "\u200c" ||
|
|
108
|
+
character === "\u200d"
|
|
109
|
+
) {
|
|
110
|
+
return character;
|
|
111
|
+
}
|
|
112
|
+
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) return "";
|
|
113
|
+
return UNSAFE_FORMAT_CHARACTER.test(character) ? "" : character;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
23
117
|
/** Strip terminal control sequences and direction-spoofing format controls. */
|
|
24
118
|
export function sanitizeTerminalText(text: string) {
|
|
25
|
-
return text
|
|
26
|
-
.replace(OSC_PATTERN, "")
|
|
27
|
-
.replace(ST_STRING_PATTERN, "")
|
|
28
|
-
.replace(CSI_PATTERN, "")
|
|
29
|
-
.replace(ESCAPE_PATTERN, "")
|
|
30
|
-
.replace(UNSAFE_FORMAT_PATTERN, "")
|
|
31
|
-
.replaceAll("\t", " ")
|
|
32
|
-
.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
|
|
119
|
+
return new TerminalTextSanitizer().push(text);
|
|
33
120
|
}
|
|
34
121
|
|
|
35
122
|
/** Reject raw terminal or direction-spoofing controls in display patterns. */
|