@aeondave/pi-persona 1.17.3 → 1.17.4
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/AGENTS.md +44 -36
- package/CHANGELOG.md +76 -1
- package/README.md +23 -13
- package/docs/ARCHITECTURE.md +97 -37
- package/docs/COLLABORATION.md +110 -0
- package/docs/EXPERIENCE.md +33 -8
- package/docs/README.md +12 -0
- package/docs/REFERENCE.md +65 -14
- package/package.json +12 -6
- package/src/agents/agent.ts +6 -0
- package/src/bridge.ts +25 -8
- package/src/bus/broker/client.ts +119 -21
- package/src/bus/broker/host.ts +123 -8
- package/src/bus/broker/messages.ts +31 -4
- package/src/bus/contact.ts +113 -38
- package/src/bus/peers.ts +15 -0
- package/src/bus/result-reader.ts +88 -0
- package/src/core/brief.ts +14 -4
- package/src/core/capabilities.ts +1 -1
- package/src/core/leg-thinking.ts +57 -0
- package/src/core/ownership.ts +1 -2
- package/src/core/pi-compat.ts +5 -12
- package/src/core/session-runs.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/engine/adapter.ts +83 -19
- package/src/engine/async.ts +49 -10
- package/src/engine/child.ts +10 -2
- package/src/engine/inproc.ts +87 -46
- package/src/engine/result-grant.ts +62 -0
- package/src/engine/session-tools.ts +6 -0
- package/src/engine/stream.ts +6 -0
- package/src/engine/worktree.ts +17 -7
- package/src/exocom/collaboration-delivery.ts +29 -0
- package/src/exocom/collaboration.ts +148 -0
- package/src/exocom/envelope.ts +43 -4
- package/src/exocom/gate.ts +7 -2
- package/src/exocom/install.ts +45 -17
- package/src/exocom/ledger.ts +58 -8
- package/src/exocom/plane.ts +32 -1
- package/src/exocom/profile.ts +40 -0
- package/src/exocom/registry.ts +5 -0
- package/src/exocom/status.ts +11 -1
- package/src/exocom/write-guard.ts +7 -0
- package/src/extension/broker-host.ts +61 -9
- package/src/extension/engine.ts +20 -21
- package/src/extension/hooks.ts +952 -938
- package/src/extension/identity.ts +41 -75
- package/src/extension/results.ts +136 -0
- package/src/extension/shared.ts +17 -0
- package/src/extension.ts +2963 -2873
- package/src/loader.ts +11 -3
- package/src/orchestration/flow-journal.ts +7 -1
- package/src/orchestration/flow-run.ts +122 -19
- package/src/orchestration/flow.ts +11 -0
- package/src/orchestration/roster.ts +71 -23
- package/src/orchestration/sdk.ts +44 -6
- package/src/orchestration/types.ts +10 -0
- package/src/persona/model-participants.ts +20 -7
- package/src/persona/orchestrate.ts +2 -0
- package/src/persona/persona.ts +1 -1
- package/src/results/archive.ts +281 -0
- package/src/results/record.ts +166 -0
- package/src/results/retrieval.ts +142 -0
- package/src/results/types.ts +128 -0
- package/src/telemetry/contract.ts +1 -1
- package/src/tools/council.ts +40 -23
- package/src/tools/delegate-tool.ts +50 -14
- package/src/tools/delegate.ts +13 -6
- package/src/tools/exocom-collaboration-input.ts +168 -0
- package/src/tools/exocom-collaboration.ts +315 -0
- package/src/tools/exocom-profile.ts +102 -0
- package/src/tools/exocom.ts +20 -3
- package/src/tools/flow.ts +24 -8
- package/src/tools/intercom-tool.ts +98 -14
- package/src/tools/models.ts +15 -0
- package/src/tools/monitor.ts +20 -1
- package/src/tools/read-result.ts +52 -0
- package/src/tools/timer.ts +23 -2
- package/src/ui/agent-overlay.ts +11 -3
- package/src/ui/agent-tree.ts +43 -1
- package/src/ui/literal-tool-result.ts +1 -1
- package/src/ui/model-picker.ts +1 -1
- package/src/ui/orchestration-launch.ts +49 -0
- package/src/ui/session-cards.ts +191 -14
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Supervisor-side broker lifecycle (spec B1-B7): the ONE cross-process host every child-engine
|
|
3
3
|
* leg connects to. Started lazily on the first child-engine build; a failed bind is reported to
|
|
4
4
|
* the user ONCE, children built while it is failed spawn without a bus endpoint (so they never
|
|
5
|
-
* burn the connect backoff against a dead socket), and the next build retries.
|
|
5
|
+
* burn the connect backoff against a dead socket), and the next build retries. Result readers
|
|
6
|
+
* use the separate strict awaited preflight on this SAME host, never bus-less degradation.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { mkdirSync } from "node:fs";
|
|
@@ -11,6 +12,7 @@ import { dirname } from "node:path";
|
|
|
11
12
|
import type { EngineAdapterBroker } from "../engine/adapter.ts";
|
|
12
13
|
import { type BrokerHost, startBrokerHost } from "../bus/broker/host.ts";
|
|
13
14
|
import type { InProcessBus } from "../bus/inproc.ts";
|
|
15
|
+
import { RESULT_READ_TIMEOUT_MS, type PreparedResultReader, type ScopedReader } from "../bus/result-reader.ts";
|
|
14
16
|
|
|
15
17
|
export interface SupervisorBrokerDeps {
|
|
16
18
|
bus: InProcessBus;
|
|
@@ -49,6 +51,8 @@ export class SupervisorBroker {
|
|
|
49
51
|
private readonly expected = new Set<string>();
|
|
50
52
|
private readonly preHostSteers = new Map<string, string[]>();
|
|
51
53
|
private readonly deps: SupervisorBrokerDeps;
|
|
54
|
+
private generation = 0;
|
|
55
|
+
private readonly preparingReaders = new Set<() => void>();
|
|
52
56
|
|
|
53
57
|
constructor(deps: SupervisorBrokerDeps) {
|
|
54
58
|
this.deps = deps;
|
|
@@ -82,6 +86,7 @@ export class SupervisorBroker {
|
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
const start = this.deps.startHost ?? startBrokerHost;
|
|
89
|
+
const generation = this.generation;
|
|
85
90
|
this.promise = start({
|
|
86
91
|
bus: this.deps.bus,
|
|
87
92
|
supervisorHandle: this.deps.supervisorHandle,
|
|
@@ -90,6 +95,7 @@ export class SupervisorBroker {
|
|
|
90
95
|
});
|
|
91
96
|
this.promise.then(
|
|
92
97
|
(h) => {
|
|
98
|
+
if (generation !== this.generation) return; // close() owns the late host's teardown
|
|
93
99
|
this.hostRef = h;
|
|
94
100
|
this.lastError = undefined;
|
|
95
101
|
this.warned = false;
|
|
@@ -98,6 +104,7 @@ export class SupervisorBroker {
|
|
|
98
104
|
this.preHostSteers.clear();
|
|
99
105
|
},
|
|
100
106
|
(err) => {
|
|
107
|
+
if (generation !== this.generation) return;
|
|
101
108
|
this.promise = undefined; // a later build retries
|
|
102
109
|
this.lastError = err instanceof Error ? err.message : String(err);
|
|
103
110
|
const dropped = [...this.preHostSteers.values()].reduce((n, texts) => n + texts.length, 0);
|
|
@@ -112,6 +119,46 @@ export class SupervisorBroker {
|
|
|
112
119
|
);
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/** Strict reader preflight, unlike ordinary bus-less degradation. Call ensure(endpoint)
|
|
123
|
+
* first, then await this BEFORE adapter registration/spawn. The owner must revoke on
|
|
124
|
+
* settlement/spawn failure and close this broker when the owning session changes. */
|
|
125
|
+
async prepareResultReader(handle: string, reader: ScopedReader, signal?: AbortSignal): Promise<PreparedResultReader> {
|
|
126
|
+
const generation = this.generation;
|
|
127
|
+
let revoked = false;
|
|
128
|
+
const revoke = (): void => {
|
|
129
|
+
if (revoked) return;
|
|
130
|
+
revoked = true;
|
|
131
|
+
try { reader.revoke(); } catch { /* fixed diagnostic, even if cleanup throws */ }
|
|
132
|
+
};
|
|
133
|
+
const fail = (): Error => new Error("Result reader preparation failed.");
|
|
134
|
+
const hostPromise = this.promise;
|
|
135
|
+
if (!hostPromise || signal?.aborted) { revoke(); throw fail(); }
|
|
136
|
+
return new Promise<PreparedResultReader>((resolve, reject) => {
|
|
137
|
+
let settled = false;
|
|
138
|
+
const cleanup = (): void => {
|
|
139
|
+
settled = true;
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
this.preparingReaders.delete(cancel);
|
|
142
|
+
signal?.removeEventListener("abort", cancel);
|
|
143
|
+
};
|
|
144
|
+
const cancel = (): void => {
|
|
145
|
+
if (settled) return;
|
|
146
|
+
revoke(); cleanup(); reject(fail());
|
|
147
|
+
};
|
|
148
|
+
const timer = setTimeout(cancel, RESULT_READ_TIMEOUT_MS);
|
|
149
|
+
timer.unref?.();
|
|
150
|
+
this.preparingReaders.add(cancel);
|
|
151
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
152
|
+
void hostPromise.then(async (host) => {
|
|
153
|
+
if (settled) return;
|
|
154
|
+
const grant = await host.prepareResultReader(handle, { read: (request, s) => reader.read(request, s), revoke }, signal);
|
|
155
|
+
if (settled || generation !== this.generation || signal?.aborted) { grant.revoke(); cancel(); return; }
|
|
156
|
+
cleanup();
|
|
157
|
+
resolve(grant);
|
|
158
|
+
}).catch(cancel);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
115
162
|
/** The adapter-facing deps, or undefined while the last start failed (children spawn bus-less). */
|
|
116
163
|
adapterDeps(endpoint: string): EngineAdapterBroker | undefined {
|
|
117
164
|
const failedBefore = this.state() === "failed";
|
|
@@ -141,6 +188,7 @@ export class SupervisorBroker {
|
|
|
141
188
|
return true;
|
|
142
189
|
},
|
|
143
190
|
hasPendingAskFrom: (handle) => this.deps.bus.hasPendingAskFrom(handle),
|
|
191
|
+
prepareResultReader: (handle, reader, signal) => this.prepareResultReader(handle, reader, signal),
|
|
144
192
|
};
|
|
145
193
|
}
|
|
146
194
|
|
|
@@ -164,18 +212,22 @@ export class SupervisorBroker {
|
|
|
164
212
|
}
|
|
165
213
|
|
|
166
214
|
async close(): Promise<void> {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
} catch {
|
|
172
|
-
/* best-effort — never block shutdown on a broker teardown error */
|
|
173
|
-
}
|
|
174
|
-
}
|
|
215
|
+
this.generation++;
|
|
216
|
+
for (const cancel of this.preparingReaders) cancel();
|
|
217
|
+
const host = this.hostRef;
|
|
218
|
+
const pending = this.promise;
|
|
175
219
|
this.hostRef = undefined;
|
|
176
220
|
this.promise = undefined;
|
|
221
|
+
this.lastError = undefined;
|
|
177
222
|
this.peers.clear();
|
|
178
223
|
this.expected.clear();
|
|
179
224
|
this.preHostSteers.clear();
|
|
225
|
+
try {
|
|
226
|
+
// Invoke immediately: bound readers revoke before the first await.
|
|
227
|
+
if (host) await host.close();
|
|
228
|
+
else if (pending) await (await pending).close();
|
|
229
|
+
} catch {
|
|
230
|
+
/* best-effort — never block shutdown on a broker teardown error */
|
|
231
|
+
}
|
|
180
232
|
}
|
|
181
233
|
}
|
package/src/extension/engine.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { emptyUsage, type ProgressSnapshot } from "../engine/stream.ts";
|
|
|
21
21
|
import type { AgentRunSpec, StrategyEngine } from "../orchestration/sdk.ts";
|
|
22
22
|
import type { AgentResult } from "../orchestration/types.ts";
|
|
23
23
|
import type { InProcessBus } from "../bus/inproc.ts";
|
|
24
|
+
import type { ResultArchiveView } from "../results/retrieval.ts";
|
|
24
25
|
|
|
25
26
|
export interface EngineFactories {
|
|
26
27
|
makeEngine: typeof makeEngine;
|
|
@@ -45,6 +46,10 @@ export interface BuildEngineDeps {
|
|
|
45
46
|
runLimits: RunLimits;
|
|
46
47
|
bus: InProcessBus;
|
|
47
48
|
supervisorHandle: string;
|
|
49
|
+
/** The live terminal-result archive (a GETTER: the owning session can change across runs).
|
|
50
|
+
* Feeds both engines' reference-handoff preflight + scoped `read_result` readers.
|
|
51
|
+
* Absent ⇒ reference-handoff legs fail closed on both engines (no archive to page). */
|
|
52
|
+
resultArchive?: () => ResultArchiveView | undefined;
|
|
48
53
|
/** Breadcrumb for a provider reroute (toast + log); absent ⇒ silent, as before. */
|
|
49
54
|
onFallback?: (info: { from: string; to: string; agent: string }) => void;
|
|
50
55
|
/** Injected git runner for the non-worktree files-changed report (tests only); defaults to
|
|
@@ -69,7 +74,7 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
69
74
|
const {
|
|
70
75
|
agents, contractDefs, controller, host, config, personaConfigs, lastCtx, workerSpineText,
|
|
71
76
|
engineFactories, makeBrokerDeps, userAgentDir, childPiSettingsEnv, runLimits: RUN_LIMITS,
|
|
72
|
-
bus, supervisorHandle: SUPERVISOR, onFallback,
|
|
77
|
+
bus, supervisorHandle: SUPERVISOR, onFallback, resultArchive,
|
|
73
78
|
} = d();
|
|
74
79
|
const resolveAgent = (n: string): AgentConfig | undefined => agents.find((a) => a.name === n);
|
|
75
80
|
// A named contract file (contracts/<name>.contract.json) wins; "default" is the built-in.
|
|
@@ -111,7 +116,7 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
111
116
|
const getBrokerDeps = (): EngineAdapterBroker | undefined => {
|
|
112
117
|
if (!brokerDepsBuilt) {
|
|
113
118
|
brokerDepsBuilt = true;
|
|
114
|
-
const sessionId =
|
|
119
|
+
const sessionId = lastCtx?.sessionManager.getSessionId();
|
|
115
120
|
if (config.broker && lastCtx && sessionId) brokerDepsMemo = makeBrokerDeps(lastCtx);
|
|
116
121
|
}
|
|
117
122
|
return brokerDepsMemo;
|
|
@@ -137,6 +142,9 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
137
142
|
if (onProgress) deps.childOptions.onProgress = onProgress;
|
|
138
143
|
const brokerDeps = getBrokerDeps();
|
|
139
144
|
if (brokerDeps) deps.broker = brokerDeps;
|
|
145
|
+
// Reference-handoff legs page the owning session's terminal archive (strict preflight
|
|
146
|
+
// + scoped reader); the getter is read at LEG time so a mid-run session switch is seen.
|
|
147
|
+
if (resultArchive !== undefined) deps.resultArchive = resultArchive;
|
|
140
148
|
// Peer messaging obeys the persona's bus capability, and blocking asks are honoured
|
|
141
149
|
// only for async runs — same guards as the inproc engine below (spec B7 / §4.9).
|
|
142
150
|
const caps = controller.capabilities;
|
|
@@ -164,6 +172,7 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
164
172
|
// can't answer, so blocking there would deadlock; the tool downgrades to one-way).
|
|
165
173
|
ideps.bus = bus;
|
|
166
174
|
ideps.supervisorHandle = SUPERVISOR;
|
|
175
|
+
if (resultArchive !== undefined) ideps.resultArchive = resultArchive;
|
|
167
176
|
if (controller.activePersona?.coaching) ideps.coaching = true;
|
|
168
177
|
// Peer messaging obeys the persona's bus capability (canUseBus; Task: sibling peer comm).
|
|
169
178
|
const caps = controller.capabilities;
|
|
@@ -187,6 +196,7 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
187
196
|
if (onProgress) deps.childOptions.onProgress = onProgress;
|
|
188
197
|
const brokerDeps = getBrokerDeps();
|
|
189
198
|
if (brokerDeps) deps.broker = brokerDeps;
|
|
199
|
+
if (resultArchive !== undefined) deps.resultArchive = resultArchive;
|
|
190
200
|
// Peer messaging obeys the persona's bus capability, and blocking asks are honoured
|
|
191
201
|
// only for async runs — same guards as the inproc engine above (spec B7 / §4.9).
|
|
192
202
|
const caps = controller.capabilities;
|
|
@@ -248,9 +258,9 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
248
258
|
const preflight = await worktreePreflight(root, defaultGitExec);
|
|
249
259
|
if (!preflight.ok) return isolatedWorktreeFailure(spec, preflight.error);
|
|
250
260
|
try {
|
|
251
|
-
return await withWorktree(root, defaultGitExec, async (dir) => {
|
|
261
|
+
return await withWorktree(root, defaultGitExec, async (dir, baseCommit) => {
|
|
252
262
|
const result = await childEngineAt(dir).run({ ...spec, isolation: "none" }, perProgress, perSignal, perSteer);
|
|
253
|
-
const artifact = await captureWorktreeArtifact(dir, defaultGitExec);
|
|
263
|
+
const artifact = await captureWorktreeArtifact(dir, baseCommit, defaultGitExec);
|
|
254
264
|
const hasOutputArtifact = hasUnifiedDiff(result.output);
|
|
255
265
|
if (!result.ok) {
|
|
256
266
|
return artifact.ok && artifact.diff.trim().length > 0
|
|
@@ -314,38 +324,27 @@ export function createBuildEngine(d: () => BuildEngineDeps): BuildEngine {
|
|
|
314
324
|
// The host's model-level availability snapshot, plus the exact active model. A provider's
|
|
315
325
|
// credentials do not grant every model in its catalog. Extension-native bridges can be
|
|
316
326
|
// absent from an auth snapshot: restore only their explicit registered model lists in that
|
|
317
|
-
// case, never every inherited model under their provider id.
|
|
318
|
-
// availability API retain their historical getAll() fallback.
|
|
327
|
+
// case, never every inherited model under their provider id.
|
|
319
328
|
export function configuredModels(ctx: ExtensionContext): ModelCostMetadata[] {
|
|
320
329
|
const reg = ctx.modelRegistry;
|
|
321
330
|
const all = reg.getAll();
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
available = reg.getAvailable();
|
|
325
|
-
} catch {
|
|
326
|
-
return all;
|
|
327
|
-
}
|
|
331
|
+
const available = reg.getAvailable();
|
|
328
332
|
const byRef = new Map(available.map((m) => [`${m.provider}\0${m.id}`, m]));
|
|
329
333
|
if (ctx.model) byRef.set(`${ctx.model.provider}\0${ctx.model.id}`, ctx.model);
|
|
330
334
|
const availableProviders = new Set(available.map((m) => m.provider));
|
|
331
335
|
const declaredRefs = new Set<string>();
|
|
332
|
-
|
|
333
|
-
try {
|
|
334
|
-
registered = reg.getRegisteredProviderIds?.() ?? [];
|
|
335
|
-
} catch {
|
|
336
|
-
/* a third-party/older registry facade may not expose extension provider ids */
|
|
337
|
-
}
|
|
336
|
+
const registered = reg.getRegisteredProviderIds();
|
|
338
337
|
for (const provider of registered) {
|
|
339
338
|
// A populated snapshot can intentionally exclude individual models for this credential.
|
|
340
339
|
if (availableProviders.has(provider)) continue;
|
|
341
340
|
let native: ReturnType<typeof reg.getRegisteredNativeProvider>;
|
|
342
341
|
try {
|
|
343
|
-
native = reg.getRegisteredNativeProvider
|
|
342
|
+
native = reg.getRegisteredNativeProvider(provider);
|
|
344
343
|
// An empty credential-filtered list is authoritative too; it need not be a stale snapshot.
|
|
345
|
-
if (reg.getProvider
|
|
344
|
+
if (reg.getProvider(provider)?.filterModels || native?.filterModels) continue;
|
|
346
345
|
} catch { continue; }
|
|
347
346
|
try {
|
|
348
|
-
const models = reg.getRegisteredProviderConfig
|
|
347
|
+
const models = reg.getRegisteredProviderConfig(provider)?.models;
|
|
349
348
|
if (Array.isArray(models)) {
|
|
350
349
|
for (const model of models) {
|
|
351
350
|
if ((model.type === undefined || model.type === "chat") && typeof model.id === "string") declaredRefs.add(`${provider}\0${model.id}`);
|