@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.
- package/README.md +2 -0
- package/dist/app/session-shell/session-name-reveal.d.ts +17 -0
- package/dist/app/session-shell/session-name-reveal.js +49 -0
- package/dist/app/session-shell/session-shell-root.d.ts +2 -1
- package/dist/app/session-shell/session-shell-root.js +14 -9
- package/dist/app/session-shell/session-shell.js +6 -0
- package/dist/app/session-shell/session-terminal-presentation.d.ts +7 -0
- package/dist/app/session-shell/session-terminal-presentation.js +11 -1
- package/dist/app/session-shell/shell-host.js +3 -1
- package/dist/composition/owned-ui.js +8 -6
- package/dist/composition/session-backend.d.ts +2 -1
- package/dist/composition/session-naming.d.ts +6 -0
- package/dist/composition/session-naming.js +23 -0
- package/dist/composition/session-runtime-registry.d.ts +5 -1
- package/dist/composition/session-runtime-registry.js +11 -3
- package/dist/contracts/owned-ui/index.d.ts +1 -0
- package/dist/contracts/owned-ui/model.d.ts +6 -0
- package/dist/contracts/owned-ui/session-naming.d.ts +22 -0
- package/dist/contracts/owned-ui/session-naming.js +1 -0
- package/dist/contracts/owned-ui/shell-components.d.ts +5 -0
- package/dist/contracts/owned-ui/validation.js +5 -0
- package/dist/features/owned-ui/settings-app.js +10 -2
- package/dist/features/session-naming/controller.d.ts +23 -0
- package/dist/features/session-naming/controller.js +149 -0
- package/dist/features/session-naming/generate.d.ts +5 -0
- package/dist/features/session-naming/generate.js +54 -0
- package/dist/features/session-naming/index.d.ts +2 -0
- package/dist/features/session-naming/index.js +1 -0
- package/dist/foundation/lifecycle/index.d.ts +2 -2
- package/dist/foundation/lifecycle/index.js +1 -1
- package/dist/foundation/lifecycle/session-repository-context.d.ts +12 -0
- package/dist/foundation/lifecycle/session-repository-context.js +59 -3
- package/dist/integrations/pi/components/shell-footer-status.js +1 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.js +3 -6
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +5 -0
- package/dist/integrations/pi/components/upstream/components/session-footer.js +44 -7
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +4 -8
- package/dist/integrations/pi/engine/adapter.d.ts +3 -2
- package/dist/integrations/pi/engine/adapter.js +37 -28
- package/dist/integrations/pi/engine/repository-metadata.d.ts +2 -0
- package/dist/integrations/pi/engine/repository-metadata.js +12 -16
- package/dist/integrations/pi/engine/session-events.d.ts +1 -0
- package/dist/integrations/pi/engine/session-events.js +2 -0
- package/dist/integrations/pi/engine/session-naming.d.ts +24 -0
- package/dist/integrations/pi/engine/session-naming.js +88 -0
- package/dist/integrations/pi/engine/view-state.d.ts +5 -1
- package/dist/integrations/pi/engine/view-state.js +3 -1
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/session-name-frame.d.ts +17 -0
- package/dist/ui/components/session-name-frame.js +24 -0
- package/dist/ui/settings/declarations.d.ts +24 -0
- package/dist/ui/settings/declarations.js +18 -0
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/tool-image-presentation.md +3 -3
- package/docs/features/session-naming.md +37 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) {
|
|
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<
|
|
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
|
-
|
|
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
|
-
/**
|
|
824
|
-
function displaySettingValue(
|
|
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 {};
|