@timurproko/a1 0.2.7-dev.1264 → 0.2.7-dev.1266

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.
@@ -1,3 +1,4 @@
1
+ import { SessionTerminalPresentation } from "./session-terminal-presentation.js";
1
2
  import { ShellClipboard } from "./shell-clipboard.js";
2
3
  import { SessionPromptHistory } from "./session-prompt-history.js";
3
4
  import { shellResourceEntries } from "./resource-labels.js";
@@ -64,7 +65,11 @@ export class OwnedUiSessionPresenter {
64
65
  #inputCoordination;
65
66
  #unbindShellSettings;
66
67
  #unbindSessionSettings;
67
- #terminalProgressEnabled = false;
68
+ #terminalPresentation = new SessionTerminalPresentation({
69
+ terminal: () => this.#terminal,
70
+ programStatus: () => this.#programStatus,
71
+ titleEnabled: () => this.#customViewport && this.#started && !this.#disposed,
72
+ });
68
73
  #programStatus;
69
74
  #fullscreenExitOutput = "transcript";
70
75
  #lastClearTime = 0;
@@ -186,7 +191,7 @@ export class OwnedUiSessionPresenter {
186
191
  this.#programStatus = options.presenters.components.chrome.programStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
187
192
  this.#login = new LoginInteraction({
188
193
  terminal: this.#terminal, root: this.root, programStatus: this.#programStatus, dialogs: options.presenters.components.dialogs,
189
- syncProgress: () => this.#syncTerminalProgress(this.view()),
194
+ syncProgress: () => this.#terminalPresentation.syncProgress(this.view()),
190
195
  });
191
196
  this.#submission = this.#createSubmission(cwd);
192
197
  this.#reloadPresentation = reloadPresentation;
@@ -216,9 +221,9 @@ export class OwnedUiSessionPresenter {
216
221
  terminal: this.#terminal,
217
222
  root: this.root,
218
223
  setTerminalProgress: enabled => {
219
- this.#terminalProgressEnabled = enabled;
224
+ this.#terminalPresentation.progressEnabled = enabled;
220
225
  if (this.#terminal.running)
221
- this.#syncTerminalProgress(this.view());
226
+ this.#terminalPresentation.syncProgress(this.view());
222
227
  },
223
228
  }, { customViewport: this.#customViewport });
224
229
  constructed.push(this.#unbindSessionSettings);
@@ -291,7 +296,9 @@ export class OwnedUiSessionPresenter {
291
296
  return this.#layoutParts[part];
292
297
  }
293
298
  activated() {
294
- this.#syncTerminalProgress(this.view());
299
+ const view = this.view();
300
+ this.#terminalPresentation.syncProgress(view);
301
+ this.#terminalPresentation.syncTitle(view, true);
295
302
  }
296
303
  handleViewportInput(data) {
297
304
  if (!this.#inputCoordination)
@@ -360,7 +367,7 @@ export class OwnedUiSessionPresenter {
360
367
  const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.identity.sessionGeneration
361
368
  ? this.#syncBlock(event.block)
362
369
  : semanticOnly ? this.view() : this.#syncView();
363
- this.#syncTerminalProgress(view);
370
+ this.#terminalPresentation.syncProgress(view);
364
371
  this.#promptSuggestions?.noteView(view, this.backend.identity.sessionGeneration !== this.#sessionGeneration);
365
372
  if (event.type === "status" && /^(Retrying|Compacting)/.test(event.status.workingMessage ?? "")) {
366
373
  this.#promptSuggestions?.invalidate();
@@ -385,8 +392,9 @@ export class OwnedUiSessionPresenter {
385
392
  return;
386
393
  this.#started = true;
387
394
  this.#terminal.start();
395
+ this.#terminalPresentation.syncTitle(this.view());
388
396
  this.#history?.start();
389
- this.#syncTerminalProgress(this.view());
397
+ this.#terminalPresentation.syncProgress(this.view());
390
398
  void this.backend.extensions.bindExtensionUi(this.#extensionBridge.context, () => { void this.shutdown(); });
391
399
  this.#syncView();
392
400
  // Performance: the spare clipboard helpers fork after the first frame is out, so startup never waits on them.
@@ -822,28 +830,13 @@ export class OwnedUiSessionPresenter {
822
830
  }
823
831
  this.#history?.rebind(replaced, scope);
824
832
  }
825
- #syncTerminalProgress(view) {
826
- if (!this.#terminal.running)
827
- return;
828
- this.#terminal.setTerminalProgress(this.#terminalProgressEnabled && view.lifecycle === "busy");
829
- if (view.lifecycle === "stopped" || view.lifecycle === "stopping") {
830
- this.#programStatus.clear();
831
- return;
832
- }
833
- this.#programStatus.setBlocked("dialog", view.dialog === null ? undefined : {
834
- kind: "question",
835
- message: view.dialog.title,
836
- });
837
- if (view.lifecycle === "failed")
838
- this.#programStatus.setErrorMessage(view.status.diagnostics.at(-1));
839
- this.#programStatus.report();
840
- }
841
833
  #syncView() {
842
834
  this.#streamPresentation.noteImmediatePresentation();
843
835
  const view = this.view();
844
836
  if (this.backend.identity.sessionGeneration !== this.#sessionGeneration)
845
837
  this.#concerns.replaceSession();
846
838
  this.root.update(view);
839
+ this.#terminalPresentation.syncTitle(view);
847
840
  this.#dialogs.syncBackend(view.dialog);
848
841
  this.#presentStartupTrustWarning(view);
849
842
  this.#terminal.requestRender();
@@ -0,0 +1,16 @@
1
+ import type { OwnedUiSessionViewModel, OwnedUiShellProgramStatusReporter } from "../../contracts/owned-ui/index.js";
2
+ import type { OwnedUiPresenterTerminal } from "./shell-host.js";
3
+ interface SessionTerminalPresentationPorts {
4
+ terminal(): OwnedUiPresenterTerminal;
5
+ programStatus(): OwnedUiShellProgramStatusReporter;
6
+ titleEnabled(): boolean;
7
+ }
8
+ /** Session-derived terminal state, published only through the presenter's guarded handle. */
9
+ export declare class SessionTerminalPresentation {
10
+ #private;
11
+ progressEnabled: boolean;
12
+ constructor(ports: SessionTerminalPresentationPorts);
13
+ syncTitle(view: OwnedUiSessionViewModel, force?: boolean): void;
14
+ syncProgress(view: OwnedUiSessionViewModel): void;
15
+ }
16
+ export {};
@@ -0,0 +1,35 @@
1
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
2
+ /** Session-derived terminal state, published only through the presenter's guarded handle. */
3
+ export class SessionTerminalPresentation {
4
+ #ports;
5
+ progressEnabled = false;
6
+ // Invariant: extension titles never change this memo, so unrelated views leave them alone.
7
+ #lastCoreTitle;
8
+ constructor(ports) { this.#ports = ports; }
9
+ syncTitle(view, force = false) {
10
+ const terminal = this.#ports.terminal();
11
+ if (!this.#ports.titleEnabled() || !terminal.running || !terminal.attached)
12
+ return;
13
+ const title = view.status.footer?.sessionName?.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/gu, " ").trim()
14
+ || PRODUCT_IDENTITY.displayName;
15
+ if (!force && title === this.#lastCoreTitle)
16
+ return;
17
+ terminal.setTitle(title);
18
+ this.#lastCoreTitle = title;
19
+ }
20
+ syncProgress(view) {
21
+ const terminal = this.#ports.terminal();
22
+ if (!terminal.running)
23
+ return;
24
+ const status = this.#ports.programStatus();
25
+ terminal.setTerminalProgress(this.progressEnabled && view.lifecycle === "busy");
26
+ if (view.lifecycle === "stopped" || view.lifecycle === "stopping") {
27
+ status.clear();
28
+ return;
29
+ }
30
+ status.setBlocked("dialog", view.dialog === null ? undefined : { kind: "question", message: view.dialog.title });
31
+ if (view.lifecycle === "failed")
32
+ status.setErrorMessage(view.status.diagnostics.at(-1));
33
+ status.report();
34
+ }
35
+ }
@@ -20,6 +20,8 @@ import { nativeHyperlinkStyle } from "../ui/components/spans.js";
20
20
  import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
21
21
  import { deferStartupReleaseUpdateObservation, readPackageVersion, StartupReleaseNote } from "./startup-route.js";
22
22
  import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
23
+ /** Production default: without an inspector the registry never registers; loaded lazily. */
24
+ const inspectLiveProcessOnDemand = async (pid) => (await import("../foundation/lifecycle/process-liveness.js")).inspectLiveProcess(pid);
23
25
  export async function composeOwnedUiApplication(options = {}) {
24
26
  return (await composeOwnedUi(options)).application;
25
27
  }
@@ -28,7 +30,7 @@ export async function composeOwnedUi(options = {}) {
28
30
  const productMode = options.productMode ?? "bare", bare = productMode === "bare";
29
31
  const sessionRuntimes = new SessionRuntimeRegistry({
30
32
  runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined,
31
- inspectProcess: options.inspectProcess,
33
+ inspectProcess: options.inspectProcess ?? inspectLiveProcessOnDemand,
32
34
  });
33
35
  const productPaths = resolveProductPaths();
34
36
  // Rationale: the manager reads the engine's settings port lazily, after the session exists.
@@ -65,6 +65,11 @@ export class PiSessionEvents {
65
65
  /** Apply one pinned Pi session event; image assets no block references any more are released afterwards. */
66
66
  handle(event) {
67
67
  try {
68
+ if (!isRecord(event) || typeof event.type !== "string")
69
+ return;
70
+ // Invariant: usage moves at message and lifecycle boundaries, not with stream chunks.
71
+ if (event.type !== "message_update" && event.type !== "tool_execution_update")
72
+ this.#ports.usageInvalidated();
68
73
  this.#applyPiEvent(event);
69
74
  }
70
75
  finally {
@@ -72,12 +77,6 @@ export class PiSessionEvents {
72
77
  }
73
78
  }
74
79
  #applyPiEvent(event) {
75
- if (!isRecord(event) || typeof event.type !== "string")
76
- return;
77
- // Invariant: usage moves at message and lifecycle boundaries, not with stream chunks, so the
78
- // two streaming event kinds keep the memo and everything else drops it.
79
- if (event.type !== "message_update" && event.type !== "tool_execution_update")
80
- this.#ports.usageInvalidated();
81
80
  switch (event.type) {
82
81
  case "agent_start":
83
82
  this.#agentRunActive = true;
@@ -221,6 +220,9 @@ export class PiSessionEvents {
221
220
  if (event.reason === "manual")
222
221
  this.#ports.deliverQueuedAfterCompaction();
223
222
  return;
223
+ case "session_info_changed":
224
+ this.#ports.emitView();
225
+ return;
224
226
  case "thinking_level_changed":
225
227
  this.#ports.thinkingLevelChanged(readThinkingLevel(event.level));
226
228
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.1264",
3
+ "version": "0.2.7-dev.1266",
4
4
  "description": "Standalone single-agent terminal UI on pinned Pi",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",
@@ -113,7 +113,7 @@
113
113
  "@mariozechner/clipboard": "^0.3.9"
114
114
  },
115
115
  "publication": {
116
- "source": "47397a0a5b680ed23515a914597d42c489e691ea",
117
- "pullRequest": 891
116
+ "source": "3af62d09bf030ab54ed112fdcd74984bd157768b",
117
+ "pullRequest": 893
118
118
  }
119
119
  }