@tt-a1i/openpi 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -10
- package/SETUP.md +8 -2
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/bin/openpi.js +25 -15
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +8 -1
- package/extensions/background-terminals/src/manager.ts +3 -5
- package/extensions/background-terminals/src/result-delivery.ts +43 -23
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +5 -1
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +45 -1
- package/extensions/plan-mode/index.ts +75 -4
- package/extensions/setup/index.ts +15 -3
- package/extensions/shared/child-session.ts +25 -5
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/setup-config.ts +10 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/subagents/index.ts +44 -4
- package/extensions/subagents/src/backends/pi.ts +76 -5
- package/extensions/subagents/src/domain.ts +16 -1
- package/extensions/subagents/src/manager.ts +5 -0
- package/extensions/subagents/src/prompt.ts +17 -3
- package/extensions/subagents/src/result-artifact.ts +32 -0
- package/extensions/subagents/src/result-delivery.ts +33 -14
- package/extensions/ui-customization/footer.ts +16 -5
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +25 -2
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/completion-projection.ts +3 -1
- package/extensions/workflows/dashboard.ts +8 -0
- package/extensions/workflows/index.ts +13 -0
- package/extensions/workflows/model.ts +5 -1
- package/extensions/workflows/prompt.ts +4 -10
- package/extensions/workflows/result-delivery.ts +96 -22
- package/extensions/workflows/retention.ts +6 -0
- package/extensions/workflows/runner.ts +6 -71
- package/package.json +7 -7
- package/skills/subagents/REFERENCE.md +3 -2
- package/skills/subagents/SKILL.md +1 -0
- package/skills/workflows/REFERENCE.md +3 -3
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +3 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/web-host.ts +330 -50
- package/web/protocol/types.ts +5 -0
- package/web/runtime/pi-runtime.ts +240 -25
- package/web/runtime/types.ts +32 -1
- package/web/ui/app.js +343 -41
- package/web/ui/index.html +3 -0
- package/web/ui/styles.css +119 -37
|
@@ -15,11 +15,15 @@ import {
|
|
|
15
15
|
hasTrustRequiringProjectResources,
|
|
16
16
|
} from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import {
|
|
18
|
+
type WebActiveTurn,
|
|
18
19
|
type WebModelSelectionOptions,
|
|
19
20
|
type WebPromptOptions,
|
|
21
|
+
type WebPromptAdmissionReceipt,
|
|
20
22
|
type WebRuntimeController,
|
|
21
23
|
type WebRuntimeEvent,
|
|
22
24
|
type WebSessionCreationOptions,
|
|
25
|
+
type WebTurnCancellationOptions,
|
|
26
|
+
type WebTurnCancellationResult,
|
|
23
27
|
WebRuntimeRequestError,
|
|
24
28
|
} from "./types.ts";
|
|
25
29
|
import { projectMessage } from "../protocol/types.ts";
|
|
@@ -35,6 +39,7 @@ import {
|
|
|
35
39
|
} from "./web-host-lease.ts";
|
|
36
40
|
|
|
37
41
|
const STARTUP_TIMEOUT_MS = 15_000;
|
|
42
|
+
const TURN_CANCELLATION_SETTLEMENT_TIMEOUT_MS = 10_000;
|
|
38
43
|
const BOOTSTRAP_WORKSPACE_DIRECTORY = ".bootstrap-workspace";
|
|
39
44
|
|
|
40
45
|
type PromptTrace = {
|
|
@@ -43,6 +48,13 @@ type PromptTrace = {
|
|
|
43
48
|
startedAt: number;
|
|
44
49
|
started: boolean;
|
|
45
50
|
queued: boolean;
|
|
51
|
+
userMessageObserved: boolean;
|
|
52
|
+
epoch?: number;
|
|
53
|
+
outcome?: "completed" | "cancelled" | "failed" | "uncertain";
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type TurnSettlement = WebActiveTurn & {
|
|
57
|
+
outcome: "completed" | "cancelled" | "failed" | "uncertain";
|
|
46
58
|
};
|
|
47
59
|
|
|
48
60
|
function errorText(error: unknown) {
|
|
@@ -77,6 +89,14 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
77
89
|
private promptAdmission: Promise<void> = Promise.resolve();
|
|
78
90
|
private activePromptTrace?: PromptTrace;
|
|
79
91
|
private readonly pendingPromptTraces: PromptTrace[] = [];
|
|
92
|
+
private nextTurnEpoch = 0;
|
|
93
|
+
private readonly terminalTurnKeys = new Set<string>();
|
|
94
|
+
private readonly turnSettlementWaiters = new Map<
|
|
95
|
+
string,
|
|
96
|
+
Set<(settlement: TurnSettlement) => void>
|
|
97
|
+
>();
|
|
98
|
+
/** Native aborts remain owned by Pi until its agent_settled event arrives. */
|
|
99
|
+
private readonly turnAbortOperations = new Map<string, Promise<unknown>>();
|
|
80
100
|
private liveMessageKey?: string;
|
|
81
101
|
private liveMessageSequence = 0;
|
|
82
102
|
private readonly webSessionDirectory: string;
|
|
@@ -124,6 +144,7 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
124
144
|
const webSessionDirectory = join(getAgentDir(), "web-sessions");
|
|
125
145
|
const webHostLease = await acquireWebHostLease(webSessionDirectory);
|
|
126
146
|
let runtime: PiWebRuntime | undefined;
|
|
147
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
127
148
|
try {
|
|
128
149
|
const created = await PiWebRuntime.createRuntime(
|
|
129
150
|
canonicalCwd,
|
|
@@ -174,6 +195,109 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
174
195
|
return !this.runtime.session.isStreaming;
|
|
175
196
|
}
|
|
176
197
|
|
|
198
|
+
getActiveTurn() {
|
|
199
|
+
return this.activeTurnFromTrace(this.activePromptTrace);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
cancelTurn(options: WebTurnCancellationOptions) {
|
|
203
|
+
return this.serializeControllerMutation(() =>
|
|
204
|
+
this.cancelActiveTurn(options),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async cancelActiveTurn(
|
|
209
|
+
options: WebTurnCancellationOptions,
|
|
210
|
+
): Promise<WebTurnCancellationResult> {
|
|
211
|
+
this.assertActive();
|
|
212
|
+
this.assertWorkspaceSelected();
|
|
213
|
+
const activeSessionId = this.runtime.session.sessionManager.getSessionId();
|
|
214
|
+
if (options.sessionId !== activeSessionId) {
|
|
215
|
+
return { ...options, state: "stale-session" };
|
|
216
|
+
}
|
|
217
|
+
const key = this.turnKey(options);
|
|
218
|
+
if (this.terminalTurnKeys.has(key)) {
|
|
219
|
+
return { ...options, state: "already-settled" };
|
|
220
|
+
}
|
|
221
|
+
const activeTurn = this.getActiveTurn();
|
|
222
|
+
if (
|
|
223
|
+
!activeTurn ||
|
|
224
|
+
activeTurn.commandId !== options.commandId ||
|
|
225
|
+
activeTurn.epoch !== options.epoch
|
|
226
|
+
) {
|
|
227
|
+
return { ...options, state: "stale-turn" };
|
|
228
|
+
}
|
|
229
|
+
if (this.turnAbortOperations.has(key)) {
|
|
230
|
+
return {
|
|
231
|
+
...options,
|
|
232
|
+
state: "failed",
|
|
233
|
+
error: "Cancellation is already waiting for Pi to settle this turn",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let ownWaiter: ((settlement: TurnSettlement) => void) | undefined;
|
|
238
|
+
const settlement = new Promise<TurnSettlement>((resolveSettlement) => {
|
|
239
|
+
ownWaiter = resolveSettlement;
|
|
240
|
+
const waiters = this.turnSettlementWaiters.get(key) ?? new Set();
|
|
241
|
+
waiters.add(resolveSettlement);
|
|
242
|
+
this.turnSettlementWaiters.set(key, waiters);
|
|
243
|
+
});
|
|
244
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
245
|
+
try {
|
|
246
|
+
const abortOperation = this.runtime.session.abort();
|
|
247
|
+
this.turnAbortOperations.set(key, abortOperation);
|
|
248
|
+
void abortOperation.catch(() => {
|
|
249
|
+
if (this.turnAbortOperations.get(key) === abortOperation) {
|
|
250
|
+
this.turnAbortOperations.delete(key);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const abortFailure = new Promise<never>((_, reject) => {
|
|
254
|
+
void abortOperation.catch(reject);
|
|
255
|
+
});
|
|
256
|
+
const settlementTimeout = new Promise<never>((_, reject) => {
|
|
257
|
+
timeoutHandle = setTimeout(
|
|
258
|
+
() =>
|
|
259
|
+
reject(
|
|
260
|
+
new Error(
|
|
261
|
+
"Cancellation did not settle within the bounded wait window",
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
TURN_CANCELLATION_SETTLEMENT_TIMEOUT_MS,
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
const terminal = await Promise.race([
|
|
268
|
+
settlement,
|
|
269
|
+
abortFailure,
|
|
270
|
+
settlementTimeout,
|
|
271
|
+
]);
|
|
272
|
+
return {
|
|
273
|
+
...options,
|
|
274
|
+
state:
|
|
275
|
+
terminal.outcome === "cancelled"
|
|
276
|
+
? "accepted"
|
|
277
|
+
: terminal.outcome === "completed"
|
|
278
|
+
? "already-settled"
|
|
279
|
+
: "failed",
|
|
280
|
+
...(terminal.outcome === "failed"
|
|
281
|
+
? { error: "The active turn failed while cancellation was requested" }
|
|
282
|
+
: terminal.outcome === "uncertain"
|
|
283
|
+
? {
|
|
284
|
+
error:
|
|
285
|
+
"Pi settled without a terminal assistant outcome for this cancellation",
|
|
286
|
+
}
|
|
287
|
+
: {}),
|
|
288
|
+
};
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return { ...options, state: "failed", error: errorText(error) };
|
|
291
|
+
} finally {
|
|
292
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
293
|
+
const waiters = this.turnSettlementWaiters.get(key);
|
|
294
|
+
if (waiters && ownWaiter) {
|
|
295
|
+
waiters.delete(ownWaiter);
|
|
296
|
+
if (waiters.size === 0) this.turnSettlementWaiters.delete(key);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
177
301
|
listModels() {
|
|
178
302
|
const current = this.runtime.session.model;
|
|
179
303
|
const available = [...this.runtime.services.modelRuntime.getAvailableSnapshot()];
|
|
@@ -287,23 +411,25 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
287
411
|
releaseAdmission = resolveAdmission;
|
|
288
412
|
});
|
|
289
413
|
const startedAt = performance.now();
|
|
290
|
-
const queued = session.isStreaming;
|
|
291
414
|
const promptTrace: PromptTrace | undefined = options?.commandId
|
|
292
415
|
? {
|
|
293
416
|
commandId: options.commandId,
|
|
294
417
|
sessionId,
|
|
295
418
|
startedAt,
|
|
296
419
|
started: false,
|
|
297
|
-
queued,
|
|
420
|
+
queued: false,
|
|
421
|
+
userMessageObserved: false,
|
|
298
422
|
}
|
|
299
423
|
: undefined;
|
|
300
424
|
this.retainRuntimeReference(agentRuntime);
|
|
301
|
-
let resolveRequest: () => void = () => undefined;
|
|
425
|
+
let resolveRequest: (receipt: WebPromptAdmissionReceipt) => void = () => undefined;
|
|
302
426
|
let rejectRequest: (error: unknown) => void = () => undefined;
|
|
303
|
-
const requestAdmission = new Promise<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
427
|
+
const requestAdmission = new Promise<WebPromptAdmissionReceipt>(
|
|
428
|
+
(resolveRequestAdmission, reject) => {
|
|
429
|
+
resolveRequest = resolveRequestAdmission;
|
|
430
|
+
rejectRequest = reject;
|
|
431
|
+
},
|
|
432
|
+
);
|
|
307
433
|
const operation = (async () => {
|
|
308
434
|
let preflightObserved = false;
|
|
309
435
|
let admitted = false;
|
|
@@ -331,14 +457,15 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
331
457
|
elapsedMs: elapsed(startedAt),
|
|
332
458
|
});
|
|
333
459
|
}
|
|
334
|
-
|
|
460
|
+
let followUpMessages = session.getFollowUpMessages().length;
|
|
335
461
|
unsubscribePromptLifecycle = session.subscribe((event) => {
|
|
336
462
|
if (event.type === "agent_start") agentLifecycleStarted = true;
|
|
337
|
-
if (
|
|
338
|
-
event.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
463
|
+
if (event.type === "queue_update") {
|
|
464
|
+
if (event.followUp.length > followUpMessages) {
|
|
465
|
+
queuedForAgent = true;
|
|
466
|
+
if (promptTrace) promptTrace.queued = true;
|
|
467
|
+
}
|
|
468
|
+
followUpMessages = event.followUp.length;
|
|
342
469
|
}
|
|
343
470
|
});
|
|
344
471
|
await session.prompt(content, {
|
|
@@ -363,7 +490,9 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
363
490
|
);
|
|
364
491
|
}
|
|
365
492
|
if (accepted) {
|
|
366
|
-
resolveRequest(
|
|
493
|
+
resolveRequest({
|
|
494
|
+
pendingFollowUps: session.getFollowUpMessages().length,
|
|
495
|
+
});
|
|
367
496
|
} else {
|
|
368
497
|
rejectRequest(
|
|
369
498
|
new WebRuntimeRequestError(
|
|
@@ -432,6 +561,7 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
432
561
|
});
|
|
433
562
|
}
|
|
434
563
|
if (promptTrace) {
|
|
564
|
+
promptTrace.outcome = "failed";
|
|
435
565
|
traceWeb("prompt_operation_failed", {
|
|
436
566
|
commandId: promptTrace.commandId,
|
|
437
567
|
sessionId,
|
|
@@ -451,7 +581,7 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
451
581
|
() => this.promptOperations.delete(operation),
|
|
452
582
|
() => this.promptOperations.delete(operation),
|
|
453
583
|
);
|
|
454
|
-
await requestAdmission;
|
|
584
|
+
return await requestAdmission;
|
|
455
585
|
}
|
|
456
586
|
|
|
457
587
|
newSession(workspacePath: string, options?: WebSessionCreationOptions) {
|
|
@@ -725,13 +855,17 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
725
855
|
|
|
726
856
|
private projectEvent(session: AgentSession, event: AgentSessionEvent) {
|
|
727
857
|
if (session !== this.runtime.session) return;
|
|
858
|
+
if (event.type === "agent_start" && this.activePromptTrace) {
|
|
859
|
+
this.startPromptTrace(this.activePromptTrace);
|
|
860
|
+
}
|
|
728
861
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
729
862
|
if (!this.activePromptTrace) {
|
|
730
863
|
this.activePromptTrace = this.pendingPromptTraces.shift();
|
|
731
|
-
} else if (this.activePromptTrace.started && this.pendingPromptTraces.length > 0) {
|
|
732
|
-
this.activePromptTrace = this.pendingPromptTraces.shift();
|
|
733
864
|
}
|
|
734
|
-
if (this.activePromptTrace)
|
|
865
|
+
if (this.activePromptTrace) {
|
|
866
|
+
this.startPromptTrace(this.activePromptTrace);
|
|
867
|
+
this.activePromptTrace.userMessageObserved = true;
|
|
868
|
+
}
|
|
735
869
|
}
|
|
736
870
|
const promptTrace = this.activePromptTrace;
|
|
737
871
|
if (promptTrace) {
|
|
@@ -770,15 +904,25 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
770
904
|
}
|
|
771
905
|
switch (event.type) {
|
|
772
906
|
case "agent_start":
|
|
907
|
+
this.emit(event.type, {
|
|
908
|
+
sessionId: session.sessionManager.getSessionId(),
|
|
909
|
+
...(this.getActiveTurn()
|
|
910
|
+
? { activeTurn: this.getActiveTurn() }
|
|
911
|
+
: {}),
|
|
912
|
+
});
|
|
913
|
+
break;
|
|
773
914
|
case "agent_settled":
|
|
774
|
-
this
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
this.
|
|
779
|
-
) {
|
|
780
|
-
this.activePromptTrace = undefined;
|
|
915
|
+
// Pi emits this only after the whole agent run (including tool loops
|
|
916
|
+
// and admitted follow-ups) has reached a terminal state. A
|
|
917
|
+
// message_end is only one model response and must not settle a turn.
|
|
918
|
+
if (this.activePromptTrace?.started) {
|
|
919
|
+
this.settlePromptTrace(this.activePromptTrace);
|
|
781
920
|
}
|
|
921
|
+
this.activePromptTrace = undefined;
|
|
922
|
+
this.pendingPromptTraces.length = 0;
|
|
923
|
+
this.emit(event.type, {
|
|
924
|
+
sessionId: session.sessionManager.getSessionId(),
|
|
925
|
+
});
|
|
782
926
|
break;
|
|
783
927
|
case "auto_retry_start":
|
|
784
928
|
this.emit(event.type, {
|
|
@@ -796,6 +940,30 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
796
940
|
break;
|
|
797
941
|
case "message_update":
|
|
798
942
|
case "message_end":
|
|
943
|
+
if (
|
|
944
|
+
event.type === "message_end" &&
|
|
945
|
+
event.message.role === "assistant" &&
|
|
946
|
+
this.activePromptTrace
|
|
947
|
+
) {
|
|
948
|
+
// Preserve the terminal model result for classification, but defer
|
|
949
|
+
// publication until Pi confirms the entire run is settled.
|
|
950
|
+
const outcome =
|
|
951
|
+
event.message.stopReason === "aborted"
|
|
952
|
+
? "cancelled"
|
|
953
|
+
: event.message.stopReason === "error"
|
|
954
|
+
? "failed"
|
|
955
|
+
: event.message.stopReason === "stop" ||
|
|
956
|
+
event.message.stopReason === "length"
|
|
957
|
+
? "completed"
|
|
958
|
+
: undefined;
|
|
959
|
+
// A later queued continuation must not erase proof that the
|
|
960
|
+
// provider result targeted by Stop was aborted. The control remains
|
|
961
|
+
// owned until agent_settled; this outcome does not claim that every
|
|
962
|
+
// queued follow-up in the same Pi execution was cancelled.
|
|
963
|
+
if (outcome && this.activePromptTrace.outcome !== "cancelled") {
|
|
964
|
+
this.activePromptTrace.outcome = outcome;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
799
967
|
this.emit(event.type, {
|
|
800
968
|
message: projectMessage(event.message),
|
|
801
969
|
...(this.liveMessageKey ? { messageKey: this.liveMessageKey } : {}),
|
|
@@ -819,10 +987,56 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
819
987
|
for (const listener of this.listeners) listener({ type, detail });
|
|
820
988
|
}
|
|
821
989
|
|
|
990
|
+
private activeTurnFromTrace(trace?: PromptTrace): WebActiveTurn | undefined {
|
|
991
|
+
if (!trace?.started || trace.epoch === undefined) return undefined;
|
|
992
|
+
return {
|
|
993
|
+
sessionId: trace.sessionId,
|
|
994
|
+
commandId: trace.commandId,
|
|
995
|
+
epoch: trace.epoch,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
private startPromptTrace(trace: PromptTrace) {
|
|
1000
|
+
if (trace.started) return;
|
|
1001
|
+
trace.started = true;
|
|
1002
|
+
trace.epoch = ++this.nextTurnEpoch;
|
|
1003
|
+
const activeTurn = this.activeTurnFromTrace(trace);
|
|
1004
|
+
if (activeTurn) this.emit("turn_started", { ...activeTurn });
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
private settlePromptTrace(trace: PromptTrace) {
|
|
1008
|
+
const activeTurn = this.activeTurnFromTrace(trace);
|
|
1009
|
+
if (!activeTurn) return;
|
|
1010
|
+
const settlement: TurnSettlement = {
|
|
1011
|
+
...activeTurn,
|
|
1012
|
+
outcome:
|
|
1013
|
+
trace.outcome ?? "uncertain",
|
|
1014
|
+
};
|
|
1015
|
+
const key = this.turnKey(activeTurn);
|
|
1016
|
+
if (this.terminalTurnKeys.has(key)) return;
|
|
1017
|
+
this.terminalTurnKeys.add(key);
|
|
1018
|
+
this.turnAbortOperations.delete(key);
|
|
1019
|
+
while (this.terminalTurnKeys.size > 64) {
|
|
1020
|
+
const oldest = this.terminalTurnKeys.values().next().value;
|
|
1021
|
+
if (typeof oldest === "string") this.terminalTurnKeys.delete(oldest);
|
|
1022
|
+
}
|
|
1023
|
+
this.emit("turn_settled", { ...settlement });
|
|
1024
|
+
for (const resolveSettlement of this.turnSettlementWaiters.get(key) ?? []) {
|
|
1025
|
+
resolveSettlement(settlement);
|
|
1026
|
+
}
|
|
1027
|
+
this.turnSettlementWaiters.delete(key);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private turnKey(turn: WebActiveTurn) {
|
|
1031
|
+
return `${turn.sessionId}\u0000${turn.commandId}\u0000${turn.epoch}`;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
822
1034
|
private removePromptTrace(trace: PromptTrace) {
|
|
823
1035
|
const pendingIndex = this.pendingPromptTraces.indexOf(trace);
|
|
824
1036
|
if (pendingIndex !== -1) this.pendingPromptTraces.splice(pendingIndex, 1);
|
|
825
1037
|
if (this.activePromptTrace !== trace) return;
|
|
1038
|
+
// A started trace can only be terminally projected by agent_settled.
|
|
1039
|
+
if (trace.started) return;
|
|
826
1040
|
this.activePromptTrace = this.pendingPromptTraces.shift();
|
|
827
1041
|
}
|
|
828
1042
|
|
|
@@ -987,5 +1201,6 @@ export class PiWebRuntime implements WebRuntimeController {
|
|
|
987
1201
|
private resetPromptTraces() {
|
|
988
1202
|
this.activePromptTrace = undefined;
|
|
989
1203
|
this.pendingPromptTraces.length = 0;
|
|
1204
|
+
this.turnAbortOperations.clear();
|
|
990
1205
|
}
|
|
991
1206
|
}
|
package/web/runtime/types.ts
CHANGED
|
@@ -33,6 +33,30 @@ export interface WebPromptOptions {
|
|
|
33
33
|
expectedSessionId?: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface WebPromptAdmissionReceipt {
|
|
37
|
+
pendingFollowUps: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WebActiveTurn {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
commandId: string;
|
|
43
|
+
epoch: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WebTurnCancellationOptions extends WebActiveTurn {}
|
|
47
|
+
|
|
48
|
+
export type WebTurnCancellationState =
|
|
49
|
+
| "accepted"
|
|
50
|
+
| "already-settled"
|
|
51
|
+
| "stale-session"
|
|
52
|
+
| "stale-turn"
|
|
53
|
+
| "failed";
|
|
54
|
+
|
|
55
|
+
export interface WebTurnCancellationResult extends WebActiveTurn {
|
|
56
|
+
state: WebTurnCancellationState;
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
export interface WebModelSelectionOptions {
|
|
37
61
|
expectedSessionId?: string;
|
|
38
62
|
}
|
|
@@ -54,7 +78,14 @@ export interface WebRuntimeController {
|
|
|
54
78
|
readonly sessionDirectory: string;
|
|
55
79
|
readonly sessionManager: SessionManager;
|
|
56
80
|
isIdle(): boolean;
|
|
57
|
-
|
|
81
|
+
getActiveTurn(): WebActiveTurn | undefined;
|
|
82
|
+
sendPrompt(
|
|
83
|
+
content: string,
|
|
84
|
+
options?: WebPromptOptions,
|
|
85
|
+
): Promise<WebPromptAdmissionReceipt>;
|
|
86
|
+
cancelTurn(
|
|
87
|
+
options: WebTurnCancellationOptions,
|
|
88
|
+
): Promise<WebTurnCancellationResult>;
|
|
58
89
|
newSession(
|
|
59
90
|
workspacePath: string,
|
|
60
91
|
options?: WebSessionCreationOptions,
|