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

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 (57) hide show
  1. package/README.md +2 -0
  2. package/dist/app/session-shell/session-name-reveal.d.ts +17 -0
  3. package/dist/app/session-shell/session-name-reveal.js +49 -0
  4. package/dist/app/session-shell/session-shell-root.d.ts +2 -1
  5. package/dist/app/session-shell/session-shell-root.js +14 -9
  6. package/dist/app/session-shell/session-shell.js +6 -0
  7. package/dist/app/session-shell/session-terminal-presentation.d.ts +7 -0
  8. package/dist/app/session-shell/session-terminal-presentation.js +11 -1
  9. package/dist/app/session-shell/shell-host.js +3 -1
  10. package/dist/composition/owned-ui.js +8 -6
  11. package/dist/composition/session-backend.d.ts +2 -1
  12. package/dist/composition/session-naming.d.ts +6 -0
  13. package/dist/composition/session-naming.js +23 -0
  14. package/dist/composition/session-runtime-registry.d.ts +5 -1
  15. package/dist/composition/session-runtime-registry.js +11 -3
  16. package/dist/contracts/owned-ui/index.d.ts +1 -0
  17. package/dist/contracts/owned-ui/model.d.ts +6 -0
  18. package/dist/contracts/owned-ui/session-naming.d.ts +22 -0
  19. package/dist/contracts/owned-ui/session-naming.js +1 -0
  20. package/dist/contracts/owned-ui/shell-components.d.ts +5 -0
  21. package/dist/contracts/owned-ui/validation.js +5 -0
  22. package/dist/features/owned-ui/settings-app.js +10 -2
  23. package/dist/features/session-naming/controller.d.ts +23 -0
  24. package/dist/features/session-naming/controller.js +149 -0
  25. package/dist/features/session-naming/generate.d.ts +5 -0
  26. package/dist/features/session-naming/generate.js +54 -0
  27. package/dist/features/session-naming/index.d.ts +2 -0
  28. package/dist/features/session-naming/index.js +1 -0
  29. package/dist/foundation/lifecycle/index.d.ts +2 -2
  30. package/dist/foundation/lifecycle/index.js +1 -1
  31. package/dist/foundation/lifecycle/session-repository-context.d.ts +12 -0
  32. package/dist/foundation/lifecycle/session-repository-context.js +59 -3
  33. package/dist/integrations/pi/components/shell-footer-status.js +1 -0
  34. package/dist/integrations/pi/components/shell-presenters-transcript.js +3 -6
  35. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +5 -0
  36. package/dist/integrations/pi/components/upstream/components/session-footer.js +44 -7
  37. package/dist/integrations/pi/components/upstream/components/tool-execution.js +4 -8
  38. package/dist/integrations/pi/engine/adapter.d.ts +3 -2
  39. package/dist/integrations/pi/engine/adapter.js +37 -28
  40. package/dist/integrations/pi/engine/repository-metadata.d.ts +2 -0
  41. package/dist/integrations/pi/engine/repository-metadata.js +12 -16
  42. package/dist/integrations/pi/engine/session-events.d.ts +1 -0
  43. package/dist/integrations/pi/engine/session-events.js +2 -0
  44. package/dist/integrations/pi/engine/session-naming.d.ts +24 -0
  45. package/dist/integrations/pi/engine/session-naming.js +88 -0
  46. package/dist/integrations/pi/engine/view-state.d.ts +5 -1
  47. package/dist/integrations/pi/engine/view-state.js +3 -1
  48. package/dist/ui/components/index.d.ts +1 -0
  49. package/dist/ui/components/index.js +1 -0
  50. package/dist/ui/components/session-name-frame.d.ts +17 -0
  51. package/dist/ui/components/session-name-frame.js +24 -0
  52. package/dist/ui/settings/declarations.d.ts +24 -0
  53. package/dist/ui/settings/declarations.js +18 -0
  54. package/docs/architecture/project-structure.md +1 -0
  55. package/docs/architecture/tool-image-presentation.md +3 -3
  56. package/docs/features/session-naming.md +37 -0
  57. package/package.json +3 -3
package/README.md CHANGED
@@ -67,6 +67,8 @@ a1 --session <id>
67
67
 
68
68
  When a session exits, A1 prints the `a1 --session <id>` command that resumes it; a custom session store is included in the printed command. Closing the terminal ends the agent, as it does for Pi.
69
69
 
70
+ Bare A1 automatically names unnamed sessions from the first prompt and regenerates automatic names on the next prompt after a worktree-context change. Manual `/name` renames are preserved. Names appear after footer stats, with a secondary-accent reveal for automatic changes. Agent settings offers **Session name format** with Kebab case (default), Sentence case, and Title Case for future generated names, or **Automatic session name** to disable naming; see [session names](docs/features/session-naming.md) for details.
71
+
70
72
  Show all commands:
71
73
 
72
74
  ```text
@@ -0,0 +1,17 @@
1
+ import type { OwnedUiSessionViewModel } from "../../contracts/owned-ui/index.js";
2
+ interface RevealPorts {
3
+ active(): boolean;
4
+ frame(value: {
5
+ readonly from: string;
6
+ readonly elapsed: number;
7
+ } | null): boolean;
8
+ requestRender(): void;
9
+ }
10
+ /** Presenter-owned short-lived animation clock; saved provenance never replays on activation. */
11
+ export declare class SessionNameReveal {
12
+ #private;
13
+ constructor(ports: RevealPorts);
14
+ update(view: OwnedUiSessionViewModel, binding: number): void;
15
+ stop(): void;
16
+ }
17
+ export {};
@@ -0,0 +1,49 @@
1
+ /** Presenter-owned short-lived animation clock; saved provenance never replays on activation. */
2
+ export class SessionNameReveal {
3
+ #ports;
4
+ #binding;
5
+ #seen;
6
+ #timer;
7
+ #animation;
8
+ constructor(ports) { this.#ports = ports; }
9
+ update(view, binding) {
10
+ const reveal = view.status.footer?.sessionNameReveal;
11
+ if (this.#binding !== binding) {
12
+ this.stop();
13
+ this.#binding = binding;
14
+ this.#seen = reveal?.id;
15
+ return;
16
+ }
17
+ if (reveal?.id === this.#seen)
18
+ return;
19
+ this.stop();
20
+ this.#seen = reveal?.id;
21
+ if (!reveal || !this.#ports.active())
22
+ return;
23
+ this.#animation = { from: reveal.from, started: performance.now() };
24
+ this.#tick();
25
+ }
26
+ stop() {
27
+ if (this.#timer !== undefined)
28
+ clearTimeout(this.#timer);
29
+ this.#timer = undefined;
30
+ this.#animation = undefined;
31
+ this.#ports.frame(null);
32
+ }
33
+ #tick() {
34
+ this.#timer = undefined;
35
+ const animation = this.#animation;
36
+ if (!animation || !this.#ports.active()) {
37
+ this.stop();
38
+ return;
39
+ }
40
+ const active = this.#ports.frame({ from: animation.from, elapsed: performance.now() - animation.started });
41
+ this.#ports.requestRender();
42
+ if (!active) {
43
+ this.#animation = undefined;
44
+ return;
45
+ }
46
+ this.#timer = setTimeout(() => this.#tick(), 15);
47
+ this.#timer.unref?.();
48
+ }
49
+ }
@@ -1,4 +1,4 @@
1
- import type { OwnedUiShellComponents, OwnedUiShellHotkeysPresentation, OwnedUiShellClipboardContent, OwnedUiShellComponentPort, OwnedUiShellEditorPort, OwnedUiShellHeaderOptions, OwnedUiShellHeaderPort, OwnedUiShellHistoryEditor, OwnedUiShellImageAssetResolver, OwnedUiShellKeybindingsHost, OwnedUiShellLoadedResourcesPort } from "../../contracts/owned-ui/index.js";
1
+ import type { OwnedUiShellComponents, OwnedUiShellHotkeysPresentation, OwnedUiShellClipboardContent, OwnedUiShellComponentPort, OwnedUiShellEditorPort, OwnedUiShellHeaderOptions, OwnedUiShellHeaderPort, OwnedUiShellHistoryEditor, OwnedUiShellImageAssetResolver, OwnedUiShellKeybindingsHost, OwnedUiShellLoadedResourcesPort, OwnedUiShellViewComponentPort } from "../../contracts/owned-ui/index.js";
2
2
  import type { TerminalComponentPort, TerminalInputCoordinationScheduler, TerminalInputDiagnosticsEvent, TerminalInputSurfaceKind, TerminalLayoutNode, TerminalPort } from "../../contracts/presentation/index.js";
3
3
  import type { ShellPresentationPorts, ThemeSource } from "../../contracts/presentation/index.js";
4
4
  import type { OwnedUiBackgroundSettingsPort, OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionBackend, OwnedUiSessionPresenters, OwnedUiSessionViewModel, OwnedUiTranscriptRendererPort, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, OwnedUiQuitOutroSettingsPort, SuggestionDecision, SuggestionDiagnosticObserver } from "../../contracts/owned-ui/index.js";
@@ -193,6 +193,7 @@ export declare class OwnedUiSessionShellRoot implements TerminalComponentPort {
193
193
  readonly resources: OwnedUiShellLoadedResourcesPort;
194
194
  /** The transcript's semantic rows, caches, and engine-backed block renderers. */
195
195
  readonly transcript: TranscriptDocument;
196
+ readonly footer: OwnedUiShellViewComponentPort;
196
197
  readonly promptChips: PromptChipStore;
197
198
  readonly viewport: SessionViewportController;
198
199
  /** Workflow results, messages, and status lines appended to the transcript. */
@@ -24,7 +24,7 @@ export class OwnedUiSessionShellRoot {
24
24
  #themeUnsubscribe;
25
25
  #view;
26
26
  #status;
27
- #footer;
27
+ footer;
28
28
  #queued;
29
29
  #extensionRenderers;
30
30
  #imageAssets;
@@ -78,7 +78,12 @@ export class OwnedUiSessionShellRoot {
78
78
  frame: progressStatusFrame,
79
79
  }, handlers);
80
80
  this.#status.setProgressPresentation(this.#customViewport ? "custom-viewport" : "pinned");
81
- this.#footer = handlers.components.chrome.footer(this.#extensions.withStatuses(view), cwd, this.#customViewport ? "a1" : "pi", () => this.#footerLevel);
81
+ const footer = handlers.components.chrome.footer(this.#extensions.withStatuses(view), cwd, this.#customViewport ? "a1" : "pi", () => this.#footerLevel);
82
+ this.footer = { ...footer, setNameReveal: frame => {
83
+ const active = footer.setNameReveal?.(frame) ?? false;
84
+ this.#invalidateChrome();
85
+ return active && this.#extensions.footer === null;
86
+ } };
82
87
  this.editor = createShellEditor({
83
88
  handlers, theme: handlers.presentation.theme, components: handlers.components,
84
89
  customViewport: this.#customViewport,
@@ -273,7 +278,7 @@ export class OwnedUiSessionShellRoot {
273
278
  this.#syncWorkingOverride();
274
279
  }
275
280
  this.#status.update(view);
276
- this.#footer.update(this.#extensions.withStatuses(view));
281
+ this.footer.update(this.#extensions.withStatuses(view));
277
282
  this.#queued.update(view.editor.queuedSubmissions);
278
283
  this.transcript.sync(view.transcript);
279
284
  this.editor.setSubmitEnabled(view.lifecycle !== "stopping" && view.lifecycle !== "stopped" && view.lifecycle !== "failed");
@@ -379,7 +384,7 @@ export class OwnedUiSessionShellRoot {
379
384
  status: layoutPort(width => this.#renderStatus(width), () => this.#status.invalidate()),
380
385
  editor: layoutPort(width => this.#input.surface.render(width), () => this.#input.surface.invalidate(), data => this.#input.surface.handleInput?.(data)),
381
386
  belowWidgets: layoutPort(width => this.#extensions.renderWidgets("belowEditor", width), () => this.#extensions.invalidate()),
382
- footer: layoutPort(width => this.#renderFooter(width), () => (this.#extensions.footer ?? this.#footer).invalidate()),
387
+ footer: layoutPort(width => this.#renderFooter(width), () => (this.#extensions.footer ?? this.footer).invalidate()),
383
388
  };
384
389
  }
385
390
  #renderDocument(width) {
@@ -409,7 +414,7 @@ export class OwnedUiSessionShellRoot {
409
414
  }
410
415
  setExtensionStatus(key, text) {
411
416
  this.#extensions.setStatus(key, text);
412
- this.#footer.update(this.#extensions.withStatuses(this.#view));
417
+ this.footer.update(this.#extensions.withStatuses(this.#view));
413
418
  this.invalidate();
414
419
  }
415
420
  setExtensionWorking(message, visible = this.#extensions.working.visible) {
@@ -453,7 +458,7 @@ export class OwnedUiSessionShellRoot {
453
458
  resetExtensionUi() {
454
459
  this.#extensions.reset();
455
460
  this.#syncWorkingOverride();
456
- this.#footer.update(this.#extensions.withStatuses(this.#view));
461
+ this.footer.update(this.#extensions.withStatuses(this.#view));
457
462
  // Invariant: an extension renderer may have drawn transcript blocks that are now unrendered by it.
458
463
  this.transcript.invalidate("extension-renderers");
459
464
  this.invalidate();
@@ -505,7 +510,7 @@ export class OwnedUiSessionShellRoot {
505
510
  this.#input.surface.invalidate();
506
511
  this.#extensions.invalidate();
507
512
  this.#status.invalidate();
508
- this.#footer.invalidate();
513
+ this.footer.invalidate();
509
514
  this.#queued.invalidate();
510
515
  }
511
516
  setFocused(focused) {
@@ -525,7 +530,7 @@ export class OwnedUiSessionShellRoot {
525
530
  this.#extensions.dispose();
526
531
  this.editor.dispose?.();
527
532
  this.#status.dispose?.();
528
- this.#footer.dispose?.();
533
+ this.footer.dispose?.();
529
534
  this.#queued.dispose?.();
530
535
  }
531
536
  #invalidateTranscriptPresentation() {
@@ -536,7 +541,7 @@ export class OwnedUiSessionShellRoot {
536
541
  return this.#customViewport ? this.#status.renderLive(width) : this.#status.render(width);
537
542
  }
538
543
  #renderFooter(width) {
539
- const footer = this.#extensions.footer ?? this.#footer;
544
+ const footer = this.#extensions.footer ?? this.footer;
540
545
  if (!this.#customViewport)
541
546
  return footer.render(width);
542
547
  width = Math.max(0, width - 2);
@@ -69,6 +69,8 @@ export class OwnedUiSessionPresenter {
69
69
  terminal: () => this.#terminal,
70
70
  programStatus: () => this.#programStatus,
71
71
  titleEnabled: () => this.#customViewport && this.#started && !this.#disposed,
72
+ binding: () => this.backend.identity.sessionBindingGeneration,
73
+ nameFrame: frame => this.root.footer.setNameReveal?.(frame) ?? false,
72
74
  });
73
75
  #programStatus;
74
76
  #fullscreenExitOutput = "transcript";
@@ -281,6 +283,8 @@ export class OwnedUiSessionPresenter {
281
283
  }
282
284
  setFocused(focused) {
283
285
  this.root.setFocused(focused);
286
+ if (!focused)
287
+ this.#terminalPresentation.nameReveal.stop();
284
288
  }
285
289
  inputCoordinationSurface() {
286
290
  return this.root.inputCoordinationSurface();
@@ -296,6 +300,7 @@ export class OwnedUiSessionPresenter {
296
300
  return this.#layoutParts[part];
297
301
  }
298
302
  activated() {
303
+ this.#terminalPresentation.nameReveal.stop();
299
304
  const view = this.view();
300
305
  this.#terminalPresentation.syncProgress(view);
301
306
  this.#terminalPresentation.syncTitle(view, true);
@@ -664,6 +669,7 @@ export class OwnedUiSessionPresenter {
664
669
  if (this.#released !== undefined)
665
670
  return { exitText: "", failures: [], settle: async () => [] };
666
671
  this.#disposed = true;
672
+ this.#terminalPresentation.nameReveal.stop();
667
673
  const failures = [];
668
674
  const attempt = (action) => { try {
669
675
  action();
@@ -1,14 +1,21 @@
1
1
  import type { OwnedUiSessionViewModel, OwnedUiShellProgramStatusReporter } from "../../contracts/owned-ui/index.js";
2
2
  import type { OwnedUiPresenterTerminal } from "./shell-host.js";
3
+ import { SessionNameReveal } from "./session-name-reveal.js";
3
4
  interface SessionTerminalPresentationPorts {
4
5
  terminal(): OwnedUiPresenterTerminal;
5
6
  programStatus(): OwnedUiShellProgramStatusReporter;
6
7
  titleEnabled(): boolean;
8
+ binding(): number;
9
+ nameFrame(frame: {
10
+ readonly from: string;
11
+ readonly elapsed: number;
12
+ } | null): boolean;
7
13
  }
8
14
  /** Session-derived terminal state, published only through the presenter's guarded handle. */
9
15
  export declare class SessionTerminalPresentation {
10
16
  #private;
11
17
  progressEnabled: boolean;
18
+ readonly nameReveal: SessionNameReveal;
12
19
  constructor(ports: SessionTerminalPresentationPorts);
13
20
  syncTitle(view: OwnedUiSessionViewModel, force?: boolean): void;
14
21
  syncProgress(view: OwnedUiSessionViewModel): void;
@@ -1,12 +1,22 @@
1
1
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
2
+ import { SessionNameReveal } from "./session-name-reveal.js";
2
3
  /** Session-derived terminal state, published only through the presenter's guarded handle. */
3
4
  export class SessionTerminalPresentation {
4
5
  #ports;
5
6
  progressEnabled = false;
7
+ nameReveal;
6
8
  // Invariant: extension titles never change this memo, so unrelated views leave them alone.
7
9
  #lastCoreTitle;
8
- constructor(ports) { this.#ports = ports; }
10
+ constructor(ports) {
11
+ this.#ports = ports;
12
+ this.nameReveal = new SessionNameReveal({
13
+ active: () => ports.titleEnabled() && ports.terminal().running && ports.terminal().attached,
14
+ frame: frame => ports.nameFrame(frame),
15
+ requestRender: () => ports.terminal().requestRender(),
16
+ });
17
+ }
9
18
  syncTitle(view, force = false) {
19
+ this.nameReveal.update(view, this.#ports.binding());
10
20
  const terminal = this.#ports.terminal();
11
21
  if (!this.#ports.titleEnabled() || !terminal.running || !terminal.attached)
12
22
  return;
@@ -126,8 +126,10 @@ export class OwnedUiTerminalHost {
126
126
  }
127
127
  /** Forgets a presenter whose construction failed before it could be attached or released. */
128
128
  disconnect(presenter) {
129
- if (this.#active === presenter)
129
+ if (this.#active === presenter) {
130
+ presenter.setFocused(false);
130
131
  this.#active = null;
132
+ }
131
133
  this.#forget(presenter);
132
134
  }
133
135
  attach(presenter) {
@@ -18,6 +18,7 @@ import { createOwnedRouteHost } from "./settings-route-host.js";
18
18
  import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
19
19
  import { nativeHyperlinkStyle } from "../ui/components/spans.js";
20
20
  import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
21
+ import { bindAutomaticSessionNaming } from "./session-naming.js";
21
22
  import { deferStartupReleaseUpdateObservation, readPackageVersion, StartupReleaseNote } from "./startup-route.js";
22
23
  import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
23
24
  /** Production default: without an inspector the registry never registers; loaded lazily. */
@@ -72,6 +73,7 @@ export async function composeOwnedUi(options = {}) {
72
73
  await host.dispose();
73
74
  throw error;
74
75
  }
76
+ const naming = bindAutomaticSessionNaming(backend, bareSettings, bare);
75
77
  const releaseNote = await StartupReleaseNote.load({
76
78
  productMode,
77
79
  configDir: productPaths.configDir,
@@ -114,7 +116,7 @@ export async function composeOwnedUi(options = {}) {
114
116
  const suggestionDiagnostics = bareSettings !== null && diagnosticDestination?.trim()
115
117
  ? await import("../features/prompt-suggestions/diagnostics.js").then(module => new module.SuggestionDiagnosticCapture({ enabled: true, destination: diagnosticDestination })) : null;
116
118
  const barePorts = bareSettings === null ? null : bareShellPorts(bareSettings, backend, suggestionDiagnostics);
117
- const promptHistory = createPromptHistoryConfiguration(settings, backend, bare);
119
+ const promptHistory = await createPromptHistoryConfiguration(settings, backend, bare);
118
120
  const clipboardDestination = options.clipboardDiagnosticsPath ?? process.env[PRODUCT_IDENTITY.environment.clipboardDiagnostics];
119
121
  const clipboardDiagnostics = bareSettings !== null && clipboardDestination?.trim()
120
122
  ? new ClipboardDiagnosticCapture(clipboardDestination) : null;
@@ -123,6 +125,7 @@ export async function composeOwnedUi(options = {}) {
123
125
  // Invariant: one release order for a failed construction and for dispose. The shared spares outlive every
124
126
  // shell and stop only after the last one is disposed; the host outlives the application and is released last.
125
127
  const releaseShared = async () => {
128
+ naming.dispose();
126
129
  clipboardServices?.dispose();
127
130
  unsubscribeAccent();
128
131
  await sessionRuntimes.dispose();
@@ -155,10 +158,7 @@ export async function composeOwnedUi(options = {}) {
155
158
  } }),
156
159
  ...(barePorts === null ? {} : { suggestions: barePorts.suggestions, promptImages: barePorts.promptImages, skills: barePorts.skills }),
157
160
  shared: { keybindings: keybindingsHost, ...(clipboardServices === null ? {} : { clipboard: clipboardServices }) },
158
- ...(promptHistory === null ? {} : { history: {
159
- ...promptHistory,
160
- editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
161
- } }),
161
+ ...(promptHistory === null ? {} : { history: promptHistory }),
162
162
  };
163
163
  // Invariant: one terminal host owns the process terminal; the session presenter reaches it only through
164
164
  // the host and paints once attached.
@@ -198,6 +198,7 @@ export async function composeOwnedUi(options = {}) {
198
198
  flush: () => backend.session.flushEvents(),
199
199
  waitUntilStopped: () => presenter.waitUntilStopped(),
200
200
  dispose: async () => {
201
+ naming.dispose();
201
202
  releaseUpdates.stop();
202
203
  try {
203
204
  await terminalHost.dispose();
@@ -246,7 +247,7 @@ function bareShellPorts(settings, generator, diagnostics) {
246
247
  },
247
248
  };
248
249
  }
249
- function createPromptHistoryConfiguration(settings, backend, bare) {
250
+ async function createPromptHistoryConfiguration(settings, backend, bare) {
250
251
  const limit = settings?.value("promptHistoryMaxItems") ?? 100;
251
252
  const scope = settings?.value("promptHistoryScope") ?? "unique";
252
253
  const enabled = settings !== null && bare && settings.value("promptHistoryEnabled");
@@ -265,6 +266,7 @@ function createPromptHistoryConfiguration(settings, backend, bare) {
265
266
  };
266
267
  return {
267
268
  limit, scope,
269
+ editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
268
270
  ...(sessionId === null ? {} : { sessionId }),
269
271
  forScope,
270
272
  onScopeChange: (listener) => settings.onChange(() => listener(settings.value("promptHistoryScope"))),
@@ -1,5 +1,5 @@
1
1
  import type { AgentSettingsPort } from "../contracts/agent-engine/index.js";
2
- import type { OwnedUiPromptSuggestionGeneratorPort, UiAccentColor } from "../contracts/owned-ui/index.js";
2
+ import type { OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionNamingPort, UiAccentColor } from "../contracts/owned-ui/index.js";
3
3
  import type { PiEngineSessionRequest } from "../integrations/pi/engine/host.js";
4
4
  import type { PiSessionPresenterBackend } from "../integrations/pi/session-presenters/index.js";
5
5
  /**
@@ -9,6 +9,7 @@ import type { PiSessionPresenterBackend } from "../integrations/pi/session-prese
9
9
  */
10
10
  export interface ComposedSessionBackend extends PiSessionPresenterBackend, OwnedUiPromptSuggestionGeneratorPort {
11
11
  settingsPort(): AgentSettingsPort | null;
12
+ readonly naming?: OwnedUiSessionNamingPort;
12
13
  }
13
14
  /** The process-level engine host as composition drives it: sessions, the live accent, and disposal. */
14
15
  export interface ComposedEngineHost {
@@ -0,0 +1,6 @@
1
+ import type { OwnedSettingsManager } from "../ui/settings/manager.js";
2
+ import type { ComposedSessionBackend } from "./session-backend.js";
3
+ /** Connect product policy to the engine and consume verified context under lifecycle coordination. */
4
+ export declare function bindAutomaticSessionNaming(backend: ComposedSessionBackend, settings: OwnedSettingsManager | null, bare: boolean): {
5
+ dispose(): void;
6
+ };
@@ -0,0 +1,23 @@
1
+ import { SessionNamingController } from "../features/session-naming/index.js";
2
+ /** Connect product policy to the engine and consume verified context under lifecycle coordination. */
3
+ export function bindAutomaticSessionNaming(backend, settings, bare) {
4
+ if (!bare || !backend.naming)
5
+ return { dispose() { } };
6
+ const naming = new SessionNamingController({
7
+ backend, naming: backend.naming,
8
+ enabled: () => settings?.value("sessionAutoName") ?? true,
9
+ format: () => settings?.value("sessionNameFormat") ?? "kebab",
10
+ withContext: async (consume, signal) => {
11
+ const sessionId = backend.identity.currentHistorySessionId();
12
+ const sessionFile = backend.identity.currentSessionFile();
13
+ if (!sessionId || !sessionFile)
14
+ throw new Error("session identity unavailable");
15
+ const { withSessionRepositoryObservation } = await import("../foundation/lifecycle/session-repository-context.js");
16
+ return await withSessionRepositoryObservation({ sessionId, sessionFile }, consume, { signal });
17
+ },
18
+ });
19
+ const unsubscribe = settings?.onChange(() => naming.settingsChanged()) ?? (() => { });
20
+ let disposed = false;
21
+ return { dispose() { if (disposed)
22
+ return; disposed = true; naming.dispose(); unsubscribe(); } };
23
+ }
@@ -11,7 +11,11 @@ export declare class SessionRuntimeRegistry {
11
11
  #private;
12
12
  constructor(options: SessionRuntimeRegistryOptions);
13
13
  /** The repository context reader for one agent; undefined names the primary agent. */
14
- readerFor(agentId: string | undefined): (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<import("../foundation/lifecycle/session-repository-context.js").SessionRepositoryContext | null>;
14
+ readerFor(agentId: string | undefined): (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<{
15
+ cwd: string;
16
+ branch: string;
17
+ revision: string;
18
+ } | null>;
15
19
  /** Drain publication before releasing claims; never republish after disposal. */
16
20
  dispose(): Promise<void>;
17
21
  }
@@ -23,14 +23,22 @@ export class SessionRuntimeRegistry {
23
23
  const owner = await this.#ownerIdentity();
24
24
  if (owner === null || !this.#current(agentId, identity))
25
25
  return null;
26
- const { activateSessionRepositoryContext } = await import("../foundation/lifecycle/session-repository-context.js");
26
+ const { activateSessionRepositoryContext, withSessionRepositoryObservation } = await import("../foundation/lifecycle/session-repository-context.js");
27
27
  if (!this.#current(agentId, identity))
28
28
  return null;
29
- return await activateSessionRepositoryContext(identity, runtimeId, owner, {
29
+ const options = {
30
30
  signal: AbortSignal.any([signal, this.#abort.signal]),
31
31
  inspectProcess,
32
32
  ...(agentId === undefined ? {} : { agentId }),
33
- });
33
+ };
34
+ const selected = await activateSessionRepositoryContext(identity, runtimeId, owner, options);
35
+ if (selected === null)
36
+ return null;
37
+ return await withSessionRepositoryObservation(identity, context => {
38
+ if (context.branch === null)
39
+ throw new Error("associated branch unavailable");
40
+ return { cwd: context.cwd, branch: context.branch, revision: context.revision };
41
+ }, options);
34
42
  });
35
43
  };
36
44
  }
@@ -1,3 +1,4 @@
1
+ export type { OwnedUiSessionNameState, OwnedUiSessionNameCompletion, OwnedUiSessionNamingPort } from "./session-naming.js";
1
2
  /** Stable stored choices for bare A1's semantic UI accent. */
2
3
  export type UiAccentColor = "purple" | "blue" | "cyan" | "green" | "orange" | "pink";
3
4
  export { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "./extension-ui.js";
@@ -122,6 +122,12 @@ export interface OwnedUiUsageView {
122
122
  export interface OwnedUiFooterView {
123
123
  readonly branch: string | null;
124
124
  readonly repositoryPath?: string | null;
125
+ readonly repositoryRevision?: string | null;
126
+ /** Ephemeral automatic-apply identity; absent when loading a saved or manually named session. */
127
+ readonly sessionNameReveal?: {
128
+ readonly id: string;
129
+ readonly from: string;
130
+ };
125
131
  readonly pullRequest?: {
126
132
  readonly number: number;
127
133
  readonly url: string;
@@ -0,0 +1,22 @@
1
+ /** Durable naming ownership and optimistic guard for one bound session. */
2
+ export interface OwnedUiSessionNameState {
3
+ readonly sessionFile: string;
4
+ readonly binding: number;
5
+ readonly name: string | null;
6
+ readonly nameEntryId: string | null;
7
+ readonly source: "auto" | "user" | "unnamed";
8
+ readonly completedContext: string | null;
9
+ }
10
+ /** An isolated naming completion: never a conversation turn or a tool-using request. */
11
+ export interface OwnedUiSessionNameCompletion {
12
+ readonly prompt: string;
13
+ readonly maxTokens: number;
14
+ readonly signal: AbortSignal;
15
+ }
16
+ /** Engine operations; repository coordination and product policy belong to the caller. */
17
+ export interface OwnedUiSessionNamingPort {
18
+ state(): OwnedUiSessionNameState | null;
19
+ complete(request: OwnedUiSessionNameCompletion): Promise<string | null>;
20
+ /** Synchronous compare-and-apply, called only inside the caller's repository-context guard. */
21
+ apply(expected: OwnedUiSessionNameState, name: string, context: string): boolean;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -89,6 +89,11 @@ export interface OwnedUiShellAutocompleteCommand {
89
89
  }
90
90
  export interface OwnedUiShellViewComponentPort extends OwnedUiShellComponentPort {
91
91
  update(view: OwnedUiSessionViewModel): void;
92
+ /** Footer-only ephemeral frame; returns whether its clipped name still needs animation. */
93
+ setNameReveal?(frame: {
94
+ readonly from: string;
95
+ readonly elapsed: number;
96
+ } | null): boolean;
92
97
  }
93
98
  export type OwnedUiShellStatusPlacement = "live" | "dock" | "hidden";
94
99
  export type OwnedUiShellProgressPresentationMode = "pinned" | "custom-viewport";
@@ -313,6 +313,11 @@ function assertOwnedUiStatusView(status) {
313
313
  if (status.footer !== undefined) {
314
314
  assertOptionalText(status.footer.branch, "owned-UI footer branch", MAX_LABEL_LENGTH);
315
315
  assertOptionalText(status.footer.repositoryPath ?? null, "owned-UI footer repository path", MAX_MESSAGE_LENGTH);
316
+ assertOptionalText(status.footer.repositoryRevision ?? null, "owned-UI footer repository revision", MAX_LABEL_LENGTH);
317
+ if (status.footer.sessionNameReveal !== undefined) {
318
+ assertBoundedText(status.footer.sessionNameReveal.id, "owned-UI name reveal id", MAX_LABEL_LENGTH);
319
+ assertPossiblyEmptyText(status.footer.sessionNameReveal.from, "owned-UI previous session name", MAX_LABEL_LENGTH);
320
+ }
316
321
  if (status.footer.pullRequest !== undefined && status.footer.pullRequest !== null) {
317
322
  const pullRequest = status.footer.pullRequest;
318
323
  assertIntegerInRange(pullRequest.number, 1, Number.MAX_SAFE_INTEGER, "owned-UI footer pull request number");
@@ -820,10 +820,18 @@ function labelOf(entry) {
820
820
  function rangeOf(entry) {
821
821
  return { minimum: entry.minimum, maximum: entry.maximum, values: numericValues(entry.choices) };
822
822
  }
823
- /** Booleans read as yes and no. */
824
- function displaySettingValue(_entry, value) {
823
+ /** Scalar choices use their readable presentation without changing stored values. */
824
+ function displaySettingValue(entry, value) {
825
825
  if (typeof value === "boolean")
826
826
  return value ? "yes" : "no";
827
+ if (entry.backend === "a1" && entry.id === "sessionNameFormat") {
828
+ if (value === "kebab")
829
+ return "Kebab case";
830
+ if (value === "sentence")
831
+ return "Sentence case";
832
+ if (value === "title")
833
+ return "Title Case";
834
+ }
827
835
  return String(value);
828
836
  }
829
837
  function describeRaw(value) {
@@ -0,0 +1,23 @@
1
+ import type { OwnedUiSessionBackend, OwnedUiSessionNamingPort } from "../../contracts/owned-ui/index.js";
2
+ import type { SessionNameFormat } from "./generate.js";
3
+ export interface SessionNamingContext {
4
+ readonly cwd: string;
5
+ readonly branch: string | null;
6
+ readonly revision: string;
7
+ }
8
+ interface NamingOptions {
9
+ readonly backend: Pick<OwnedUiSessionBackend, "identity" | "session">;
10
+ readonly naming: OwnedUiSessionNamingPort;
11
+ /** Executes the synchronous callback under authoritative repository-context coordination. */
12
+ readonly withContext: <T>(consume: (context: SessionNamingContext) => T, signal: AbortSignal) => Promise<T>;
13
+ readonly enabled: () => boolean;
14
+ readonly format: () => SessionNameFormat;
15
+ }
16
+ /** One background naming chain per eligible prompt/context; owns no timers or engine objects. */
17
+ export declare class SessionNamingController {
18
+ #private;
19
+ constructor(options: NamingOptions);
20
+ settingsChanged(): void;
21
+ dispose(): void;
22
+ }
23
+ export {};