@tt-a1i/openpi 0.3.0 → 0.3.1
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.
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
resetOpenPiToolSurface,
|
|
18
18
|
} from "../shared/tool-surface.ts";
|
|
19
19
|
|
|
20
|
-
const CapabilitySchema = Type.
|
|
20
|
+
const CapabilitySchema = Type.Unsafe<OpenPiCapability>({
|
|
21
|
+
type: "string",
|
|
22
|
+
enum: OPENPI_CAPABILITY_NAMES,
|
|
23
|
+
});
|
|
21
24
|
|
|
22
25
|
const OpenPiLoadToolsParameters = Type.Object({
|
|
23
26
|
groups: Type.Optional(
|
|
@@ -95,8 +95,7 @@ import {
|
|
|
95
95
|
SUBAGENT_WAIT_PARAMETER_DESCRIPTIONS,
|
|
96
96
|
SUBAGENT_WAIT_TOOL_DESCRIPTION,
|
|
97
97
|
} from "./src/prompt.ts";
|
|
98
|
-
import {
|
|
99
|
-
import { resultDeliveryOptions } from "../background-terminals/src/result-delivery.ts";
|
|
98
|
+
import { createSubagentResultDelivery } from "./src/result-delivery.ts";
|
|
100
99
|
import {
|
|
101
100
|
effectiveChildToolAllowlist,
|
|
102
101
|
resolveStandaloneChildProjectTrust,
|
|
@@ -210,7 +209,7 @@ export function createSubagentResultDispatcher(
|
|
|
210
209
|
pi: ExtensionAPI,
|
|
211
210
|
outputFor: (snap: SubagentSnapshot) => string = truncatedOutput,
|
|
212
211
|
) {
|
|
213
|
-
return (snaps: readonly SubagentSnapshot[]
|
|
212
|
+
return (snaps: readonly SubagentSnapshot[]) => {
|
|
214
213
|
if (snaps.length === 0) return;
|
|
215
214
|
const content = snaps
|
|
216
215
|
.map((snap) =>
|
|
@@ -249,7 +248,7 @@ export function createSubagentResultDispatcher(
|
|
|
249
248
|
display: false,
|
|
250
249
|
details,
|
|
251
250
|
},
|
|
252
|
-
|
|
251
|
+
{ deliverAs: "followUp", triggerTurn: true },
|
|
253
252
|
);
|
|
254
253
|
};
|
|
255
254
|
}
|
|
@@ -322,8 +321,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
322
321
|
let requestWidgetRender: (() => void) | undefined;
|
|
323
322
|
let navigationLayerRegistered = false;
|
|
324
323
|
let dashboardOpen = false;
|
|
325
|
-
const resultDelivery = createDeferredResultDelivery<SubagentSnapshot>();
|
|
326
324
|
const dispatchResults = createSubagentResultDispatcher(pi);
|
|
325
|
+
const resultDelivery = createSubagentResultDelivery<SubagentSnapshot>({
|
|
326
|
+
isIdle: () => sessionContext?.isIdle() === true,
|
|
327
|
+
// Every unconsumed fire-and-forget result must reach the parent. The
|
|
328
|
+
// delivery coordinator batches results that settled while it was busy.
|
|
329
|
+
deliver: dispatchResults,
|
|
330
|
+
});
|
|
331
|
+
pi.on("agent_settled", () => resultDelivery.parentSettled());
|
|
327
332
|
const hideLifecycleTools = () =>
|
|
328
333
|
patchOwnedTools(pi, "subagents", {
|
|
329
334
|
disable: OPENPI_TOOL_SURFACE.subagents.deferred,
|
|
@@ -436,25 +441,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
441
|
navigationLayerRegistered = true;
|
|
437
442
|
};
|
|
438
443
|
|
|
439
|
-
/**
|
|
440
|
-
* `wake` decides whether this costs the model a turn. A subagent that
|
|
441
|
-
* settled while the model sits idle is the result it is waiting on. A
|
|
442
|
-
* backlog that piled up while it worked is not: waking once per stale
|
|
443
|
-
* subagent forces a turn each, and the model can only answer "that one
|
|
444
|
-
* already finished". `nextTurn` still enters context with the user's next
|
|
445
|
-
* message, without demanding a reply.
|
|
446
|
-
*/
|
|
447
|
-
const deliverResults = (
|
|
448
|
-
snaps: readonly SubagentSnapshot[],
|
|
449
|
-
wake: boolean,
|
|
450
|
-
) => {
|
|
451
|
-
dispatchResults(snaps, wake);
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
const flushResults = (wake: boolean) => {
|
|
455
|
-
deliverResults(resultDelivery.drain(), wake);
|
|
456
|
-
};
|
|
457
|
-
|
|
458
444
|
const deliverBtwResult = (snap: SubagentSnapshot) => {
|
|
459
445
|
// appendEntry is a synchronous SessionManager operation and emits an
|
|
460
446
|
// entry_appended event, so it is safe while the parent is streaming and
|
|
@@ -500,10 +486,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
486
|
// subagent_wait can consume it before agent_settled flushes follow-ups.
|
|
501
487
|
// Defer a copy: the live snapshot keeps mutating if the subagent is
|
|
502
488
|
// restarted before the deferred result flushes.
|
|
489
|
+
// The delivery coordinator closes both sides of the wake-up race: it
|
|
490
|
+
// flushes now if the parent is already idle, otherwise the parent's next
|
|
491
|
+
// agent_settled edge rechecks this same pending Map.
|
|
503
492
|
resultDelivery.defer({ ...snap, meta: { ...snap.meta } });
|
|
504
|
-
// Settled while the model sits idle: it has nothing else in flight, so
|
|
505
|
-
// this is the result it is waiting on — wake it.
|
|
506
|
-
if (sessionContext?.isIdle()) flushResults(true);
|
|
507
493
|
};
|
|
508
494
|
|
|
509
495
|
pi.on("session_start", (_event, ctx) => {
|
|
@@ -530,10 +516,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
530
516
|
managerPromise?.then(updateStatus).catch(() => undefined);
|
|
531
517
|
});
|
|
532
518
|
|
|
533
|
-
// These settled while the model was working on something else, so they go
|
|
534
|
-
// into context without forcing a turn per stale subagent.
|
|
535
|
-
pi.on("agent_settled", () => flushResults(false));
|
|
536
|
-
|
|
537
519
|
pi.on("session_shutdown", async () => {
|
|
538
520
|
if (navigationLayerRegistered) {
|
|
539
521
|
removeEditorLayer(pi, "subagents");
|
|
@@ -1,17 +1,62 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface SubagentResultDeliveryOptions<T> {
|
|
2
|
+
/** True only when the parent has no run or queued continuation in flight. */
|
|
3
|
+
readonly isIdle: () => boolean;
|
|
4
|
+
/** Deliver one drained batch and wake the parent. */
|
|
5
|
+
readonly deliver: (results: readonly T[]) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One-shot result delivery for fire-and-forget subagents.
|
|
10
|
+
*
|
|
11
|
+
* The tool contract promises that a settled child re-invokes the parent. A
|
|
12
|
+
* child that settles while the parent is busy therefore remains retractable
|
|
13
|
+
* until the parent's `agent_settled` event, but it must never be downgraded to
|
|
14
|
+
* a `nextTurn` message that needs another user prompt. There are two symmetric
|
|
15
|
+
* wake-up edges so no ordering can lose the notification:
|
|
16
|
+
*
|
|
17
|
+
* 1. child settles after the parent became idle -> `defer` flushes now;
|
|
18
|
+
* 2. parent settles after the child -> `parentSettled` flushes the batch.
|
|
19
|
+
*
|
|
20
|
+
* The parent boundary wakes even if an earlier extension handler has already
|
|
21
|
+
* started another turn: Pi queues the follow-up into that active run.
|
|
22
|
+
*
|
|
23
|
+
* The Map is the one-shot gate: `subagent_wait` may consume a result before it
|
|
24
|
+
* is delivered, and whichever path drains first prevents duplicate delivery.
|
|
25
|
+
*/
|
|
26
|
+
export function createSubagentResultDelivery<T extends { id: string }>(
|
|
27
|
+
options: SubagentResultDeliveryOptions<T>,
|
|
28
|
+
) {
|
|
2
29
|
const pending = new Map<string, T>();
|
|
3
30
|
|
|
31
|
+
const flush = () => {
|
|
32
|
+
if (pending.size === 0) return;
|
|
33
|
+
const results = [...pending.values()];
|
|
34
|
+
pending.clear();
|
|
35
|
+
try {
|
|
36
|
+
options.deliver(results);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// A synchronous session teardown may reject append/send. Preserve the
|
|
39
|
+
// original batch ahead of anything deferred re-entrantly while delivery
|
|
40
|
+
// ran, so a later boundary can retry without loss or reordering.
|
|
41
|
+
const current = [...pending.values()];
|
|
42
|
+
pending.clear();
|
|
43
|
+
for (const result of results) pending.set(result.id, result);
|
|
44
|
+
for (const result of current) pending.set(result.id, result);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
4
49
|
return {
|
|
5
50
|
defer(result: T) {
|
|
6
51
|
pending.set(result.id, result);
|
|
52
|
+
if (options.isIdle()) flush();
|
|
7
53
|
},
|
|
8
54
|
consume(ids: Iterable<string>) {
|
|
9
55
|
for (const id of ids) pending.delete(id);
|
|
10
56
|
},
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return results;
|
|
57
|
+
/** Flush at the authoritative parent boundary. */
|
|
58
|
+
parentSettled() {
|
|
59
|
+
flush();
|
|
15
60
|
},
|
|
16
61
|
clear() {
|
|
17
62
|
pending.clear();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tt-a1i/openpi",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|