@timurproko/a1 0.2.7-dev.808 → 0.2.7-dev.810

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 (141) hide show
  1. package/README.md +4 -2
  2. package/dist/app/session-shell/dialog-coordinator.d.ts +42 -0
  3. package/dist/app/session-shell/dialog-coordinator.js +133 -0
  4. package/dist/app/session-shell/dock-chrome.d.ts +34 -0
  5. package/dist/app/session-shell/dock-chrome.js +109 -0
  6. package/dist/app/session-shell/exit-notice.d.ts +1 -1
  7. package/dist/app/session-shell/exit-notice.js +1 -1
  8. package/dist/app/session-shell/extension-surface.d.ts +30 -0
  9. package/dist/app/session-shell/extension-surface.js +105 -0
  10. package/dist/app/session-shell/input-surface-switch.d.ts +36 -0
  11. package/dist/app/session-shell/input-surface-switch.js +85 -0
  12. package/dist/app/session-shell/login-interaction.d.ts +27 -0
  13. package/dist/app/session-shell/login-interaction.js +91 -0
  14. package/dist/app/session-shell/prompt-submission.d.ts +48 -0
  15. package/dist/app/session-shell/prompt-submission.js +276 -0
  16. package/dist/app/session-shell/resident-tab-coordinator.d.ts +41 -0
  17. package/dist/app/session-shell/resident-tab-coordinator.js +120 -0
  18. package/dist/app/session-shell/response-copy-transport.d.ts +13 -0
  19. package/dist/app/session-shell/response-copy-transport.js +16 -7
  20. package/dist/app/session-shell/session-concern.d.ts +22 -0
  21. package/dist/app/session-shell/session-concern.js +29 -0
  22. package/dist/app/session-shell/session-lifecycle.d.ts +29 -0
  23. package/dist/app/session-shell/session-lifecycle.js +49 -0
  24. package/dist/app/session-shell/session-shell-root.d.ts +1 -1
  25. package/dist/app/session-shell/session-shell-root.js +182 -931
  26. package/dist/app/session-shell/session-shell.d.ts +2 -8
  27. package/dist/app/session-shell/session-shell.js +202 -901
  28. package/dist/app/session-shell/settings-bindings.d.ts +17 -0
  29. package/dist/app/session-shell/settings-bindings.js +91 -0
  30. package/dist/app/session-shell/shell-editor-factory.d.ts +27 -0
  31. package/dist/app/session-shell/shell-editor-factory.js +102 -0
  32. package/dist/app/session-shell/shell-host.d.ts +6 -0
  33. package/dist/app/session-shell/shell-host.js +8 -0
  34. package/dist/app/session-shell/shell-layout.d.ts +64 -0
  35. package/dist/app/session-shell/shell-layout.js +18 -0
  36. package/dist/app/session-shell/suggestion-surface.d.ts +25 -0
  37. package/dist/app/session-shell/suggestion-surface.js +63 -0
  38. package/dist/app/session-shell/transcript-document.d.ts +78 -0
  39. package/dist/app/session-shell/transcript-document.js +365 -0
  40. package/dist/app/session-shell/viewport-layout.d.ts +31 -0
  41. package/dist/app/session-shell/viewport-layout.js +160 -0
  42. package/dist/app/session-shell/workflow-output.d.ts +33 -0
  43. package/dist/app/session-shell/workflow-output.js +174 -0
  44. package/dist/app/session-shell/workflow-router.d.ts +25 -0
  45. package/dist/app/session-shell/workflow-router.js +39 -0
  46. package/dist/composition/owned-ui.d.ts +4 -25
  47. package/dist/composition/owned-ui.js +26 -270
  48. package/dist/composition/resident-tab-bridge.d.ts +16 -0
  49. package/dist/composition/resident-tab-bridge.js +159 -0
  50. package/dist/composition/session-backend.d.ts +19 -0
  51. package/dist/composition/session-backend.js +1 -0
  52. package/dist/composition/session-runtime-registry.d.ts +20 -0
  53. package/dist/composition/session-runtime-registry.js +85 -0
  54. package/dist/composition/startup-route.d.ts +32 -0
  55. package/dist/composition/startup-route.js +72 -0
  56. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  57. package/dist/foundation/release/progress-palette.generated.js +2 -2
  58. package/dist/foundation/startup/startup-descriptor.js +2 -2
  59. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  60. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  61. package/dist/integrations/pi/components/shell-extension-ui.js +79 -60
  62. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  64. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  65. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  66. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  67. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  68. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  69. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  70. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  71. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  72. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  73. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  74. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  75. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  76. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  77. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  78. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  79. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  81. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  82. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  83. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  84. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  85. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  86. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  87. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +2 -4
  88. package/dist/integrations/pi/components/upstream/components/tool-execution.js +77 -98
  89. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  90. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  91. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  92. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  93. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  94. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  95. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  96. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  97. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  98. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  99. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  100. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  101. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  102. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  103. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  104. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  105. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  106. package/dist/integrations/pi/engine/adapter.d.ts +4 -77
  107. package/dist/integrations/pi/engine/adapter.js +85 -393
  108. package/dist/integrations/pi/engine/command-dispatch.d.ts +13 -0
  109. package/dist/integrations/pi/engine/command-dispatch.js +57 -0
  110. package/dist/integrations/pi/engine/host.js +2 -2
  111. package/dist/integrations/pi/engine/ports.d.ts +56 -0
  112. package/dist/integrations/pi/engine/ports.js +89 -0
  113. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  114. package/dist/integrations/pi/engine/view-state.d.ts +42 -0
  115. package/dist/integrations/pi/engine/view-state.js +126 -0
  116. package/dist/integrations/pi/startup-public.js +164 -150
  117. package/dist/integrations/pi/startup-public.manifest.json +351 -310
  118. package/dist/integrations/pi/tui-runtime/adapter.js +4 -0
  119. package/dist/integrations/pi/tui-runtime/contracts.d.ts +5 -0
  120. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  121. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  122. package/dist/native/darwin-arm64/manifest.json +1 -1
  123. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  124. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  125. package/dist/native/darwin-x64/manifest.json +1 -1
  126. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  127. package/dist/native/darwin-x64/terminal-host.node +0 -0
  128. package/dist/native/linux-x64/manifest.json +1 -1
  129. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  130. package/dist/native/linux-x64/terminal-host.node +0 -0
  131. package/dist/native/win32-x64/manifest.json +2 -2
  132. package/dist/native/win32-x64/process-guardian.node +0 -0
  133. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  134. package/dist/native/win32-x64/terminal-host.node +0 -0
  135. package/dist/runtime-payload-inventory.json +5181 -5369
  136. package/dist/ui/components/transcript-viewport.js +10 -19
  137. package/docs/architecture/boundaries.md +14 -0
  138. package/docs/architecture/resident-tabs.md +8 -8
  139. package/docs/architecture/response-copy-delivery.md +1 -1
  140. package/docs/architecture/toolchain.md +2 -2
  141. package/package.json +5 -5
@@ -2,7 +2,7 @@ import { PiTranscriptProjection } from "./transcript-projection.js";
2
2
  import { isRecord, readModel, readStringArray, readThinkingLevel, stringProperty } from "./message-values.js";
3
3
  import { getAgentDir } from "../startup-public.js";
4
4
  import { assertOwnedUiSnapshot, } from "../../../contracts/owned-ui/index.js";
5
- import { PiCommandDispatch } from "./command-dispatch.js";
5
+ import { PiCommandDispatch, performEngineCommand } from "./command-dispatch.js";
6
6
  import { PiEventDelivery } from "./event-delivery.js";
7
7
  import { PiExtensionUiBinding } from "./extension-ui-binding.js";
8
8
  import { PiPromptSuggestions } from "./prompt-suggestions.js";
@@ -15,16 +15,12 @@ import { readUsageView } from "./usage-view.js";
15
15
  import { PiWorkflowContexts } from "./workflow-contexts.js";
16
16
  import { PiWorkflowRunner } from "./workflow-runner.js";
17
17
  import { defaultWorkflowHost } from "./workflow-support.js";
18
+ import { EngineViewState } from "./view-state.js";
19
+ import { createEnginePorts } from "./ports.js";
18
20
  /** Explicit flush failure when required delivery was interrupted rather than completed. */
19
21
  export class EngineDeliveryError extends Error {
20
22
  constructor() { super("Engine delivery did not complete"); this.name = "EngineDeliveryError"; }
21
23
  }
22
- const DEFAULT_SURFACE = {
23
- columns: 100,
24
- rows: 32,
25
- focusedRegion: "editor",
26
- hardwareCursor: false,
27
- };
28
24
  /** Owns the pinned Pi session lifecycle and serves it to the owned shell as the neutral session backend. */
29
25
  export class PiEngineAdapter {
30
26
  #agentDir;
@@ -38,31 +34,11 @@ export class PiEngineAdapter {
38
34
  #resources;
39
35
  #settings;
40
36
  #suggestions;
41
- #viewRevision = 0;
42
- #lifecycle = "starting";
43
- #editor = {
44
- text: "",
45
- queuedSubmissions: [],
46
- selection: null,
47
- cursorOffset: 0,
48
- historyRevision: 0,
49
- submitEnabled: false,
50
- };
51
- #status = {
52
- title: "Pi",
53
- workingMessage: null,
54
- diagnostics: [],
55
- badges: [],
56
- };
57
- #terminal = DEFAULT_SURFACE;
58
- #activeModel = null;
59
- #thinkingLevel = "medium";
37
+ #state = new EngineViewState();
60
38
  #projection = new PiTranscriptProjection({
61
39
  retryAttempt: () => this.#engine.session?.retryAttempt ?? 0,
62
40
  blockChanged: block => this.#emitEvent({ type: "transcript-block", block }),
63
41
  });
64
- #usageCache;
65
- #diagnostics = [];
66
42
  // Invariant: every port closes over adapter state read at call time, so delivery is built before a session binds.
67
43
  #delivery = new PiEventDelivery({
68
44
  sessionId: () => this.#sessionId,
@@ -80,36 +56,30 @@ export class PiEngineAdapter {
80
56
  #events = new PiSessionEvents(this.#projection, {
81
57
  sessionGeneration: () => this.#engine.generation,
82
58
  sessionMessages: () => this.#engine.session?.messages ?? [],
83
- activeModel: () => this.#activeModel,
59
+ activeModel: () => this.#state.activeModel,
84
60
  replaceTranscript: blocks => { this.#setTranscript(blocks); },
85
61
  emit: value => { this.#emitEvent(value); },
86
62
  emitView: () => { this.#emitView(); },
87
63
  enterWork: message => {
88
- const wasBusy = this.#lifecycle === "busy";
89
- this.#lifecycle = "busy";
90
- this.#status = { ...this.#status, workingMessage: message, workingProgress: null };
91
- if (!wasBusy)
64
+ if (!this.#state.enterWork(message))
92
65
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "busy", reason: null });
93
- this.#emitEvent({ type: "status", status: this.#status });
66
+ this.#emitEvent({ type: "status", status: this.#state.status });
94
67
  },
95
68
  leaveWork: () => {
96
- this.#lifecycle = "ready";
97
- this.#status = { ...this.#status, workingMessage: null, workingProgress: null };
69
+ this.#state.leaveWork();
98
70
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
99
- this.#emitEvent({ type: "status", status: this.#status });
71
+ this.#emitEvent({ type: "status", status: this.#state.status });
100
72
  },
101
73
  workProgress: percent => {
102
- if (this.#status.workingProgress === percent)
103
- return;
104
- this.#status = { ...this.#status, workingProgress: percent };
105
- this.#emitEvent({ type: "status", status: this.#status });
74
+ if (this.#state.setWorkProgress(percent))
75
+ this.#emitEvent({ type: "status", status: this.#state.status });
106
76
  },
107
77
  queueChanged: queuedSubmissions => {
108
- this.#editor = { ...this.#editor, queuedSubmissions: [...queuedSubmissions], historyRevision: this.#editor.historyRevision + 1 };
109
- this.#emitEvent({ type: "editor-state", editor: this.#editor });
78
+ this.#state.setQueuedSubmissions(queuedSubmissions);
79
+ this.#emitEvent({ type: "editor-state", editor: this.#state.editor });
110
80
  },
111
- thinkingLevelChanged: level => { this.#thinkingLevel = level; },
112
- usageInvalidated: () => { this.#usageCache = undefined; },
81
+ thinkingLevelChanged: level => { this.#state.thinkingLevel = level; },
82
+ usageInvalidated: () => { this.#state.invalidateUsage(); },
113
83
  compactionStarted: () => { this.#engine.compactionStarted(); },
114
84
  compactionEnded: () => { this.#engine.compactionEnded(); },
115
85
  deliverQueuedAfterCompaction: () => { this.#engine.deliverQueuedAfterCompaction(); },
@@ -146,10 +116,7 @@ export class PiEngineAdapter {
146
116
  }, {
147
117
  disposed: () => this.#disposed,
148
118
  started: runtime => {
149
- this.#terminal = {
150
- ...this.#terminal,
151
- hardwareCursor: runtime.services.settingsManager?.getShowHardwareCursor?.() ?? this.#terminal.hardwareCursor,
152
- };
119
+ this.#state.setHardwareCursor(runtime.services.settingsManager?.getShowHardwareCursor?.() ?? this.#state.terminal.hardwareCursor);
153
120
  const httpIdleTimeoutMs = runtime.services.settingsManager?.getHttpIdleTimeoutMs?.();
154
121
  if (typeof httpIdleTimeoutMs === "number")
155
122
  this.#engineHost.httpPolicyLoaded(httpIdleTimeoutMs, this.#sessionId);
@@ -168,7 +135,7 @@ export class PiEngineAdapter {
168
135
  session: () => this.#engine.session,
169
136
  runtime: () => this.#engine.runtime,
170
137
  disposed: () => this.#disposed,
171
- activeModel: () => this.#activeModel,
138
+ activeModel: () => this.#state.activeModel,
172
139
  emitView: () => { this.#emitView(); },
173
140
  });
174
141
  this.#workflows = new PiWorkflowRunner({ agentDir: this.#agentDir, host: this.#workflowHost, contexts: this.#contexts }, {
@@ -181,10 +148,10 @@ export class PiEngineAdapter {
181
148
  pendingCommandCount: () => this.#commands.pendingCount,
182
149
  beginRunning: () => { this.#runningCommands++; },
183
150
  endRunning: () => { this.#runningCommands--; },
184
- activeModel: () => this.#activeModel,
185
- setActiveModel: model => { this.#activeModel = model; },
186
- thinkingLevel: () => this.#thinkingLevel,
187
- setThinkingLevel: level => { this.#thinkingLevel = level; },
151
+ activeModel: () => this.#state.activeModel,
152
+ setActiveModel: model => { this.#state.activeModel = model; },
153
+ thinkingLevel: () => this.#state.thinkingLevel,
154
+ setThinkingLevel: level => { this.#state.thinkingLevel = level; },
188
155
  emitView: () => { this.#emitView(); },
189
156
  reconcileActiveModelAvailability: () => { this.#reconcileActiveModelAvailability(); },
190
157
  bindExtensionUiToSession: () => this.#extensions.rebind(),
@@ -200,7 +167,7 @@ export class PiEngineAdapter {
200
167
  sessionGeneration: () => this.#engine.generation,
201
168
  beginRunning: () => { this.#runningCommands++; },
202
169
  endRunning: () => { this.#runningCommands--; },
203
- perform: command => this.#perform(command),
170
+ perform: command => performEngineCommand(command, this.#commandContext()),
204
171
  emit: value => { this.#emitEvent(value); },
205
172
  emitView: () => { this.#emitView(); },
206
173
  diagnostic: (severity, code, message, recoverable) => { this.#addDiagnostic(severity, code, message, recoverable); },
@@ -212,14 +179,14 @@ export class PiEngineAdapter {
212
179
  this.#settings = new PiEngineSettings({ availableThemes: options.availableThemes ?? null, productMode: options.settingsProductMode ?? "bare" }, {
213
180
  runtime: () => this.#engine.runtime,
214
181
  requireSession: () => this.#requireWorkflowSession(),
215
- thinkingLevelChanged: level => { this.#thinkingLevel = level; this.#emitView(); },
182
+ thinkingLevelChanged: level => { this.#state.thinkingLevel = level; this.#emitView(); },
216
183
  httpPolicyChanged: timeoutMs => { this.#engineHost.httpPolicyChanged(timeoutMs, this.#sessionId); },
217
184
  emitView: () => { this.#emitView(); },
218
185
  });
219
186
  this.#suggestions = new PiPromptSuggestions({
220
187
  session: () => this.#engine.session,
221
188
  runtime: () => this.#engine.runtime,
222
- activeModel: () => this.#activeModel,
189
+ activeModel: () => this.#state.activeModel,
223
190
  identity: () => ({ sessionId: this.#sessionId, sessionGeneration: this.#engine.generation, runSequence: this.#events.runSequence, responseSequence: this.#events.responseSequence }),
224
191
  unavailable: () => this.#disposed || this.#delivery.overloaded || this.#admissionStopped,
225
192
  });
@@ -236,38 +203,12 @@ export class PiEngineAdapter {
236
203
  setWorkflowInteractionHost(interaction) {
237
204
  this.#workflowInteraction = interaction;
238
205
  }
239
- get sessionId() {
240
- return this.#sessionId;
241
- }
242
- get sessionGeneration() {
243
- return this.#engine.generation;
244
- }
245
- /** Actual session replacements, excluding delivery-only invalidation of callbacks. */
246
- get sessionBindingGeneration() { return this.#engine.bindingGeneration; }
247
- get cwd() {
248
- return this.#engine.cwd;
249
- }
250
- get agentDir() {
251
- return this.#agentDir;
252
- }
253
- resolveTranscriptImage(assetId) {
206
+ #resolveTranscriptImage(assetId) {
254
207
  return this.#projection.assets.resolve(assetId);
255
208
  }
256
- currentSessionFile() {
257
- return this.#engine.currentSessionFile();
258
- }
259
- currentSessionResumeMetadata() {
260
- return this.#engine.currentSessionResumeMetadata();
261
- }
262
- get disposed() {
263
- return this.#disposed;
264
- }
265
- /**
266
- * Report a newer A1 release found by the startup check. The shell renders the diagnostic as
267
- * pinned Pi's `Update Available` notice; it arrives whenever the check resolves, so a late
268
- * result is appended to the transcript like Pi's own fire-and-forget version check.
269
- */
270
- announceReleaseUpdate(release) {
209
+ // Rationale: the startup check's newer release becomes pinned Pi's `Update Available` notice; it arrives
210
+ // whenever the check resolves, so a late result is appended like Pi's own fire-and-forget version check.
211
+ #announceReleaseUpdate(release) {
271
212
  if (this.#disposed)
272
213
  return;
273
214
  const changelog = release.changelogUrl === null ? "" : `\nChangelog: ${release.changelogUrl}`;
@@ -287,12 +228,11 @@ export class PiEngineAdapter {
287
228
  if (this.#engine.started)
288
229
  return this.view();
289
230
  await this.#engine.start().catch(error => {
290
- this.#lifecycle = "failed";
231
+ this.#state.lifecycle = "failed";
291
232
  this.#addDiagnostic("error", "engine-startup", error instanceof Error ? error.message : String(error), false);
292
233
  throw error;
293
234
  });
294
- this.#lifecycle = "ready";
295
- this.#editor = { ...this.#editor, submitEnabled: true };
235
+ this.#state.markReady();
296
236
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
297
237
  this.#emitView();
298
238
  return this.view();
@@ -315,94 +255,14 @@ export class PiEngineAdapter {
315
255
  async flushEvents() {
316
256
  const failed = this.#delivery.failed;
317
257
  await this.#delivery.settle();
318
- if (failed || this.#delivery.failed || this.#disposed && this.#lifecycle !== "stopped") {
258
+ if (failed || this.#delivery.failed || this.#disposed && this.#state.lifecycle !== "stopped") {
319
259
  this.#delivery.clearFailure();
320
260
  throw new EngineDeliveryError();
321
261
  }
322
262
  }
323
- nonVisualResources() {
324
- return this.#resources.nonVisualResources();
325
- }
326
- extensionResources() {
327
- return this.#resources.extensionResources();
328
- }
329
- workflowAutocompleteCommands() {
330
- return this.#resources.workflowAutocompleteCommands();
331
- }
332
- visualExtensionSupport() {
333
- return this.#extensions.support();
334
- }
335
- bindExtensionUi(ui, shutdown) {
336
- return this.#extensions.bind(ui, shutdown);
337
- }
338
- unbindExtensionUi() {
339
- return this.#extensions.unbind();
340
- }
341
- async cycleModelWorkflow(direction) {
342
- return this.#workflows.cycleModelWorkflow(direction);
343
- }
344
- pinnedProjectTrustContext() {
345
- return this.#contexts.pinnedProjectTrustContext();
346
- }
347
- persistProjectTrust(updates) {
348
- this.#contexts.persistProjectTrust(updates);
349
- }
350
- pinnedScopedModelsContext() {
351
- return this.#contexts.pinnedScopedModelsContext();
352
- }
353
- updateScopedModels(enabledModelIds) {
354
- this.#contexts.updateScopedModels(enabledModelIds);
355
- }
356
- persistScopedModels(enabledModelIds) {
357
- this.#contexts.persistScopedModels(enabledModelIds);
358
- }
359
- refreshScopedModels(signal) {
360
- return this.#contexts.refreshScopedModels(signal);
361
- }
362
- modelsContext() {
363
- return this.#contexts.modelsContext();
364
- }
365
- setSessionModelScope(scopeIds) {
366
- this.#contexts.setSessionModelScope(scopeIds);
367
- }
368
- persistModelScope(scopeIds) {
369
- this.#contexts.persistModelScope(scopeIds);
370
- }
371
- refreshModels(signal) {
372
- return this.#contexts.refreshModels(signal);
373
- }
374
- pinnedLoginOptions(authType) {
375
- return this.#contexts.loginOptions(authType);
376
- }
377
- pinnedLoginMethodOptions(providerReference) {
378
- return this.#contexts.pinnedLoginMethodOptions(providerReference);
379
- }
380
- pinnedAmbientAuthentication(selection) {
381
- return this.#contexts.pinnedAmbientAuthentication(selection);
382
- }
383
- pinnedLogoutOptions() {
384
- return this.#contexts.logoutOptions();
385
- }
386
- pinnedForkOptions() {
387
- return this.#contexts.pinnedForkOptions();
388
- }
389
- /** Bind the owned UI's clipboard lifecycle without changing the comparison host. True means acknowledged delivery. */
390
- bindClipboardWriter(writer) {
391
- return this.#workflows.bindClipboardWriter(writer);
392
- }
393
- /** Workflow and tree copying share the active owner's write fence and delivery acknowledgment. */
394
- copyWorkflowText(text) {
395
- return this.#workflows.copyWorkflowText(text);
396
- }
397
263
  abortBashWorkflow() {
398
264
  this.#workflows.abortBashWorkflow();
399
265
  }
400
- executeBashWorkflow(command, excludeFromContext) {
401
- return this.#workflows.executeBashWorkflow(command, excludeFromContext);
402
- }
403
- reloadBlockedResult() {
404
- return this.#workflows.reloadBlockedResult();
405
- }
406
266
  async executeWorkflow(request) {
407
267
  const result = await this.#workflows.executeWorkflow(request);
408
268
  if (request.command === "tree" && result.outcome === "completed" && request.selection !== undefined) {
@@ -417,40 +277,13 @@ export class PiEngineAdapter {
417
277
  configuredTheme() {
418
278
  return this.#settings.configuredTheme();
419
279
  }
420
- /** Persist the global thinking default without changing the live session level. */
421
- setDefaultThinkingLevel(level) {
422
- this.#settings.setDefaultThinkingLevel(level);
423
- }
424
280
  /** Settings port for the live runtime, or null before the runtime is available. */
425
281
  settingsPort() {
426
282
  return this.#settings.settingsPort();
427
283
  }
428
- /** Which settings surface this adapter serves; hidden-in-bare effects are unbindable in bare mode. */
429
- get settingsProductMode() {
430
- return this.#settings.productMode;
431
- }
432
- bindSettingsOwner(owner, handlers) {
433
- return this.#settings.bindSettingsOwner(owner, handlers);
434
- }
435
284
  pinnedSettingsSnapshot() {
436
285
  return this.#settings.pinnedSettingsSnapshot();
437
286
  }
438
- #pinnedMessageRenderer(customType) {
439
- return this.#requireWorkflowSession().extensionRunner?.getMessageRenderer?.(customType);
440
- }
441
- #pinnedShortcutDescriptions(bindings) {
442
- const shortcuts = this.#requireWorkflowSession().extensionRunner?.getShortcuts?.(bindings);
443
- return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
444
- }
445
- #pinnedToolRenderers(toolName) {
446
- const runner = this.#requireWorkflowSession().extensionRunner;
447
- if (runner === undefined)
448
- return undefined;
449
- if (typeof runner.resolveToolRenderers === "function") {
450
- return runner.resolveToolRenderers(toolName, () => runner.getToolDefinition?.(toolName));
451
- }
452
- return runner.getToolDefinition?.(toolName);
453
- }
454
287
  clearQueuedWorkflows() {
455
288
  const session = this.#requireWorkflowSession();
456
289
  const result = session.clearQueue?.();
@@ -465,15 +298,15 @@ export class PiEngineAdapter {
465
298
  return {
466
299
  contractVersion: 1,
467
300
  sessionId: this.#sessionId,
468
- revision: this.#viewRevision,
469
- lifecycle: this.#lifecycle,
301
+ revision: this.#state.revision,
302
+ lifecycle: this.#state.lifecycle,
470
303
  transcript: this.#projection.snapshot(),
471
- editor: { ...this.#editor, queuedSubmissions: [...this.#editor.queuedSubmissions] },
304
+ editor: { ...this.#state.editor, queuedSubmissions: [...this.#state.editor.queuedSubmissions] },
472
305
  status: {
473
- ...this.#status,
474
- diagnostics: [...this.#status.diagnostics],
475
- badges: [...this.#status.badges],
476
- usage: this.#usageCache ??= readUsageView(this.#engine.session, this.#engine.runtime, this.#activeModel),
306
+ ...this.#state.status,
307
+ diagnostics: [...this.#state.status.diagnostics],
308
+ badges: [...this.#state.status.badges],
309
+ usage: this.#state.usage(() => readUsageView(this.#engine.session, this.#engine.runtime, this.#state.activeModel)),
477
310
  footer: {
478
311
  branch: this.#engine.gitBranch,
479
312
  repositoryPath: this.#engine.repositoryCwd,
@@ -483,24 +316,24 @@ export class PiEngineAdapter {
483
316
  extensionStatuses: [],
484
317
  },
485
318
  },
486
- terminal: { ...this.#terminal },
487
- activeModel: this.#activeModel === null ? null : { ...this.#activeModel },
319
+ terminal: { ...this.#state.terminal },
320
+ activeModel: this.#state.activeModel === null ? null : { ...this.#state.activeModel },
488
321
  routedModel: routedModel === null ? null : {
489
322
  model: routedModel,
490
323
  ...(routed?.thinkingLevel === undefined ? {} : { thinkingLevel: readThinkingLevel(routed.thinkingLevel) }),
491
324
  },
492
- thinkingLevel: this.#thinkingLevel,
325
+ thinkingLevel: this.#state.thinkingLevel,
493
326
  activeCommandIds: [...this.#commands.activeCommandIds],
494
327
  dialog: null,
495
328
  overlay: null,
496
329
  customizations: [],
497
- diagnostics: this.#diagnostics.map(diagnostic => ({ ...diagnostic })),
330
+ diagnostics: this.#state.diagnostics.map(diagnostic => ({ ...diagnostic })),
498
331
  };
499
332
  }
500
333
  snapshot() {
501
334
  const snapshot = {
502
335
  contractVersion: 1,
503
- snapshotId: `snapshot-${this.#viewRevision}`,
336
+ snapshotId: `snapshot-${this.#state.revision}`,
504
337
  sessionId: this.#sessionId,
505
338
  sequence: this.#delivery.sequence,
506
339
  view: this.view(),
@@ -520,10 +353,10 @@ export class PiEngineAdapter {
520
353
  this.#workflows.cancelPending("quit");
521
354
  this.#projection.assets.clear();
522
355
  await this.#extensions.unbind();
523
- this.#lifecycle = "stopping";
356
+ this.#state.lifecycle = "stopping";
524
357
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "stopping", reason: null });
525
358
  await this.#engine.dispose();
526
- this.#lifecycle = "stopped";
359
+ this.#state.lifecycle = "stopped";
527
360
  this.#emitEvent({ type: "session-lifecycle", lifecycle: "stopped", reason: null });
528
361
  this.#emitView();
529
362
  try {
@@ -534,28 +367,17 @@ export class PiEngineAdapter {
534
367
  throw error;
535
368
  }
536
369
  }
537
- // Invariant: every sub-port member delegates at call time, so ports built once follow session replacement.
370
+ // Invariant: every port member delegates at call time, so ports built once follow session replacement.
538
371
  #createPorts() {
539
- const engine = this.#engine;
540
- const contexts = this.#contexts;
541
- const workflows = this.#workflows;
542
- const settings = this.#settings;
543
- const resources = this.#resources;
544
- const extensions = this.#extensions;
545
- const isDisposed = () => this.#disposed;
546
- const sessionId = this.#sessionId;
547
- const agentDir = this.#agentDir;
548
- return {
549
- identity: {
550
- sessionId,
551
- agentDir,
552
- get cwd() { return engine.cwd; },
553
- get sessionGeneration() { return engine.generation; },
554
- get sessionBindingGeneration() { return engine.bindingGeneration; },
555
- get disposed() { return isDisposed(); },
556
- currentSessionResumeMetadata: () => engine.currentSessionResumeMetadata(),
557
- currentSessionFile: () => engine.currentSessionFile(),
558
- },
372
+ return createEnginePorts({
373
+ sessionId: this.#sessionId,
374
+ agentDir: this.#agentDir,
375
+ engine: this.#engine,
376
+ contexts: this.#contexts,
377
+ workflows: this.#workflows,
378
+ settings: this.#settings,
379
+ resources: this.#resources,
380
+ extensions: this.#extensions,
559
381
  session: {
560
382
  start: () => this.start(),
561
383
  view: () => this.view(),
@@ -565,67 +387,22 @@ export class PiEngineAdapter {
565
387
  flushEvents: () => this.flushEvents(),
566
388
  dispose: () => this.dispose(),
567
389
  },
568
- workflows: {
569
- executeWorkflow: request => this.executeWorkflow(request),
570
- executeBashWorkflow: (command, excludeFromContext) => workflows.executeBashWorkflow(command, excludeFromContext),
571
- cycleModelWorkflow: direction => workflows.cycleModelWorkflow(direction),
572
- workflowAutocompleteCommands: () => resources.workflowAutocompleteCommands(),
573
- clearQueuedWorkflows: () => this.clearQueuedWorkflows(),
574
- reloadBlockedResult: () => workflows.reloadBlockedResult(),
575
- copyWorkflowText: text => workflows.copyWorkflowText(text),
576
- setWorkflowInteractionHost: interaction => { this.setWorkflowInteractionHost(interaction); },
577
- },
578
- settings: {
579
- get productMode() { return settings.productMode; },
580
- snapshot: () => {
581
- // Invariant: the selector-only model values travel on the presentation source, not in the neutral snapshot.
582
- const { currentModel: _currentModel, availableDefaultModels: _availableDefaultModels, ...snapshot } = settings.pinnedSettingsSnapshot();
583
- return snapshot;
584
- },
585
- bindOwner: (owner, handlers) => settings.bindSettingsOwner(owner, handlers),
586
- setDefaultThinkingLevel: level => { settings.setDefaultThinkingLevel(level); },
587
- configuredTheme: () => settings.configuredTheme(),
588
- },
589
- catalog: {
590
- modelsContext: () => contexts.modelsContext(),
591
- setSessionModelScope: scopeIds => { contexts.setSessionModelScope(scopeIds); },
592
- persistModelScope: scopeIds => { contexts.persistModelScope(scopeIds); },
593
- refreshModels: signal => contexts.refreshModels(signal),
594
- pinnedScopedModelsContext: () => contexts.pinnedScopedModelsContext(),
595
- updateScopedModels: enabledModelIds => { contexts.updateScopedModels(enabledModelIds); },
596
- persistScopedModels: enabledModelIds => { contexts.persistScopedModels(enabledModelIds); },
597
- refreshScopedModels: signal => contexts.refreshScopedModels(signal),
598
- pinnedProjectTrustContext: () => contexts.pinnedProjectTrustContext(),
599
- persistProjectTrust: updates => { contexts.persistProjectTrust(updates); },
600
- pinnedLoginOptions: authType => contexts.loginOptions(authType),
601
- pinnedLoginMethodOptions: providerReference => contexts.pinnedLoginMethodOptions(providerReference),
602
- pinnedAmbientAuthentication: selection => contexts.pinnedAmbientAuthentication(selection),
603
- pinnedLogoutOptions: () => contexts.logoutOptions(),
604
- pinnedForkOptions: () => contexts.pinnedForkOptions(),
605
- },
606
- extensions: {
607
- nonVisualResources: () => resources.nonVisualResources(),
608
- extensionResources: () => resources.extensionResources(),
609
- resolveTranscriptImage: assetId => this.resolveTranscriptImage(assetId),
610
- visualExtensionSupport: () => extensions.support(),
611
- bindExtensionUi: (ui, shutdown) => extensions.bind(ui, shutdown),
612
- unbindExtensionUi: () => extensions.unbind(),
613
- bindClipboardWriter: writer => workflows.bindClipboardWriter(writer),
614
- announceReleaseUpdate: release => { this.announceReleaseUpdate(release); },
615
- },
616
- presentation: {
617
- pinnedModelSelectorContext: () => contexts.pinnedModelSelectorContext(),
618
- pinnedSessionSelectorContext: () => contexts.pinnedSessionSelectorContext(),
619
- pinnedTreeSelectorContext: () => contexts.pinnedTreeSelectorContext(),
620
- pinnedMessageRenderer: customType => this.#pinnedMessageRenderer(customType),
621
- pinnedToolRenderers: toolName => this.#pinnedToolRenderers(toolName),
622
- pinnedShortcutDescriptions: bindings => this.#pinnedShortcutDescriptions(bindings),
623
- pinnedSettingsModels: () => {
624
- const { currentModel, availableDefaultModels } = settings.pinnedSettingsSnapshot();
625
- return { ...(currentModel === undefined ? {} : { currentModel }), availableDefaultModels };
626
- },
627
- applyPinnedSettingValue: (callback, value) => settings.applyPinnedSettingValue(callback, value),
628
- },
390
+ disposed: () => this.#disposed,
391
+ executeWorkflow: request => this.executeWorkflow(request),
392
+ clearQueuedWorkflows: () => this.clearQueuedWorkflows(),
393
+ setWorkflowInteractionHost: interaction => { this.setWorkflowInteractionHost(interaction); },
394
+ announceReleaseUpdate: release => { this.#announceReleaseUpdate(release); },
395
+ resolveTranscriptImage: assetId => this.#resolveTranscriptImage(assetId),
396
+ extensionRunner: () => this.#requireWorkflowSession().extensionRunner,
397
+ });
398
+ }
399
+ #commandContext() {
400
+ return {
401
+ engine: this.#engine,
402
+ state: this.#state,
403
+ admissionOpen: () => !this.#delivery.overloaded && !this.#admissionStopped,
404
+ emit: value => { this.#emitEvent(value); },
405
+ dispose: () => this.dispose(),
629
406
  };
630
407
  }
631
408
  #requireWorkflowSession() {
@@ -633,75 +410,16 @@ export class PiEngineAdapter {
633
410
  throw new Error("engine adapter is not running");
634
411
  return this.#engine.session;
635
412
  }
636
- async #perform(command) {
637
- const runtime = this.#engine.runtime;
638
- const session = this.#engine.session;
639
- const generation = this.#engine.generation;
640
- if (!runtime || !session)
641
- throw new Error("engine session is unavailable");
642
- switch (command.type) {
643
- case "prompt":
644
- case "steer":
645
- case "follow-up":
646
- case "abort":
647
- case "retry":
648
- case "compact": {
649
- const result = await this.#engine.sessionCommands?.execute(command.type === "prompt" || command.type === "steer" || command.type === "follow-up"
650
- ? {
651
- type: command.type,
652
- text: command.text,
653
- ...(command.images === undefined ? {} : { images: [...command.images] }),
654
- }
655
- : { type: command.type });
656
- if (!result || result.outcome === "rejected" || result.outcome === "failed") {
657
- throw new Error(command.type === "retry" ? "no previous prompt is available to retry" : `Pi session command failed: ${command.type}`);
658
- }
659
- return;
660
- }
661
- case "set-model": {
662
- const model = runtime.services.modelRuntime.getModel(command.model.providerId, command.model.modelId);
663
- if (!model) {
664
- throw new Error(`model is unavailable: ${command.model.providerId}/${command.model.modelId}`);
665
- }
666
- await session.setModel(model);
667
- if (generation === this.#engine.generation && !this.#delivery.overloaded && !this.#admissionStopped)
668
- this.#activeModel = { ...command.model };
669
- return;
670
- }
671
- case "set-thinking-level":
672
- session.setThinkingLevel(command.thinkingLevel);
673
- this.#thinkingLevel = command.thinkingLevel;
674
- return;
675
- case "new-session":
676
- await runtime.newSession();
677
- return;
678
- case "resume-session":
679
- await runtime.switchSession(command.sessionPath);
680
- return;
681
- case "resize-surface":
682
- this.#terminal = { ...command.surface };
683
- this.#emitEvent({ type: "terminal-surface", surface: this.#terminal });
684
- return;
685
- case "shutdown":
686
- await this.dispose();
687
- return;
688
- case "set-setting":
689
- case "apply-customization":
690
- case "remove-customization":
691
- throw new Error("owned UI state commands belong to the owned UI layer, not the Pi engine adapter");
692
- }
693
- }
694
413
  // Invariant: in-place tree navigation rebuilds derived content without clearing the editor draft.
695
414
  #refreshNavigatedSession(editorText) {
696
415
  const session = this.#engine.session;
697
416
  if (!session)
698
417
  return;
699
- if (editorText && !this.#editor.text.trim()) {
700
- this.#editor = { ...this.#editor, text: editorText, selection: null, cursorOffset: editorText.length };
701
- }
702
- this.#activeModel = readModel(session.model);
418
+ if (editorText)
419
+ this.#state.offerEditorText(editorText);
420
+ this.#state.activeModel = readModel(session.model);
703
421
  this.#reconcileActiveModelAvailability();
704
- this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
422
+ this.#state.thinkingLevel = readThinkingLevel(session.thinkingLevel);
705
423
  this.#projection.assets.clear();
706
424
  this.#setTranscript(this.#projection.rebuild(session.messages, "finalized"));
707
425
  this.#emitView();
@@ -710,19 +428,11 @@ export class PiEngineAdapter {
710
428
  #sessionReplaced(session) {
711
429
  this.#delivery.discardObsolete(this.#engine.generation);
712
430
  this.#commands.reset();
713
- this.#editor = {
714
- text: "",
715
- queuedSubmissions: [],
716
- selection: null,
717
- cursorOffset: 0,
718
- historyRevision: this.#editor.historyRevision + 1,
719
- submitEnabled: true,
720
- };
721
- this.#status = { ...this.#status, workingMessage: null, workingProgress: null, badges: [] };
431
+ this.#state.replaceSession();
722
432
  this.#events.reset();
723
- this.#activeModel = readModel(session.model);
433
+ this.#state.activeModel = readModel(session.model);
724
434
  this.#reconcileActiveModelAvailability();
725
- this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
435
+ this.#state.thinkingLevel = readThinkingLevel(session.thinkingLevel);
726
436
  this.#projection.assets.clear();
727
437
  // Invariant: a new binding cannot inherit arguments/results from a reused invocation id.
728
438
  this.#setTranscript([]);
@@ -733,17 +443,17 @@ export class PiEngineAdapter {
733
443
  #reconcileActiveModelAvailability() {
734
444
  const modelRuntime = this.#engine.runtime?.services.modelRuntime;
735
445
  if (!modelRuntime) {
736
- this.#activeModel = null;
446
+ this.#state.activeModel = null;
737
447
  return;
738
448
  }
739
449
  const available = modelRuntime.getAvailableSnapshot?.() ?? [];
740
450
  const sessionModel = readModel(this.#engine.session?.model);
741
- const authoritative = this.#activeModel ?? sessionModel;
451
+ const authoritative = this.#state.activeModel ?? sessionModel;
742
452
  if (authoritative === null)
743
453
  return;
744
454
  const remainsAvailable = available.some(model => stringProperty(model, "provider") === authoritative.providerId
745
455
  && stringProperty(model, "id") === authoritative.modelId);
746
- this.#activeModel = remainsAvailable ? authoritative : null;
456
+ this.#state.activeModel = remainsAvailable ? authoritative : null;
747
457
  }
748
458
  #setTranscript(blocks) {
749
459
  // Invariant: lazy delivery snapshots must freeze before their authoritative source can be removed.
@@ -758,13 +468,12 @@ export class PiEngineAdapter {
758
468
  this.#emitEvent({ type: "diagnostic", diagnostic });
759
469
  }
760
470
  #emitView() {
761
- this.#usageCache = undefined;
762
- this.#viewRevision += 1;
471
+ this.#state.invalidate();
763
472
  this.#emitEvent({ type: "session-view", view: this.view() });
764
473
  }
765
474
  #emitEvent(value) {
766
475
  if (value.type !== "session-view")
767
- this.#viewRevision += 1;
476
+ this.#state.advance();
768
477
  this.#delivery.emit(value);
769
478
  }
770
479
  // Concurrency: cancellation runs synchronously so the overload reservation already guards reentrant outcomes.
@@ -779,10 +488,7 @@ export class PiEngineAdapter {
779
488
  this.#admissionStopped = !canResume;
780
489
  this.#engine.suspend();
781
490
  this.#events.abandonRun();
782
- this.#status = { ...this.#status, workingMessage: null, workingProgress: null };
783
- this.#lifecycle = this.#disposed ? "stopped" : canResume ? "ready" : "failed";
784
- this.#editor = { ...this.#editor, submitEnabled: canResume && !this.#disposed };
785
- this.#viewRevision += 1;
491
+ this.#state.settleOverload(canResume, this.#disposed);
786
492
  // Invariant: overload publishes one authoritative view; settle tool phases before finalizing other live blocks.
787
493
  this.#projection.settleFailedDeclarations({ role: "assistant", stopReason: cancelled ? "aborted" : "error",
788
494
  errorMessage: "Tool result unavailable after UI delivery overload" });
@@ -799,21 +505,7 @@ export class PiEngineAdapter {
799
505
  this.#engine.resume();
800
506
  }
801
507
  #recordDiagnostic(severity, code, message, recoverable) {
802
- const diagnostic = {
803
- sequence: this.#diagnostics.length,
804
- code,
805
- severity,
806
- message,
807
- recoverable,
808
- };
809
- this.#diagnostics.push(diagnostic);
810
- if (this.#diagnostics.length > 100)
811
- this.#diagnostics.shift();
812
- this.#status = {
813
- ...this.#status,
814
- diagnostics: [...this.#status.diagnostics, message].slice(-8),
815
- };
816
- return diagnostic;
508
+ return this.#state.recordDiagnostic(severity, code, message, recoverable);
817
509
  }
818
510
  }
819
511
  export async function createPiEngineAdapter(options) {