@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.
Files changed (74) hide show
  1. package/README.md +18 -10
  2. package/SETUP.md +8 -2
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/bin/openpi.js +25 -15
  5. package/extensions/ai-providers/LICENSE.upstream +23 -0
  6. package/extensions/ai-providers/README.md +59 -0
  7. package/extensions/ai-providers/antigravity/credentials.ts +52 -0
  8. package/extensions/ai-providers/antigravity/discovery.ts +130 -0
  9. package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
  10. package/extensions/ai-providers/antigravity/models.ts +84 -0
  11. package/extensions/ai-providers/antigravity/oauth.ts +700 -0
  12. package/extensions/ai-providers/antigravity/provider.ts +1116 -0
  13. package/extensions/ai-providers/antigravity/routing.ts +340 -0
  14. package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
  15. package/extensions/ai-providers/cursor/constants.ts +5 -0
  16. package/extensions/ai-providers/cursor/credentials.ts +14 -0
  17. package/extensions/ai-providers/cursor/discovery.ts +291 -0
  18. package/extensions/ai-providers/cursor/input-images.ts +106 -0
  19. package/extensions/ai-providers/cursor/models.ts +45 -0
  20. package/extensions/ai-providers/cursor/oauth.ts +263 -0
  21. package/extensions/ai-providers/cursor/proto.ts +1064 -0
  22. package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
  23. package/extensions/ai-providers/cursor/provider.ts +1175 -0
  24. package/extensions/ai-providers/cursor/proxy.ts +213 -0
  25. package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
  26. package/extensions/ai-providers/index.ts +86 -0
  27. package/extensions/ai-providers/oauth-adapter.ts +81 -0
  28. package/extensions/ai-providers/usage.ts +10 -0
  29. package/extensions/background-terminals/index.ts +8 -1
  30. package/extensions/background-terminals/src/manager.ts +3 -5
  31. package/extensions/background-terminals/src/result-delivery.ts +43 -23
  32. package/extensions/cron/index.ts +68 -27
  33. package/extensions/cron/schedule.ts +5 -1
  34. package/extensions/model-info/cache-diagnostics.ts +220 -0
  35. package/extensions/model-info/index.ts +45 -1
  36. package/extensions/plan-mode/index.ts +75 -4
  37. package/extensions/setup/index.ts +15 -3
  38. package/extensions/shared/child-session.ts +25 -5
  39. package/extensions/shared/completion-inbox.ts +193 -0
  40. package/extensions/shared/setup-config.ts +10 -1
  41. package/extensions/shared/structured-output.ts +154 -0
  42. package/extensions/subagents/index.ts +44 -4
  43. package/extensions/subagents/src/backends/pi.ts +76 -5
  44. package/extensions/subagents/src/domain.ts +16 -1
  45. package/extensions/subagents/src/manager.ts +5 -0
  46. package/extensions/subagents/src/prompt.ts +17 -3
  47. package/extensions/subagents/src/result-artifact.ts +32 -0
  48. package/extensions/subagents/src/result-delivery.ts +33 -14
  49. package/extensions/ui-customization/footer.ts +16 -5
  50. package/extensions/user-input-fold/index.ts +42 -6
  51. package/extensions/web/index.ts +25 -2
  52. package/extensions/workflows/acceptance.ts +43 -19
  53. package/extensions/workflows/completion-projection.ts +3 -1
  54. package/extensions/workflows/dashboard.ts +8 -0
  55. package/extensions/workflows/index.ts +13 -0
  56. package/extensions/workflows/model.ts +5 -1
  57. package/extensions/workflows/prompt.ts +4 -10
  58. package/extensions/workflows/result-delivery.ts +96 -22
  59. package/extensions/workflows/retention.ts +6 -0
  60. package/extensions/workflows/runner.ts +6 -71
  61. package/package.json +7 -7
  62. package/skills/subagents/REFERENCE.md +3 -2
  63. package/skills/subagents/SKILL.md +1 -0
  64. package/skills/workflows/REFERENCE.md +3 -3
  65. package/skills/workflows/SKILL.md +1 -1
  66. package/web/adapter/pi-adapter.ts +3 -0
  67. package/web/host/pi-coding-agent-entry.ts +162 -0
  68. package/web/host/web-host.ts +330 -50
  69. package/web/protocol/types.ts +5 -0
  70. package/web/runtime/pi-runtime.ts +240 -25
  71. package/web/runtime/types.ts +32 -1
  72. package/web/ui/app.js +343 -41
  73. package/web/ui/index.html +3 -0
  74. 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<void>((resolveRequestAdmission, reject) => {
304
- resolveRequest = resolveRequestAdmission;
305
- rejectRequest = reject;
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
- const pendingMessagesBefore = session.pendingMessageCount;
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.type === "queue_update" &&
339
- event.steering.length + event.followUp.length > pendingMessagesBefore
340
- ) {
341
- queuedForAgent = true;
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) this.activePromptTrace.started = true;
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.emit(event.type);
775
- if (
776
- event.type === "agent_settled" &&
777
- this.activePromptTrace?.started &&
778
- this.pendingPromptTraces.length === 0
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
  }
@@ -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
- sendPrompt(content: string, options?: WebPromptOptions): Promise<void>;
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,