@timurproko/a1 0.2.7-dev.1265 → 0.2.7-dev.1268
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 +11 -7
- package/dist/composition/session-backend.d.ts +2 -1
- package/dist/composition/session-naming.d.ts +6 -0
- package/dist/composition/session-naming.js +22 -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/session-naming/controller.d.ts +21 -0
- package/dist/features/session-naming/controller.js +148 -0
- package/dist/features/session-naming/generate.d.ts +3 -0
- package/dist/features/session-naming/generate.js +44 -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 +12 -0
- package/dist/ui/settings/declarations.js +9 -0
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/tool-image-presentation.md +3 -3
- package/docs/features/session-naming.md +23 -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. Disable **Automatic session name** in Agent settings; 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,8 +18,11 @@ 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";
|
|
24
|
+
/** Production default: without an inspector the registry never registers; loaded lazily. */
|
|
25
|
+
const inspectLiveProcessOnDemand = async (pid) => (await import("../foundation/lifecycle/process-liveness.js")).inspectLiveProcess(pid);
|
|
23
26
|
export async function composeOwnedUiApplication(options = {}) {
|
|
24
27
|
return (await composeOwnedUi(options)).application;
|
|
25
28
|
}
|
|
@@ -28,7 +31,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
28
31
|
const productMode = options.productMode ?? "bare", bare = productMode === "bare";
|
|
29
32
|
const sessionRuntimes = new SessionRuntimeRegistry({
|
|
30
33
|
runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined,
|
|
31
|
-
inspectProcess: options.inspectProcess,
|
|
34
|
+
inspectProcess: options.inspectProcess ?? inspectLiveProcessOnDemand,
|
|
32
35
|
});
|
|
33
36
|
const productPaths = resolveProductPaths();
|
|
34
37
|
// Rationale: the manager reads the engine's settings port lazily, after the session exists.
|
|
@@ -70,6 +73,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
70
73
|
await host.dispose();
|
|
71
74
|
throw error;
|
|
72
75
|
}
|
|
76
|
+
const naming = bindAutomaticSessionNaming(backend, bareSettings, bare);
|
|
73
77
|
const releaseNote = await StartupReleaseNote.load({
|
|
74
78
|
productMode,
|
|
75
79
|
configDir: productPaths.configDir,
|
|
@@ -112,7 +116,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
112
116
|
const suggestionDiagnostics = bareSettings !== null && diagnosticDestination?.trim()
|
|
113
117
|
? await import("../features/prompt-suggestions/diagnostics.js").then(module => new module.SuggestionDiagnosticCapture({ enabled: true, destination: diagnosticDestination })) : null;
|
|
114
118
|
const barePorts = bareSettings === null ? null : bareShellPorts(bareSettings, backend, suggestionDiagnostics);
|
|
115
|
-
const promptHistory = createPromptHistoryConfiguration(settings, backend, bare);
|
|
119
|
+
const promptHistory = await createPromptHistoryConfiguration(settings, backend, bare);
|
|
116
120
|
const clipboardDestination = options.clipboardDiagnosticsPath ?? process.env[PRODUCT_IDENTITY.environment.clipboardDiagnostics];
|
|
117
121
|
const clipboardDiagnostics = bareSettings !== null && clipboardDestination?.trim()
|
|
118
122
|
? new ClipboardDiagnosticCapture(clipboardDestination) : null;
|
|
@@ -121,6 +125,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
121
125
|
// Invariant: one release order for a failed construction and for dispose. The shared spares outlive every
|
|
122
126
|
// shell and stop only after the last one is disposed; the host outlives the application and is released last.
|
|
123
127
|
const releaseShared = async () => {
|
|
128
|
+
naming.dispose();
|
|
124
129
|
clipboardServices?.dispose();
|
|
125
130
|
unsubscribeAccent();
|
|
126
131
|
await sessionRuntimes.dispose();
|
|
@@ -153,10 +158,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
153
158
|
} }),
|
|
154
159
|
...(barePorts === null ? {} : { suggestions: barePorts.suggestions, promptImages: barePorts.promptImages, skills: barePorts.skills }),
|
|
155
160
|
shared: { keybindings: keybindingsHost, ...(clipboardServices === null ? {} : { clipboard: clipboardServices }) },
|
|
156
|
-
...(promptHistory === null ? {} : { history:
|
|
157
|
-
...promptHistory,
|
|
158
|
-
editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
|
|
159
|
-
} }),
|
|
161
|
+
...(promptHistory === null ? {} : { history: promptHistory }),
|
|
160
162
|
};
|
|
161
163
|
// Invariant: one terminal host owns the process terminal; the session presenter reaches it only through
|
|
162
164
|
// the host and paints once attached.
|
|
@@ -196,6 +198,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
196
198
|
flush: () => backend.session.flushEvents(),
|
|
197
199
|
waitUntilStopped: () => presenter.waitUntilStopped(),
|
|
198
200
|
dispose: async () => {
|
|
201
|
+
naming.dispose();
|
|
199
202
|
releaseUpdates.stop();
|
|
200
203
|
try {
|
|
201
204
|
await terminalHost.dispose();
|
|
@@ -244,7 +247,7 @@ function bareShellPorts(settings, generator, diagnostics) {
|
|
|
244
247
|
},
|
|
245
248
|
};
|
|
246
249
|
}
|
|
247
|
-
function createPromptHistoryConfiguration(settings, backend, bare) {
|
|
250
|
+
async function createPromptHistoryConfiguration(settings, backend, bare) {
|
|
248
251
|
const limit = settings?.value("promptHistoryMaxItems") ?? 100;
|
|
249
252
|
const scope = settings?.value("promptHistoryScope") ?? "unique";
|
|
250
253
|
const enabled = settings !== null && bare && settings.value("promptHistoryEnabled");
|
|
@@ -263,6 +266,7 @@ function createPromptHistoryConfiguration(settings, backend, bare) {
|
|
|
263
266
|
};
|
|
264
267
|
return {
|
|
265
268
|
limit, scope,
|
|
269
|
+
editor: await import("../integrations/pi/components/history-editor-loader.js").then(module => module.loadHistoryEditor()),
|
|
266
270
|
...(sessionId === null ? {} : { sessionId }),
|
|
267
271
|
forScope,
|
|
268
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,22 @@
|
|
|
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
|
+
withContext: async (consume, signal) => {
|
|
10
|
+
const sessionId = backend.identity.currentHistorySessionId();
|
|
11
|
+
const sessionFile = backend.identity.currentSessionFile();
|
|
12
|
+
if (!sessionId || !sessionFile)
|
|
13
|
+
throw new Error("session identity unavailable");
|
|
14
|
+
const { withSessionRepositoryObservation } = await import("../foundation/lifecycle/session-repository-context.js");
|
|
15
|
+
return await withSessionRepositoryObservation({ sessionId, sessionFile }, consume, { signal });
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const unsubscribe = settings?.onChange(() => naming.settingsChanged()) ?? (() => { });
|
|
19
|
+
let disposed = false;
|
|
20
|
+
return { dispose() { if (disposed)
|
|
21
|
+
return; disposed = true; naming.dispose(); unsubscribe(); } };
|
|
22
|
+
}
|
|
@@ -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");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OwnedUiSessionBackend, OwnedUiSessionNamingPort } from "../../contracts/owned-ui/index.js";
|
|
2
|
+
export interface SessionNamingContext {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly branch: string | null;
|
|
5
|
+
readonly revision: string;
|
|
6
|
+
}
|
|
7
|
+
interface NamingOptions {
|
|
8
|
+
readonly backend: Pick<OwnedUiSessionBackend, "identity" | "session">;
|
|
9
|
+
readonly naming: OwnedUiSessionNamingPort;
|
|
10
|
+
/** Executes the synchronous callback under authoritative repository-context coordination. */
|
|
11
|
+
readonly withContext: <T>(consume: (context: SessionNamingContext) => T, signal: AbortSignal) => Promise<T>;
|
|
12
|
+
readonly enabled: () => boolean;
|
|
13
|
+
}
|
|
14
|
+
/** One background naming chain per eligible prompt/context; owns no timers or engine objects. */
|
|
15
|
+
export declare class SessionNamingController {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(options: NamingOptions);
|
|
18
|
+
settingsChanged(): void;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
export {};
|