@tt-a1i/openpi 0.6.0 → 0.7.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 +13 -11
- package/SETUP.md +2 -2
- package/THIRD_PARTY_NOTICES.md +242 -0
- package/extensions/ai-providers/README.md +12 -6
- package/extensions/ai-providers/cursor/connect-frame-reader.ts +76 -0
- package/extensions/ai-providers/cursor/input-images.ts +2 -3
- package/extensions/ai-providers/cursor/proto.ts +218 -11
- package/extensions/ai-providers/cursor/protobuf.ts +12 -2
- package/extensions/ai-providers/cursor/provider.ts +280 -37
- package/extensions/ai-providers/cursor/tool-bridge.ts +68 -0
- package/extensions/ai-providers/index.ts +3 -3
- package/extensions/file-mutation-display/index.ts +13 -9
- package/extensions/shared/agent-transcript.ts +3 -2
- package/extensions/shared/child-session.ts +14 -0
- package/extensions/subagents/index.ts +20 -3
- package/extensions/subagents/src/agent-types.ts +5 -17
- package/extensions/subagents/src/backends/pi.ts +70 -59
- package/extensions/subagents/src/backends/tool-preview.ts +29 -0
- package/extensions/subagents/src/manager.ts +2 -71
- package/extensions/subagents/src/prompt.ts +2 -2
- package/extensions/subagents/src/runtime.ts +10 -3
- package/extensions/web/index.ts +69 -5
- package/extensions/workflows/artifacts.ts +362 -23
- package/extensions/workflows/dashboard.ts +141 -21
- package/extensions/workflows/index.ts +62 -20
- package/extensions/workflows/progress-projection.ts +7 -1
- package/extensions/workflows/runner.ts +5 -162
- package/extensions/workflows/sandbox.ts +4 -0
- package/package.json +28 -4
- package/skills/subagents/REFERENCE.md +6 -7
- package/skills/subagents/SKILL.md +1 -1
- package/skills/workflows/REFERENCE.md +3 -1
- package/web/dist/app.js +87 -0
- package/web/dist/favicon.svg +9 -0
- package/web/dist/index.html +15 -0
- package/web/dist/styles.css +3 -0
- package/web/host/web-host.ts +6 -9
- package/web/ui/index.html +3 -131
- package/web/ui/public/favicon.svg +9 -0
- package/web/ui/src/app/App.tsx +134 -0
- package/web/ui/src/app/providers.tsx +38 -0
- package/web/ui/src/components/Markdown.tsx +58 -0
- package/web/ui/src/components/OpenPiLogo.tsx +41 -0
- package/web/ui/src/features/activity/ActivityBar.tsx +120 -0
- package/web/ui/src/features/composer/Composer.tsx +237 -0
- package/web/ui/src/features/sessions/SessionSidebar.tsx +418 -0
- package/web/ui/src/features/transcript/Transcript.tsx +860 -0
- package/web/ui/src/i18n.ts +159 -0
- package/web/ui/src/lib/format.ts +57 -0
- package/web/ui/src/main.tsx +16 -0
- package/web/ui/src/protocol/client.ts +199 -0
- package/web/ui/src/protocol/event-stream.ts +88 -0
- package/web/ui/src/store/web-store.ts +926 -0
- package/web/ui/src/styles.css +420 -0
- package/web/ui/tsconfig.json +12 -0
- package/web/ui/vite-env.d.ts +1 -0
- package/web/vite.config.mjs +21 -1
- package/web/host/static-assets.ts +0 -4
- package/web/ui/app.js +0 -1700
- package/web/ui/styles.css +0 -680
|
@@ -53,7 +53,11 @@ import {
|
|
|
53
53
|
formatActivityStatus,
|
|
54
54
|
} from "../shared/activity-status.ts";
|
|
55
55
|
import { fitNavigationSides } from "../shared/below-editor-navigation.ts";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
inheritedChildToolAllowlist,
|
|
58
|
+
resolveStandaloneChildProjectTrust,
|
|
59
|
+
waitBounded,
|
|
60
|
+
} from "../shared/child-session.ts";
|
|
57
61
|
import { contextPercent } from "../shared/context-utilization.ts";
|
|
58
62
|
import { completionOwnerFor } from "../shared/completion-inbox.ts";
|
|
59
63
|
import {
|
|
@@ -544,6 +548,7 @@ interface ScriptAgentResult {
|
|
|
544
548
|
}
|
|
545
549
|
|
|
546
550
|
interface AgentCallOptions {
|
|
551
|
+
working_dir?: unknown;
|
|
547
552
|
agent_type?: unknown;
|
|
548
553
|
label?: unknown;
|
|
549
554
|
phase?: unknown;
|
|
@@ -1339,11 +1344,12 @@ export default function workflows(
|
|
|
1339
1344
|
structured: boolean,
|
|
1340
1345
|
cwd: string,
|
|
1341
1346
|
agentTypePrompt?: string,
|
|
1347
|
+
childProjectTrusted = projectTrusted,
|
|
1342
1348
|
) =>
|
|
1343
1349
|
createWorkflowResources(
|
|
1344
1350
|
cwd,
|
|
1345
1351
|
structured ? "structured" : "plain",
|
|
1346
|
-
|
|
1352
|
+
childProjectTrusted,
|
|
1347
1353
|
agentTypePrompt,
|
|
1348
1354
|
);
|
|
1349
1355
|
|
|
@@ -1638,6 +1644,36 @@ export default function workflows(
|
|
|
1638
1644
|
);
|
|
1639
1645
|
}
|
|
1640
1646
|
|
|
1647
|
+
const childTools = inheritedChildToolAllowlist(
|
|
1648
|
+
pi.getActiveTools(),
|
|
1649
|
+
agentType?.tools,
|
|
1650
|
+
);
|
|
1651
|
+
if (
|
|
1652
|
+
opts.working_dir !== undefined &&
|
|
1653
|
+
(typeof opts.working_dir !== "string" || !opts.working_dir.trim())
|
|
1654
|
+
) {
|
|
1655
|
+
return fail(
|
|
1656
|
+
`agent "${label}": working_dir must be a non-empty string`,
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
const requestedCwd = path.resolve(
|
|
1660
|
+
ctx.cwd,
|
|
1661
|
+
typeof opts.working_dir === "string" ? opts.working_dir : ".",
|
|
1662
|
+
);
|
|
1663
|
+
try {
|
|
1664
|
+
if (!fs.statSync(requestedCwd).isDirectory())
|
|
1665
|
+
throw new Error("not a directory");
|
|
1666
|
+
} catch {
|
|
1667
|
+
return fail(
|
|
1668
|
+
`agent "${label}": working_dir is not a directory: ${requestedCwd}`,
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
const childProjectTrusted = resolveStandaloneChildProjectTrust({
|
|
1672
|
+
parentCwd: ctx.cwd,
|
|
1673
|
+
childCwd: requestedCwd,
|
|
1674
|
+
parentTrusted: projectTrusted,
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1641
1677
|
const explicitModel =
|
|
1642
1678
|
typeof opts.model === "string" && opts.model.trim()
|
|
1643
1679
|
? opts.model.trim()
|
|
@@ -1708,11 +1744,14 @@ export default function workflows(
|
|
|
1708
1744
|
const operatorFingerprint = operatorKey
|
|
1709
1745
|
? agentCallKey("workflow-operator", {
|
|
1710
1746
|
execution: {
|
|
1747
|
+
cwd: requestedCwd,
|
|
1748
|
+
projectTrusted: childProjectTrusted,
|
|
1749
|
+
tools: childTools,
|
|
1711
1750
|
agentType: agentType
|
|
1712
1751
|
? {
|
|
1713
1752
|
name: agentType.name,
|
|
1714
1753
|
body: agentType.body,
|
|
1715
|
-
tools:
|
|
1754
|
+
tools: childTools,
|
|
1716
1755
|
}
|
|
1717
1756
|
: undefined,
|
|
1718
1757
|
model: model ? `${model.provider}/${model.id}` : undefined,
|
|
@@ -1732,7 +1771,7 @@ export default function workflows(
|
|
|
1732
1771
|
const replaySafe =
|
|
1733
1772
|
operatorKey === undefined &&
|
|
1734
1773
|
isReplaySafeAgentCall({
|
|
1735
|
-
tools: agentType?.tools,
|
|
1774
|
+
tools: agentType?.tools === undefined ? undefined : childTools,
|
|
1736
1775
|
isolation: opts.isolation,
|
|
1737
1776
|
});
|
|
1738
1777
|
const replayLease = beginProcessReplayWorkspaceLease(replaySafe);
|
|
@@ -1744,13 +1783,14 @@ export default function workflows(
|
|
|
1744
1783
|
try {
|
|
1745
1784
|
replayResources = await getResources(
|
|
1746
1785
|
effectiveSchema !== undefined,
|
|
1747
|
-
|
|
1786
|
+
requestedCwd,
|
|
1748
1787
|
agentType?.body,
|
|
1788
|
+
childProjectTrusted,
|
|
1749
1789
|
);
|
|
1750
1790
|
replayIdentity = createReplayIdentity(
|
|
1751
|
-
|
|
1791
|
+
requestedCwd,
|
|
1752
1792
|
replayResources.loader,
|
|
1753
|
-
|
|
1793
|
+
childProjectTrusted,
|
|
1754
1794
|
);
|
|
1755
1795
|
} catch {
|
|
1756
1796
|
// Fingerprinting is an optimization boundary. If resources cannot
|
|
@@ -1768,7 +1808,7 @@ export default function workflows(
|
|
|
1768
1808
|
? {
|
|
1769
1809
|
name: agentType.name,
|
|
1770
1810
|
body: agentType.body,
|
|
1771
|
-
tools:
|
|
1811
|
+
tools: childTools,
|
|
1772
1812
|
}
|
|
1773
1813
|
: undefined,
|
|
1774
1814
|
model: model ? `${model.provider}/${model.id}` : undefined,
|
|
@@ -1925,7 +1965,7 @@ export default function workflows(
|
|
|
1925
1965
|
);
|
|
1926
1966
|
}
|
|
1927
1967
|
const created = await createWorktree({
|
|
1928
|
-
cwd:
|
|
1968
|
+
cwd: requestedCwd,
|
|
1929
1969
|
label,
|
|
1930
1970
|
id: `${details.runId}-${record.index}`,
|
|
1931
1971
|
});
|
|
@@ -1937,18 +1977,19 @@ export default function workflows(
|
|
|
1937
1977
|
worktree = created.worktree;
|
|
1938
1978
|
if (!runSettled) record.worktreeBranch = worktree.branch;
|
|
1939
1979
|
}
|
|
1940
|
-
|
|
1941
|
-
throw runSignal.reason instanceof Error
|
|
1942
|
-
? runSignal.reason
|
|
1943
|
-
: new Error("Workflow was aborted");
|
|
1944
|
-
}
|
|
1945
|
-
const agentCwd = worktree?.path ?? ctx.cwd;
|
|
1980
|
+
const agentCwd = worktree?.path ?? requestedCwd;
|
|
1946
1981
|
|
|
1947
1982
|
// Inside the try, not before it: building resources can throw
|
|
1948
1983
|
// (bad settings, an unreadable skills dir), and a throw out here
|
|
1949
1984
|
// would skip the finally and leak the worktree permanently —
|
|
1950
1985
|
// nothing sweeps `.git/pi-worktrees/` afterwards.
|
|
1951
1986
|
try {
|
|
1987
|
+
if (runSignal.aborted || runSettled) {
|
|
1988
|
+
throw runSignal.reason instanceof Error
|
|
1989
|
+
? runSignal.reason
|
|
1990
|
+
: new Error("Workflow was aborted");
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1952
1993
|
let rejectResourceLoad: (() => void) | undefined;
|
|
1953
1994
|
const resourceAbort = new Promise<never>((_resolve, reject) => {
|
|
1954
1995
|
rejectResourceLoad = () =>
|
|
@@ -1968,6 +2009,7 @@ export default function workflows(
|
|
|
1968
2009
|
effectiveSchema !== undefined,
|
|
1969
2010
|
agentCwd,
|
|
1970
2011
|
agentType?.body,
|
|
2012
|
+
childProjectTrusted,
|
|
1971
2013
|
),
|
|
1972
2014
|
resourceAbort,
|
|
1973
2015
|
]).finally(() => {
|
|
@@ -1992,7 +2034,7 @@ export default function workflows(
|
|
|
1992
2034
|
settingsManager: resources.settingsManager,
|
|
1993
2035
|
...(sessionManager ? { sessionManager } : {}),
|
|
1994
2036
|
modelRegistry: ctx.modelRegistry,
|
|
1995
|
-
|
|
2037
|
+
tools: childTools,
|
|
1996
2038
|
...(testAgentSessionFactory
|
|
1997
2039
|
? { sessionFactory: testAgentSessionFactory }
|
|
1998
2040
|
: {}),
|
|
@@ -2108,9 +2150,9 @@ export default function workflows(
|
|
|
2108
2150
|
// unfingerprintable calls always run for real.
|
|
2109
2151
|
const completedIdentity = callKey
|
|
2110
2152
|
? createReplayIdentity(
|
|
2111
|
-
|
|
2153
|
+
requestedCwd,
|
|
2112
2154
|
resources.loader,
|
|
2113
|
-
|
|
2155
|
+
childProjectTrusted,
|
|
2114
2156
|
)
|
|
2115
2157
|
: undefined;
|
|
2116
2158
|
const completedKey = completedIdentity
|
|
@@ -2157,7 +2199,7 @@ export default function workflows(
|
|
|
2157
2199
|
runId: details.runId,
|
|
2158
2200
|
agentIndex: record.index,
|
|
2159
2201
|
agentLabel: record.label,
|
|
2160
|
-
repoCwd:
|
|
2202
|
+
repoCwd: requestedCwd,
|
|
2161
2203
|
worktree,
|
|
2162
2204
|
});
|
|
2163
2205
|
let cleanup: WorktreeCleanup;
|
|
@@ -2174,7 +2216,7 @@ export default function workflows(
|
|
|
2174
2216
|
const reclaimer =
|
|
2175
2217
|
workflowLifecycleTestHooks?.reclaimWorktree ??
|
|
2176
2218
|
reclaimWorktree;
|
|
2177
|
-
cleanup = await reclaimer(
|
|
2219
|
+
cleanup = await reclaimer(requestedCwd, worktree).catch(
|
|
2178
2220
|
(error): WorktreeCleanup => ({
|
|
2179
2221
|
removed: false,
|
|
2180
2222
|
branchDeleted: false,
|
|
@@ -250,8 +250,11 @@ export class AgentProgressProjection {
|
|
|
250
250
|
snapshot(
|
|
251
251
|
toolTimings: ReadonlyMap<string, ProgressToolTiming> = new Map(),
|
|
252
252
|
): AgentProgressProjectionSnapshot {
|
|
253
|
+
// Reserve the initial task, then spend the remaining byte budget on the
|
|
254
|
+
// newest evidence. Forward selection would silently discard final errors
|
|
255
|
+
// after enough large tool results, even below the entry-count limit.
|
|
253
256
|
const selected = this.firstEntry
|
|
254
|
-
? [this.firstEntry, ...this.tailEntries]
|
|
257
|
+
? [this.firstEntry, ...this.tailEntries.slice().reverse()]
|
|
255
258
|
: [];
|
|
256
259
|
const transcript: TranscriptEntry[] = [];
|
|
257
260
|
let totalBytes = 0;
|
|
@@ -277,6 +280,9 @@ export class AgentProgressProjection {
|
|
|
277
280
|
: { timestamp: entry.timestamp }),
|
|
278
281
|
});
|
|
279
282
|
}
|
|
283
|
+
// Budgeting order is not display order: keep the retained tail chronological.
|
|
284
|
+
const newestFirstTail = transcript.splice(1);
|
|
285
|
+
transcript.push(...newestFirstTail.reverse());
|
|
280
286
|
if (transcript.length < this.totalEntries) {
|
|
281
287
|
transcript.push({
|
|
282
288
|
role: "toolResult",
|
|
@@ -50,7 +50,6 @@ import { truncateUtf8 } from "./serialization.ts";
|
|
|
50
50
|
import { bindWorkflowToolRenderer } from "./tool-renderer.ts";
|
|
51
51
|
|
|
52
52
|
const AGENT_OUTPUT_MAX_BYTES = 64 * 1024;
|
|
53
|
-
export const MODEL_PROGRESS_TIMEOUT_MS = 45_000;
|
|
54
53
|
|
|
55
54
|
export type WorkflowModel = NonNullable<ExtensionContext["model"]>;
|
|
56
55
|
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
@@ -111,8 +110,6 @@ export interface RunAgentOptions {
|
|
|
111
110
|
replayFilesystemBoundary?: ReplayFilesystemBoundaryOptions;
|
|
112
111
|
/** Test-only override for the per-tool execution timeout. */
|
|
113
112
|
toolCallTimeoutMs?: number;
|
|
114
|
-
/** Test-only override for the per-provider-turn model-progress timeout. */
|
|
115
|
-
modelProgressTimeoutMs?: number;
|
|
116
113
|
/** Test-only override for the end-to-end abort/shutdown deadline. */
|
|
117
114
|
shutdownTimeoutMs?: number;
|
|
118
115
|
/** Test seam for lifecycle races; production always uses createAgentSession. */
|
|
@@ -245,119 +242,6 @@ function errorText(error: unknown): string {
|
|
|
245
242
|
);
|
|
246
243
|
}
|
|
247
244
|
|
|
248
|
-
function formatTimeout(timeoutMs: number) {
|
|
249
|
-
return timeoutMs % 1_000 === 0
|
|
250
|
-
? `${timeoutMs / 1_000} seconds`
|
|
251
|
-
: `${timeoutMs} ms`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export function resolveModelProgressTimeoutMs(
|
|
255
|
-
settingsManager: SettingsManager,
|
|
256
|
-
override?: number,
|
|
257
|
-
) {
|
|
258
|
-
if (override !== undefined) return override;
|
|
259
|
-
const configured =
|
|
260
|
-
settingsManager.getProjectSettings().httpIdleTimeoutMs ??
|
|
261
|
-
settingsManager.getGlobalSettings().httpIdleTimeoutMs;
|
|
262
|
-
return typeof configured === "number" && Number.isFinite(configured)
|
|
263
|
-
? Math.max(MODEL_PROGRESS_TIMEOUT_MS, Math.floor(configured))
|
|
264
|
-
: MODEL_PROGRESS_TIMEOUT_MS;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Abort any provider turn that stops producing model-visible progress. */
|
|
268
|
-
export function createModelProgressWatchdog(
|
|
269
|
-
onTimeout: (error: Error) => Promise<unknown>,
|
|
270
|
-
options: { timeoutMs?: number; model?: string } = {},
|
|
271
|
-
) {
|
|
272
|
-
const timeoutMs = options.timeoutMs ?? MODEL_PROGRESS_TIMEOUT_MS;
|
|
273
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
274
|
-
let activeTurn = false;
|
|
275
|
-
let closed = false;
|
|
276
|
-
let rejectTimeout!: (error: Error) => void;
|
|
277
|
-
const timeout = new Promise<never>((_resolve, reject) => {
|
|
278
|
-
rejectTimeout = reject;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
const clear = () => {
|
|
282
|
-
if (timer) clearTimeout(timer);
|
|
283
|
-
timer = undefined;
|
|
284
|
-
};
|
|
285
|
-
const schedule = () => {
|
|
286
|
-
clear();
|
|
287
|
-
if (!activeTurn || closed) return;
|
|
288
|
-
// This timer owns the awaited watchdog outcome. Keep it referenced so a
|
|
289
|
-
// short-lived Node 22 process cannot exit with the promise still pending.
|
|
290
|
-
timer = setTimeout(() => {
|
|
291
|
-
timer = undefined;
|
|
292
|
-
activeTurn = false;
|
|
293
|
-
closed = true;
|
|
294
|
-
const model = options.model ? ` for ${options.model}` : "";
|
|
295
|
-
const error = new Error(
|
|
296
|
-
`Agent provider turn${model} produced no model-visible progress for ${formatTimeout(timeoutMs)}; the provider request may be stalled. Retry the workflow.`,
|
|
297
|
-
);
|
|
298
|
-
rejectTimeout(error);
|
|
299
|
-
try {
|
|
300
|
-
void onTimeout(error).catch(() => {});
|
|
301
|
-
} catch {
|
|
302
|
-
// The timeout result remains authoritative even if abort throws before
|
|
303
|
-
// returning its promise; bounded shutdown below gets another chance.
|
|
304
|
-
}
|
|
305
|
-
}, timeoutMs);
|
|
306
|
-
};
|
|
307
|
-
const armTurn = () => {
|
|
308
|
-
if (closed) return;
|
|
309
|
-
activeTurn = true;
|
|
310
|
-
schedule();
|
|
311
|
-
};
|
|
312
|
-
const markProgress = () => {
|
|
313
|
-
if (!activeTurn || closed) return;
|
|
314
|
-
schedule();
|
|
315
|
-
};
|
|
316
|
-
const completeTurn = () => {
|
|
317
|
-
activeTurn = false;
|
|
318
|
-
clear();
|
|
319
|
-
};
|
|
320
|
-
const cancel = () => {
|
|
321
|
-
closed = true;
|
|
322
|
-
activeTurn = false;
|
|
323
|
-
clear();
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
armTurn,
|
|
328
|
-
markProgress,
|
|
329
|
-
completeTurn,
|
|
330
|
-
cancel,
|
|
331
|
-
async waitFor<T>(operation: Promise<T>) {
|
|
332
|
-
try {
|
|
333
|
-
return await Promise.race([operation, timeout]);
|
|
334
|
-
} finally {
|
|
335
|
-
cancel();
|
|
336
|
-
}
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function isModelVisibleProgress(event: AgentSessionEvent) {
|
|
342
|
-
if (event.type !== "message_update" || event.message.role !== "assistant") {
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
// Raw transport heartbeats never become AgentSession events. Empty stream,
|
|
346
|
-
// text, and thinking starts likewise cannot keep a provider turn alive.
|
|
347
|
-
const update = event.assistantMessageEvent;
|
|
348
|
-
if (
|
|
349
|
-
update.type === "text_delta" ||
|
|
350
|
-
update.type === "thinking_delta" ||
|
|
351
|
-
update.type === "toolcall_delta"
|
|
352
|
-
) {
|
|
353
|
-
return update.delta.length > 0;
|
|
354
|
-
}
|
|
355
|
-
if (update.type === "text_end" || update.type === "thinking_end") {
|
|
356
|
-
return update.content.length > 0;
|
|
357
|
-
}
|
|
358
|
-
return update.type === "toolcall_start" || update.type === "toolcall_end";
|
|
359
|
-
}
|
|
360
|
-
|
|
361
245
|
export async function runAgent(
|
|
362
246
|
options: RunAgentOptions,
|
|
363
247
|
): Promise<AgentOutcome> {
|
|
@@ -367,8 +251,6 @@ export async function runAgent(
|
|
|
367
251
|
let session: AgentSession | undefined;
|
|
368
252
|
let unsubscribeToolGuards: (() => void) | undefined;
|
|
369
253
|
let aborted = false;
|
|
370
|
-
let terminalCause: "abort" | "model-progress-timeout" | undefined;
|
|
371
|
-
let modelProgressTimeoutMessage: string | undefined;
|
|
372
254
|
let abortOperation: Promise<unknown> | undefined;
|
|
373
255
|
let rejectForAbort: ((error: Error) => void) | undefined;
|
|
374
256
|
let rejectForProjectionFailure: ((error: Error) => void) | undefined;
|
|
@@ -389,7 +271,6 @@ export async function runAgent(
|
|
|
389
271
|
const onAbort = () => {
|
|
390
272
|
if (aborted) return;
|
|
391
273
|
aborted = true;
|
|
392
|
-
terminalCause ??= "abort";
|
|
393
274
|
if (session) {
|
|
394
275
|
try {
|
|
395
276
|
abortOperation ??= session.abort();
|
|
@@ -594,10 +475,6 @@ export async function runAgent(
|
|
|
594
475
|
});
|
|
595
476
|
};
|
|
596
477
|
|
|
597
|
-
let armModelProgress = () => {};
|
|
598
|
-
let markModelProgress = () => {};
|
|
599
|
-
let completeModelTurn = () => {};
|
|
600
|
-
let cancelModelProgressWatchdog = () => {};
|
|
601
478
|
let compactionReconcileQueued = false;
|
|
602
479
|
const queueCompactionReconcile = () => {
|
|
603
480
|
if (compactionReconcileQueued) return;
|
|
@@ -625,7 +502,6 @@ export async function runAgent(
|
|
|
625
502
|
};
|
|
626
503
|
const unsubscribe = childSession.subscribe((event) => {
|
|
627
504
|
if (settled) return;
|
|
628
|
-
if (event.type === "turn_start") armModelProgress();
|
|
629
505
|
if (event.type === "tool_execution_start") {
|
|
630
506
|
toolRenderer.start(
|
|
631
507
|
event.toolCallId,
|
|
@@ -648,10 +524,6 @@ export async function runAgent(
|
|
|
648
524
|
event.isError,
|
|
649
525
|
);
|
|
650
526
|
}
|
|
651
|
-
if (isModelVisibleProgress(event)) markModelProgress();
|
|
652
|
-
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
653
|
-
completeModelTurn();
|
|
654
|
-
}
|
|
655
527
|
if (event.type === "message_end") {
|
|
656
528
|
assistantSettlement = observeAssistantSettlement(
|
|
657
529
|
assistantSettlement,
|
|
@@ -686,32 +558,10 @@ export async function runAgent(
|
|
|
686
558
|
captureToolRenderData(childSession.messages);
|
|
687
559
|
snapshotProjection();
|
|
688
560
|
if (!aborted) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
terminalCause ??= "model-progress-timeout";
|
|
692
|
-
if (terminalCause === "model-progress-timeout") {
|
|
693
|
-
modelProgressTimeoutMessage ??= error.message;
|
|
694
|
-
}
|
|
695
|
-
abortOperation ??= childSession.abort();
|
|
696
|
-
void abortOperation.catch(() => {});
|
|
697
|
-
return abortOperation;
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
timeoutMs: resolveModelProgressTimeoutMs(
|
|
701
|
-
options.settingsManager,
|
|
702
|
-
options.modelProgressTimeoutMs,
|
|
703
|
-
),
|
|
704
|
-
model: modelId,
|
|
705
|
-
},
|
|
706
|
-
);
|
|
707
|
-
armModelProgress = watchdog.armTurn;
|
|
708
|
-
markModelProgress = watchdog.markProgress;
|
|
709
|
-
completeModelTurn = watchdog.completeTurn;
|
|
710
|
-
cancelModelProgressWatchdog = watchdog.cancel;
|
|
561
|
+
// Pi owns transport liveness and retries. Quiet model output is not
|
|
562
|
+
// evidence of a stalled request (thinking and retry backoff can be silent).
|
|
711
563
|
await Promise.race([
|
|
712
|
-
|
|
713
|
-
childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
|
|
714
|
-
),
|
|
564
|
+
childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
|
|
715
565
|
abortRace,
|
|
716
566
|
projectionFailureRace,
|
|
717
567
|
]);
|
|
@@ -719,7 +569,6 @@ export async function runAgent(
|
|
|
719
569
|
} catch (error) {
|
|
720
570
|
promptErrorMessage ??= errorText(error);
|
|
721
571
|
} finally {
|
|
722
|
-
cancelModelProgressWatchdog();
|
|
723
572
|
options.signal?.removeEventListener("abort", onAbort);
|
|
724
573
|
settled = true;
|
|
725
574
|
unsubscribe();
|
|
@@ -758,11 +607,7 @@ export async function runAgent(
|
|
|
758
607
|
? `Cleanup failed: ${cleanupErrors.join("; ")}`
|
|
759
608
|
: undefined;
|
|
760
609
|
|
|
761
|
-
if (
|
|
762
|
-
terminalCause === "abort" ||
|
|
763
|
-
(terminalCause === undefined &&
|
|
764
|
-
assistantSettlement?.stopReason === "aborted")
|
|
765
|
-
) {
|
|
610
|
+
if (aborted || assistantSettlement?.stopReason === "aborted") {
|
|
766
611
|
return {
|
|
767
612
|
ok: false,
|
|
768
613
|
output,
|
|
@@ -779,9 +624,7 @@ export async function runAgent(
|
|
|
779
624
|
}
|
|
780
625
|
|
|
781
626
|
const failureMessage =
|
|
782
|
-
(
|
|
783
|
-
? modelProgressTimeoutMessage
|
|
784
|
-
: agentFailureMessage(assistantSettlement, promptErrorMessage)) ??
|
|
627
|
+
agentFailureMessage(assistantSettlement, promptErrorMessage) ??
|
|
785
628
|
cleanupError;
|
|
786
629
|
if (failureMessage !== undefined) {
|
|
787
630
|
return {
|
|
@@ -35,6 +35,7 @@ export interface SandboxAgentOptions {
|
|
|
35
35
|
provider?: unknown;
|
|
36
36
|
effort?: unknown;
|
|
37
37
|
isolation?: unknown;
|
|
38
|
+
working_dir?: unknown;
|
|
38
39
|
operator?: unknown;
|
|
39
40
|
inputs?: unknown;
|
|
40
41
|
}
|
|
@@ -110,6 +111,9 @@ function sanitizeAgentOptions(value: unknown): SandboxAgentOptions {
|
|
|
110
111
|
...(value.provider !== undefined ? { provider: value.provider } : {}),
|
|
111
112
|
...(value.effort !== undefined ? { effort: value.effort } : {}),
|
|
112
113
|
...(value.isolation !== undefined ? { isolation: value.isolation } : {}),
|
|
114
|
+
...(value.working_dir !== undefined
|
|
115
|
+
? { working_dir: value.working_dir }
|
|
116
|
+
: {}),
|
|
113
117
|
...(value.operator !== undefined ? { operator: value.operator } : {}),
|
|
114
118
|
...(value.inputs !== undefined ? { inputs: value.inputs } : {}),
|
|
115
119
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tt-a1i/openpi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "OpenPI — a Pi-native multi-agent workbench with background execution, isolated subagents, replay-safe workflows, goals, tasks, and observable TUI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "tt-a1i",
|
|
@@ -58,21 +58,43 @@
|
|
|
58
58
|
"acorn": "^8.17.0",
|
|
59
59
|
"effect": "^4.0.0-beta.99",
|
|
60
60
|
"jiti": "2.7.0",
|
|
61
|
-
"marked": "^18.0.5",
|
|
62
61
|
"undici": "8.9.0"
|
|
63
62
|
},
|
|
64
63
|
"devDependencies": {
|
|
64
|
+
"@astryxdesign/core": "0.5.2",
|
|
65
|
+
"@astryxdesign/theme-neutral": "0.5.2",
|
|
66
|
+
"@axe-core/playwright": "4.13.0",
|
|
65
67
|
"@biomejs/biome": "2.5.8",
|
|
66
68
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
67
69
|
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
68
70
|
"@earendil-works/pi-tui": "^0.85.1",
|
|
69
71
|
"@effect/tsgo": "^0.24.2",
|
|
70
72
|
"@effect/vitest": "^4.0.0-beta.99",
|
|
73
|
+
"@playwright/test": "1.62.1",
|
|
74
|
+
"@stylexjs/stylex": "0.19.0",
|
|
75
|
+
"@tailwindcss/vite": "4.3.3",
|
|
76
|
+
"@testing-library/react": "16.3.3",
|
|
71
77
|
"@types/node": "^26.1.1",
|
|
78
|
+
"@types/react": "19.2.18",
|
|
79
|
+
"@types/react-dom": "19.2.5",
|
|
80
|
+
"@vitejs/plugin-react": "6.1.1",
|
|
81
|
+
"eventsource-parser": "4.1.0",
|
|
82
|
+
"i18next": "26.4.1",
|
|
83
|
+
"jsdom": "30.0.1",
|
|
84
|
+
"lucide-react": "1.39.0",
|
|
85
|
+
"react": "19.2.8",
|
|
86
|
+
"react-dom": "19.2.8",
|
|
87
|
+
"react-i18next": "17.0.13",
|
|
88
|
+
"react-markdown": "10.1.0",
|
|
89
|
+
"rehype-sanitize": "6.0.0",
|
|
90
|
+
"remark-breaks": "4.0.0",
|
|
91
|
+
"remark-gfm": "4.0.1",
|
|
92
|
+
"tailwindcss": "4.3.3",
|
|
72
93
|
"typebox": "^1.3.6",
|
|
73
94
|
"typescript": "^7.0.2",
|
|
74
95
|
"vite": "^8.2.0",
|
|
75
|
-
"vitest": "4.1.10"
|
|
96
|
+
"vitest": "4.1.10",
|
|
97
|
+
"zustand": "5.0.15"
|
|
76
98
|
},
|
|
77
99
|
"peerDependencies": {
|
|
78
100
|
"@earendil-works/pi-ai": ">=0.85.1",
|
|
@@ -98,7 +120,9 @@
|
|
|
98
120
|
"dev:web": "node --experimental-strip-types scripts/dev-web.mjs",
|
|
99
121
|
"dev:web:ui": "vite --config web/vite.config.mjs --host 127.0.0.1",
|
|
100
122
|
"dev:web:backend": "node scripts/dev-web-backend.mjs",
|
|
101
|
-
"
|
|
123
|
+
"build:web": "vite build --config web/vite.config.mjs",
|
|
124
|
+
"check:web": "node --check scripts/dev-web.mjs && node --check web/vite.config.mjs && tsc --noEmit --project web/ui/tsconfig.json && bun run build:web",
|
|
125
|
+
"test:web:e2e": "bun run build:web && playwright test --config tests/web/playwright.config.ts",
|
|
102
126
|
"benchmark:workflow-child-startup": "node --experimental-strip-types scripts/benchmark-workflow-child-startup.mjs",
|
|
103
127
|
"test": "node scripts/run-tests.mjs",
|
|
104
128
|
"provenance": "node scripts/provenance.mjs"
|
|
@@ -32,7 +32,7 @@ file:line references. You cannot modify files — do not attempt to.
|
|
|
32
32
|
| ------------------ | -------- | ------------------------------------------------------------------------------------ |
|
|
33
33
|
| `name` | yes | `[a-z0-9-]`, ≤64 chars, and must equal the filename stem. |
|
|
34
34
|
| `description` | yes | Shown to the parent model when it picks a type. ≤1024 chars. |
|
|
35
|
-
| `tools` | no | Tool allowlist. **Omit to inherit
|
|
35
|
+
| `tools` | no | Tool allowlist. **Omit to inherit active parent child-eligible tools.** |
|
|
36
36
|
| `model` | no | `provider/model-id`, or a bare id resolved against the current provider; ≤256 chars. |
|
|
37
37
|
| `reasoning_effort` | no | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. |
|
|
38
38
|
| body | no | Appended to the child's system prompt. ≤16384 chars. |
|
|
@@ -50,16 +50,15 @@ file with the same name.
|
|
|
50
50
|
|
|
51
51
|
| Role | Tools | Relative effort guidance | Purpose |
|
|
52
52
|
| ------------- | ------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------ |
|
|
53
|
-
| `explorer` |
|
|
54
|
-
| `implementer` |
|
|
55
|
-
| `reviewer` |
|
|
56
|
-
| `advisor` |
|
|
53
|
+
| `explorer` | Active parent child-eligible tools | Moderate | Read-only codebase tracing; increase for harder tasks. |
|
|
54
|
+
| `implementer` | Active parent child-eligible tools | Medium-high | Focused implementation; adjust for scope and risk. |
|
|
55
|
+
| `reviewer` | Active parent child-eligible tools | High | Read-only correctness, safety, and regression review. |
|
|
56
|
+
| `advisor` | Active parent child-eligible tools | High | Deep read-only analysis and technical advice. |
|
|
57
57
|
|
|
58
58
|
These are relative selection hints, not fixed Pi thinking levels. Built-ins set
|
|
59
59
|
no model or reasoning-effort default. An explicit user requirement takes
|
|
60
60
|
priority; otherwise the parent model chooses from levels supported by the
|
|
61
|
-
resolved child model according to the role and task difficulty.
|
|
62
|
-
allowlists still intersect with plan mode and the child denylist.
|
|
61
|
+
resolved child model according to the role and task difficulty. Custom role allowlists intersect with active parent tools, Plan Mode, and the child denylist. Built-in read-only task guidance is a role instruction, not a filesystem sandbox. Existing custom role files are never widened automatically.
|
|
63
62
|
|
|
64
63
|
## Discovery
|
|
65
64
|
|
|
@@ -10,7 +10,7 @@ The tool definitions are canonical for parameters, limits, model syntax, isolati
|
|
|
10
10
|
- Delegate substantial independent work, not a lookup or edit the parent can do directly.
|
|
11
11
|
- Give the child a standalone prompt with paths, constraints, relevant context, and the expected report; it cannot see the parent conversation or ask the user.
|
|
12
12
|
- Inherit the parent model by default. When choosing the child's reasoning effort, honor an explicit user requirement first; otherwise use the selected role's relative guidance and the task's difficulty, choosing from levels supported by the resolved child model.
|
|
13
|
-
- Prefer a matching agent type when one exists;
|
|
13
|
+
- Prefer a matching agent type when one exists; built-ins inherit active parent tools, while an explicit custom tool list is enforced as a narrowing restriction. Model precedence is explicit spawn override, selected type-file model, configured built-in role model, then parent model. Reasoning precedence is explicit spawn override, selected type default, then parent effort. Types live in `~/.pi/agent/agents/*.md` and, for trusted projects, `.pi/agents/*.md`; see [Agent types](REFERENCE.md).
|
|
14
14
|
- Isolate concurrent writers in worktrees according to the `subagent_spawn` schema so they cannot overwrite one checkout or git index. While Plan Mode is active, use only read-only exploration types (or no type); worktree isolation and types narrowed by Plan Mode are rejected.
|
|
15
15
|
- After spawning, continue useful parent work. In an interactive session, if none remains, tell the user the child is still running and end the turn; automatic result delivery will re-invoke the parent when it settles. Do not block merely because the next step depends on the result or because there is nothing else to do. Use `subagent_wait` only when the user explicitly asks to keep the current response open for the result, or when non-interactive automation must return it in the same invocation.
|
|
16
16
|
- Use optional `output_schema` when downstream work needs a machine-validated result rather than prose. The child then receives one terminating `structured_output` tool, and the run fails if it finishes without submitting a matching value. Keep schemas small and task-specific; the validated JSON is delivered to the parent and preserved in a private content-addressed artifact. Omit the option for ordinary text reports.
|
|
@@ -14,8 +14,10 @@ The `workflow` script is an async JavaScript function body executed in a restric
|
|
|
14
14
|
|
|
15
15
|
`await agent(prompt, options)` runs one child and always resolves to `{ ok, output, structured?, ref?, acceptance?, acceptanceWarning?, error? }`. Check `ok` before reading output. Children receive normal trust-aware resources but cannot recursively orchestrate or ask the user.
|
|
16
16
|
|
|
17
|
-
Useful options include `agent_type`, `label`, `phase`, `schema`, `model`, `provider`, `effort`, `isolation`, `operator`, and `inputs`. The legacy `acceptance` option remains readable only during the 0.x migration window described below.
|
|
17
|
+
Useful options include `agent_type`, `label`, `phase`, `schema`, `model`, `provider`, `effort`, `working_dir`, `isolation`, `operator`, and `inputs`. The legacy `acceptance` option remains readable only during the 0.x migration window described below.
|
|
18
18
|
|
|
19
|
+
- Set `working_dir` when tools must run in another repository. Relative paths resolve against the parent cwd; prompt text alone does not change it. The directory must exist. Project resource trust is checked for the target independently.
|
|
20
|
+
- Built-in roles inherit currently active parent child-eligible tools, including shell/network when available. Explicit custom tool lists only narrow this surface. Built-ins with inherited tools execute for real on resume; custom bounded read-only calls retain the replay filesystem boundary below.
|
|
19
21
|
- Prefer a matching `agent_type`. Model precedence is explicit model/provider, type file, configured built-in role, then parent. Effort precedence is explicit effort, type default, then parent.
|
|
20
22
|
- `schema` validates structured output. Use it whenever later workflow logic branches on fields.
|
|
21
23
|
- `acceptance` is deprecated since OpenPI 0.5 and scheduled for removal in 1.0. Compatibility calls still return the child-authored ledger with `authority: "model-self-attestation"` and a migration warning, but it never determines `ok`. Use ordinary `schema` for findings, then let the parent evaluate them alongside runtime-observed exit codes, test receipts, file fingerprints, and tool results. Old DSL, journals, and artifacts remain readable during 0.x.
|