@timurproko/a1 0.2.7-dev.1276 → 0.2.7-dev.1285
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 +5 -1
- package/dist/app/session-shell/session-shell-root.d.ts +4 -68
- package/dist/app/session-shell/session-shell.js +5 -11
- package/dist/app/session-shell/shell-host.js +2 -2
- package/dist/app/session-shell/shell-options.d.ts +65 -0
- package/dist/app/session-shell/shell-options.js +1 -0
- package/dist/app/session-shell/workflow-router.d.ts +3 -1
- package/dist/app/session-shell/workflow-router.js +12 -0
- package/dist/cli/dispatch.js +27 -1
- package/dist/cli/packages.js +18 -2
- package/dist/cli/session-context.js +6 -2
- package/dist/composition/owned-ui.js +20 -8
- package/dist/composition/session-naming.js +1 -1
- package/dist/composition/session-runtime-registry.d.ts +2 -0
- package/dist/composition/session-runtime-registry.js +49 -9
- package/dist/contracts/agent-engine/package-ports.d.ts +10 -14
- package/dist/contracts/agent-engine/package-ports.js +5 -9
- package/dist/contracts/owned-ui/session-workflows.d.ts +6 -0
- package/dist/contracts/presentation/index.d.ts +1 -0
- package/dist/contracts/presentation/progress-palette.generated.d.ts +1 -1
- package/dist/contracts/presentation/progress-palette.generated.js +1 -1
- package/dist/contracts/presentation/progress-spinner.d.ts +7 -0
- package/dist/contracts/presentation/progress-spinner.js +1 -0
- package/dist/features/launch/session-context.d.ts +8 -0
- package/dist/features/launch/session-context.js +19 -10
- package/dist/features/owned-ui/reference-screen-app.js +9 -5
- package/dist/features/owned-ui/session-fork-prompt.d.ts +2 -1
- package/dist/features/owned-ui/session-fork-prompt.js +11 -5
- package/dist/features/owned-ui/settings-app.d.ts +1 -1
- package/dist/features/owned-ui/settings-app.js +14 -7
- package/dist/features/owned-ui/settings-spinner-preview.d.ts +9 -0
- package/dist/features/owned-ui/settings-spinner-preview.js +29 -0
- package/dist/features/session-naming/context.d.ts +17 -0
- package/dist/features/session-naming/context.js +39 -0
- package/dist/features/session-naming/controller.d.ts +1 -5
- package/dist/features/session-naming/controller.js +7 -4
- package/dist/features/session-naming/generate.d.ts +2 -1
- package/dist/features/session-naming/generate.js +14 -10
- package/dist/features/session-naming/index.d.ts +1 -1
- package/dist/foundation/lifecycle/index.d.ts +3 -1
- package/dist/foundation/lifecycle/index.js +3 -1
- package/dist/foundation/lifecycle/session-claim-lock.d.ts +2 -2
- package/dist/foundation/lifecycle/session-claim-lock.js +36 -10
- package/dist/foundation/lifecycle/session-context-retry.d.ts +2 -0
- package/dist/foundation/lifecycle/session-context-retry.js +17 -0
- package/dist/foundation/lifecycle/session-repository-context.d.ts +7 -1
- package/dist/foundation/lifecycle/session-repository-context.js +105 -39
- package/dist/foundation/lifecycle/session-worktree-entry.d.ts +3 -0
- package/dist/foundation/lifecycle/session-worktree-entry.js +87 -0
- package/dist/foundation/release/automatic-update.js +2 -2
- package/dist/foundation/release/bootstrap.js +19 -3
- package/dist/foundation/release/release-gc.js +20 -5
- package/dist/foundation/release/release-state.d.ts +12 -3
- package/dist/foundation/release/release-state.js +55 -24
- package/dist/foundation/release/release-use.d.ts +10 -0
- package/dist/foundation/release/release-use.js +70 -0
- package/dist/foundation/release/update-activation.js +2 -2
- package/dist/foundation/release/update-recovery.js +2 -2
- package/dist/foundation/release/update.js +1 -1
- package/dist/foundation/release/warmup.js +8 -9
- package/dist/foundation/session-writer-lock/index.d.ts +2 -2
- package/dist/foundation/session-writer-lock/index.js +82 -12
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/fatal-diagnostic.d.ts +2 -0
- package/dist/foundation/terminal-cleanup/fatal-diagnostic.js +59 -0
- package/dist/foundation/terminal-cleanup/fatal-exit.js +1 -29
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/share-operation-dialog.d.ts +2 -1
- package/dist/integrations/pi/components/share-operation-dialog.js +13 -5
- package/dist/integrations/pi/components/shell-footer-status.d.ts +2 -1
- package/dist/integrations/pi/components/shell-footer-status.js +15 -8
- package/dist/integrations/pi/components/shell-session-info-reference.js +11 -8
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/auth-url.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/auth-url.js +1 -1
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/login-dialog.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/oauth-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/oauth-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +14 -8
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +3 -1
- package/dist/integrations/pi/engine/adapter.js +24 -6
- package/dist/integrations/pi/engine/changelog.js +5 -2
- package/dist/integrations/pi/engine/delivery-retention.d.ts +7 -0
- package/dist/integrations/pi/engine/delivery-retention.js +66 -0
- package/dist/integrations/pi/engine/event-delivery.d.ts +17 -1
- package/dist/integrations/pi/engine/event-delivery.js +25 -3
- package/dist/integrations/pi/engine/extension-ui-binding.d.ts +2 -1
- package/dist/integrations/pi/engine/extension-ui-binding.js +45 -0
- package/dist/integrations/pi/engine/host-ports.d.ts +2 -0
- package/dist/integrations/pi/engine/host.d.ts +3 -0
- package/dist/integrations/pi/engine/host.js +2 -0
- package/dist/integrations/pi/engine/package-integration.js +19 -4
- package/dist/integrations/pi/engine/pending-delivery.d.ts +4 -1
- package/dist/integrations/pi/engine/pending-delivery.js +51 -18
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.d.ts +8 -0
- package/dist/integrations/pi/engine/runtime-integration.js +58 -9
- package/dist/integrations/pi/engine/session-cwd-recovery.d.ts +11 -0
- package/dist/integrations/pi/engine/session-cwd-recovery.js +18 -0
- package/dist/integrations/pi/engine/session-runtime.d.ts +3 -0
- package/dist/integrations/pi/engine/session-runtime.js +2 -0
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -2
- package/dist/integrations/pi/engine/session-selection.js +25 -5
- package/dist/integrations/pi/engine/transcript-image-assets.d.ts +3 -0
- package/dist/integrations/pi/engine/transcript-image-assets.js +65 -14
- package/dist/integrations/pi/engine/view-state.d.ts +14 -0
- package/dist/integrations/pi/engine/view-state.js +17 -0
- package/dist/integrations/pi/engine/workflow-runner.d.ts +2 -0
- package/dist/integrations/pi/engine/workflow-runner.js +47 -26
- package/dist/integrations/pi/engine/worktree-tools.d.ts +4 -0
- package/dist/integrations/pi/engine/worktree-tools.js +26 -0
- package/dist/integrations/pi/session-presenters/components.d.ts +2 -1
- package/dist/integrations/pi/session-presenters/components.js +5 -3
- package/dist/integrations/pi/session-presenters/presenters.d.ts +3 -1
- package/dist/integrations/pi/session-presenters/presenters.js +1 -1
- package/dist/integrations/pi/startup-public.d.ts +1 -1
- package/dist/integrations/pi/startup-public.js +16 -20
- package/dist/integrations/pi/startup-public.manifest.json +29 -29
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/product-identity.json +1 -1
- package/dist/runtime-payload-inventory.json +5027 -5027
- package/dist/ui/components/index.d.ts +1 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/progress-status.d.ts +7 -0
- package/dist/ui/components/progress-status.js +21 -0
- package/dist/ui/components/value-menu.d.ts +1 -1
- package/dist/ui/components/value-menu.js +4 -2
- package/dist/ui/settings/declarations.d.ts +16 -4
- package/dist/ui/settings/declarations.js +13 -4
- package/docs/architecture/internal-naming.md +1 -1
- package/docs/architecture/project-structure.md +7 -11
- package/docs/features/session-exports.md +1 -1
- package/docs/features/session-naming.md +8 -6
- package/docs/features/worktrees.md +38 -0
- package/docs/local-worktree-cleanup.md +27 -4
- package/docs/openspec-archive-automation.md +30 -4
- package/docs/worktree-link-recovery.md +2 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -73,7 +73,11 @@ a1 --session <id>
|
|
|
73
73
|
|
|
74
74
|
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.
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
If a saved session's folder was moved or deleted, recovery offers **Continue**, **Fork**, or **Cancel**. Continue uses the current folder for this run and asks again on the next resume. Fork keeps the original and creates a new session ID in the current folder, so later resumes no longer ask. This also applies to session import and extension-triggered switching.
|
|
77
|
+
|
|
78
|
+
Bare A1 automatically names unnamed sessions from the current task and relevant discussion, using up to three descriptive words and 32 characters, and regenerates 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; narrow footers reduce stats before shortening the name. Agent settings offers **Session name format** with Sentence case (default, first choice), Kebab case, and Title Case for future generated names, or **Automatic session name** to disable naming. Valid saved formats are preserved; see [session names](docs/features/session-naming.md) for details.
|
|
79
|
+
|
|
80
|
+
Inside an A1 agent session, `a1 worktree enter <branch>` creates or resumes a task checkout and attaches it. `a1 worktree attach <path>` attaches an existing checkout; `a1 worktree detach` releases it without deleting work. A1 handles coordination and ordinary recovery internally; successful attachment needs no second inventory check. See [worktree commands](docs/features/worktrees.md).
|
|
77
81
|
|
|
78
82
|
Show all commands:
|
|
79
83
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { OwnedUiShellComponents, OwnedUiShellHotkeysPresentation, OwnedUiShellClipboardContent, OwnedUiShellComponentPort, OwnedUiShellEditorPort, OwnedUiShellHeaderOptions, OwnedUiShellHeaderPort, OwnedUiShellHistoryEditor, OwnedUiShellImageAssetResolver, OwnedUiShellKeybindingsHost, OwnedUiShellLoadedResourcesPort, OwnedUiShellViewComponentPort } from "../../contracts/owned-ui/index.js";
|
|
2
|
-
import type { TerminalComponentPort,
|
|
2
|
+
import type { TerminalComponentPort, TerminalInputSurfaceKind, TerminalLayoutNode } from "../../contracts/presentation/index.js";
|
|
3
3
|
import type { ShellPresentationPorts, ThemeSource } from "../../contracts/presentation/index.js";
|
|
4
|
-
import type {
|
|
5
|
-
import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
|
|
4
|
+
import type { OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionPresenters, OwnedUiSessionViewModel, OwnedUiTranscriptRendererPort, OwnedUiViewportSettings, SuggestionDecision, SuggestionDiagnosticObserver } from "../../contracts/owned-ui/index.js";
|
|
6
5
|
import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
|
|
7
6
|
import { PromptChipStore, type PreparedPrompt } from "./prompt-chips.js";
|
|
8
7
|
import type { PastePreparationClientOptions } from "./paste-preparation-client.js";
|
|
@@ -15,70 +14,8 @@ import type { PasteEvent, PasteSource } from "./paste-protocol.js";
|
|
|
15
14
|
import { DockChrome } from "./dock-chrome.js";
|
|
16
15
|
import { WorkflowOutput } from "./workflow-output.js";
|
|
17
16
|
import { TranscriptDocument } from "./transcript-document.js";
|
|
18
|
-
import type {
|
|
19
|
-
type
|
|
20
|
-
type OwnedUiStartupOptions = OwnedUiShellHeaderOptions;
|
|
21
|
-
/** The engine session the shell presents and the composition's decisions about it. */
|
|
22
|
-
export interface OwnedUiShellEngineOptions {
|
|
23
|
-
readonly backend: OwnedUiSessionBackend;
|
|
24
|
-
readonly cwd: string;
|
|
25
|
-
/**
|
|
26
|
-
* Declared A1-owned routes. A route this host claims resolves to its app;
|
|
27
|
-
* every other route continues to the pinned workflow table unchanged.
|
|
28
|
-
*/
|
|
29
|
-
readonly routeHost?: UiRouteHost;
|
|
30
|
-
/** One product-owned route presented after the first usable frame and any startup modal. */
|
|
31
|
-
readonly startupRoute?: {
|
|
32
|
-
readonly route: string;
|
|
33
|
-
readonly input?: UiRouteInput;
|
|
34
|
-
readonly onClosed?: () => void | Promise<void>;
|
|
35
|
-
};
|
|
36
|
-
/** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
|
|
37
|
-
readonly sessionLayout?: "pinned" | "custom-viewport";
|
|
38
|
-
}
|
|
39
|
-
/** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
|
|
40
|
-
export interface OwnedUiShellPresentationOptions {
|
|
41
|
-
readonly terminal?: OwnedUiTerminalPort;
|
|
42
|
-
readonly startup?: OwnedUiStartupOptions;
|
|
43
|
-
/** Live profile-local settings, supplied only to the bare-A1 composition. */
|
|
44
|
-
readonly viewportSettings?: OwnedUiViewportSettingsPort;
|
|
45
|
-
/** Live fullscreen canvas choice, supplied only to the bare-A1 composition. */
|
|
46
|
-
readonly backgroundSettings?: OwnedUiBackgroundSettingsPort;
|
|
47
|
-
/** Deterministic scheduling seam for presentation-cadence tests. */
|
|
48
|
-
readonly stream?: {
|
|
49
|
-
readonly intervalMs?: number;
|
|
50
|
-
readonly scheduler?: StreamPresentationScheduler;
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Minimum time the reload box stays visible so a fast reload still reads as one.
|
|
54
|
-
* Production uses the default hold; tests inject `now`/`sleep` for determinism.
|
|
55
|
-
*/
|
|
56
|
-
readonly reload?: {
|
|
57
|
-
readonly minVisibleMs?: number;
|
|
58
|
-
readonly now?: () => number;
|
|
59
|
-
readonly sleep?: (ms: number) => Promise<void>;
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Quit outro settings and seams, supplied only to the bare-A1 composition.
|
|
63
|
-
* `interactive` states whether the terminal can show the animation; production
|
|
64
|
-
* passes stdout's TTY state, tests opt in explicitly.
|
|
65
|
-
*/
|
|
66
|
-
readonly quitOutro?: OwnedUiQuitOutroSettingsPort & {
|
|
67
|
-
readonly interactive: boolean;
|
|
68
|
-
readonly now?: () => number;
|
|
69
|
-
readonly sleep?: (ms: number) => Promise<void>;
|
|
70
|
-
readonly seed?: number;
|
|
71
|
-
};
|
|
72
|
-
readonly input?: {
|
|
73
|
-
readonly scheduler?: TerminalInputCoordinationScheduler;
|
|
74
|
-
readonly onEvent?: (event: TerminalInputDiagnosticsEvent) => void;
|
|
75
|
-
readonly now?: () => number;
|
|
76
|
-
/** Evidence-only baseline seam; production leaves coordination enabled. */
|
|
77
|
-
readonly coordination?: boolean;
|
|
78
|
-
/** Evidence-only baseline seam; production leaves dock reuse enabled. */
|
|
79
|
-
readonly viewportReuse?: boolean;
|
|
80
|
-
};
|
|
81
|
-
}
|
|
17
|
+
import type { OwnedUiShellEngineOptions, OwnedUiShellPresentationOptions } from "./shell-options.js";
|
|
18
|
+
export type { OwnedUiShellEngineOptions, OwnedUiShellPresentationOptions } from "./shell-options.js";
|
|
82
19
|
/** Persistent prompt history: its store, its bound, the editor that recalls it, and the image sidecar. */
|
|
83
20
|
export interface OwnedUiShellHistoryOptions {
|
|
84
21
|
readonly store: import("../../contracts/owned-ui/index.js").PromptHistoryPort;
|
|
@@ -286,4 +223,3 @@ export type PinnedLayoutParts = Readonly<Record<PinnedLayoutPart, TerminalCompon
|
|
|
286
223
|
* so its single `transcript` scroll view is shared by whichever session the parts currently resolve to.
|
|
287
224
|
*/
|
|
288
225
|
export declare function pinnedLayoutRoot(parts: PinnedLayoutParts, theme: ThemeSource): TerminalLayoutNode;
|
|
289
|
-
export {};
|
|
@@ -7,7 +7,7 @@ import { LoginInteraction } from "./login-interaction.js";
|
|
|
7
7
|
import { PromptSubmission } from "./prompt-submission.js";
|
|
8
8
|
import { SessionConcerns } from "./session-concern.js";
|
|
9
9
|
import { composeExitText, SessionStopLatch } from "./session-lifecycle.js";
|
|
10
|
-
import { openWorkflowSelector } from "./workflow-router.js";
|
|
10
|
+
import { openWorkflowSelector, selectSessionCwdRecovery } from "./workflow-router.js";
|
|
11
11
|
import { SessionSelectors } from "./session-selectors.js";
|
|
12
12
|
import { bindSessionSettings, bindShellSettings } from "./settings-bindings.js";
|
|
13
13
|
import { promptSuggestionSurface, suggestionPresentationBlockReason, } from "./suggestion-surface.js";
|
|
@@ -606,18 +606,12 @@ export class OwnedUiSessionPresenter {
|
|
|
606
606
|
this.selectors.showModelSelector((result.detail ?? request.argument.trim()) || undefined);
|
|
607
607
|
return { outcome: "completed", diagnostic: null };
|
|
608
608
|
}
|
|
609
|
-
if (result.outcome === "requires-
|
|
610
|
-
|
|
611
|
-
return this.runWorkflow({ ...request, confirmed: confirmed === true });
|
|
609
|
+
if (result.outcome === "requires-selection" && result.cwdRecovery !== undefined && (request.command === "resume" || request.command === "import")) {
|
|
610
|
+
return this.runWorkflow(await selectSessionCwdRecovery(request, result, this.#extensionBridge.context));
|
|
612
611
|
}
|
|
613
612
|
if (result.outcome === "requires-confirmation" && request.command === "import") {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
return this.runWorkflow({
|
|
617
|
-
...request,
|
|
618
|
-
confirmed: confirmed === true,
|
|
619
|
-
...(confirmed === true && result.detail ? { cwdOverride: result.detail } : {}),
|
|
620
|
-
});
|
|
613
|
+
const confirmed = await this.#extensionBridge.context.confirm("Import session", result.message);
|
|
614
|
+
return this.runWorkflow({ ...request, confirmed: confirmed === true });
|
|
621
615
|
}
|
|
622
616
|
if (result.outcome === "requires-selection" || result.outcome === "requires-confirmation") {
|
|
623
617
|
this.root.workflow.appendResult({ command: request.command, outcome: "failed", message: `Owned controller missing for ${request.command}` });
|
|
@@ -270,8 +270,8 @@ export class OwnedUiTerminalHost {
|
|
|
270
270
|
requestHyperlinkCleanup: rows => { if (attached())
|
|
271
271
|
this.#damageTerminal?.requestHyperlinkCleanup(rows); },
|
|
272
272
|
openRoute: (surface, callbacks) => this.#openRoute(presenter, surface, callbacks),
|
|
273
|
-
// Invariant: an
|
|
274
|
-
beginPresentationHold: () => attached() ? runtime.beginPresentationHold() : () => { },
|
|
273
|
+
// Invariant: only an attached presenter on a running runtime can hold frames.
|
|
274
|
+
beginPresentationHold: () => attached() && runtime.active ? runtime.beginPresentationHold() : () => { },
|
|
275
275
|
switchMode: mode => runtime.switchMode(mode),
|
|
276
276
|
setTitle: title => { if (attached())
|
|
277
277
|
runtime.setTitle(title); },
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { OwnedUiBackgroundSettingsPort, OwnedUiSessionBackend, OwnedUiShellHeaderOptions, OwnedUiViewportSettingsPort, OwnedUiQuitOutroSettingsPort } from "../../contracts/owned-ui/index.js";
|
|
2
|
+
import type { TerminalInputCoordinationScheduler, TerminalInputDiagnosticsEvent, TerminalPort } from "../../contracts/presentation/index.js";
|
|
3
|
+
import type { StreamPresentationScheduler } from "./stream-presentation-coalescer.js";
|
|
4
|
+
import type { UiRouteHost, UiRouteInput } from "../../ui/apps/index.js";
|
|
5
|
+
/** The engine session the shell presents and the composition's decisions about it. */
|
|
6
|
+
export interface OwnedUiShellEngineOptions {
|
|
7
|
+
readonly backend: OwnedUiSessionBackend;
|
|
8
|
+
readonly cwd: string;
|
|
9
|
+
/**
|
|
10
|
+
* Declared A1-owned routes. A route this host claims resolves to its app;
|
|
11
|
+
* every other route continues to the pinned workflow table unchanged.
|
|
12
|
+
*/
|
|
13
|
+
readonly routeHost?: UiRouteHost;
|
|
14
|
+
/** One product-owned route presented after the first usable frame and any startup modal. */
|
|
15
|
+
readonly startupRoute?: {
|
|
16
|
+
readonly route: string;
|
|
17
|
+
readonly input?: UiRouteInput;
|
|
18
|
+
readonly onClosed?: () => void | Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
/** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
|
|
21
|
+
readonly sessionLayout?: "pinned" | "custom-viewport";
|
|
22
|
+
}
|
|
23
|
+
/** Terminal, startup, viewport, and presentation-cadence seams; production leaves most at their defaults. */
|
|
24
|
+
export interface OwnedUiShellPresentationOptions {
|
|
25
|
+
readonly terminal?: TerminalPort;
|
|
26
|
+
readonly startup?: OwnedUiShellHeaderOptions;
|
|
27
|
+
/** Live profile-local settings, supplied only to the bare-A1 composition. */
|
|
28
|
+
readonly viewportSettings?: OwnedUiViewportSettingsPort;
|
|
29
|
+
/** Live fullscreen canvas choice, supplied only to the bare-A1 composition. */
|
|
30
|
+
readonly backgroundSettings?: OwnedUiBackgroundSettingsPort;
|
|
31
|
+
/** Deterministic scheduling seam for presentation-cadence tests. */
|
|
32
|
+
readonly stream?: {
|
|
33
|
+
readonly intervalMs?: number;
|
|
34
|
+
readonly scheduler?: StreamPresentationScheduler;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Minimum time the reload box stays visible so a fast reload still reads as one.
|
|
38
|
+
* Production uses the default hold; tests inject `now`/`sleep` for determinism.
|
|
39
|
+
*/
|
|
40
|
+
readonly reload?: {
|
|
41
|
+
readonly minVisibleMs?: number;
|
|
42
|
+
readonly now?: () => number;
|
|
43
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Quit outro settings and seams, supplied only to the bare-A1 composition.
|
|
47
|
+
* `interactive` states whether the terminal can show the animation; production
|
|
48
|
+
* passes stdout's TTY state, tests opt in explicitly.
|
|
49
|
+
*/
|
|
50
|
+
readonly quitOutro?: OwnedUiQuitOutroSettingsPort & {
|
|
51
|
+
readonly interactive: boolean;
|
|
52
|
+
readonly now?: () => number;
|
|
53
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
54
|
+
readonly seed?: number;
|
|
55
|
+
};
|
|
56
|
+
readonly input?: {
|
|
57
|
+
readonly scheduler?: TerminalInputCoordinationScheduler;
|
|
58
|
+
readonly onEvent?: (event: TerminalInputDiagnosticsEvent) => void;
|
|
59
|
+
readonly now?: () => number;
|
|
60
|
+
/** Evidence-only baseline seam; production leaves coordination enabled. */
|
|
61
|
+
readonly coordination?: boolean;
|
|
62
|
+
/** Evidence-only baseline seam; production leaves dock reuse enabled. */
|
|
63
|
+
readonly viewportReuse?: boolean;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OwnedUiCommandResult, OwnedUiWorkflowRequest } from "../../contracts/owned-ui/index.js";
|
|
1
|
+
import type { OwnedUiCommandResult, OwnedUiExtensionUiPort, OwnedUiWorkflowRequest, OwnedUiWorkflowResult } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
/** The owned selectors a workflow can open in place of asking the engine to choose. */
|
|
3
3
|
export interface WorkflowSelectors {
|
|
4
4
|
showScopedModelsSelector(): void;
|
|
@@ -14,6 +14,8 @@ export interface WorkflowSelectors {
|
|
|
14
14
|
showLogoutSelector(): Promise<void>;
|
|
15
15
|
showTreeSelector(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
/** Maps the pinned recovery labels back to explicit choices, including Escape as cancellation. */
|
|
18
|
+
export declare function selectSessionCwdRecovery(request: OwnedUiWorkflowRequest, result: OwnedUiWorkflowResult, ui: Pick<OwnedUiExtensionUiPort, "select">): Promise<OwnedUiWorkflowRequest>;
|
|
17
19
|
/**
|
|
18
20
|
* Opens the owned selector a workflow request asks for when the user has not chosen yet. Returns
|
|
19
21
|
* the command result when a selector handled the request, settling once an asynchronous selector
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/** Maps the pinned recovery labels back to explicit choices, including Escape as cancellation. */
|
|
2
|
+
export async function selectSessionCwdRecovery(request, result, ui) {
|
|
3
|
+
const options = result.options ?? [];
|
|
4
|
+
const selected = await ui.select(result.message, options.map(option => option.label));
|
|
5
|
+
const choice = options.find(option => option.label === selected)?.id;
|
|
6
|
+
const cwdRecoveryChoice = choice === "continue" || choice === "fork" ? choice : "cancel";
|
|
7
|
+
return {
|
|
8
|
+
...request,
|
|
9
|
+
cwdRecoveryChoice,
|
|
10
|
+
...(cwdRecoveryChoice === "continue" ? { cwdOverride: result.cwdRecovery.fallbackCwd } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
1
13
|
const unchosen = (request) => request.selection === undefined && request.confirmed === undefined;
|
|
2
14
|
const unchosenBare = (request) => unchosen(request) && request.argument.trim().length === 0;
|
|
3
15
|
// Invariant: the routes are checked in this order, matching the order the workflows were introduced.
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -20,6 +20,10 @@ export function cliUsage(capabilities) {
|
|
|
20
20
|
"uninstall [--yes] [--purge]",
|
|
21
21
|
"uninstall <source>",
|
|
22
22
|
"list",
|
|
23
|
+
"worktree enter <branch> [--base <branch>]",
|
|
24
|
+
"worktree attach <path>",
|
|
25
|
+
"worktree detach",
|
|
26
|
+
"worktree list",
|
|
23
27
|
"session worktrees",
|
|
24
28
|
"session link-worktree <path>",
|
|
25
29
|
"session unlink-worktree",
|
|
@@ -51,7 +55,13 @@ export function cliHelp(capabilities) {
|
|
|
51
55
|
` ${command} uninstall <source>`,
|
|
52
56
|
` ${command} list`,
|
|
53
57
|
"",
|
|
54
|
-
"
|
|
58
|
+
"Worktrees (successful enter/attach is authoritative; detach preserves files):",
|
|
59
|
+
` ${command} worktree enter <branch> [--base <branch>]`,
|
|
60
|
+
` ${command} worktree attach <path>`,
|
|
61
|
+
` ${command} worktree detach`,
|
|
62
|
+
` ${command} worktree list`,
|
|
63
|
+
"",
|
|
64
|
+
"Session context (compatibility aliases):",
|
|
55
65
|
` ${command} session worktrees`,
|
|
56
66
|
` ${command} session link-worktree <path>`,
|
|
57
67
|
` ${command} session unlink-worktree`,
|
|
@@ -123,6 +133,8 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
123
133
|
}
|
|
124
134
|
if (command === "update")
|
|
125
135
|
return parseUpdate(rest, capabilities.runningChannel ?? "stable");
|
|
136
|
+
if (command === "worktree")
|
|
137
|
+
return parseWorktree(rest);
|
|
126
138
|
if (command === "session")
|
|
127
139
|
return parseSessionContext(rest);
|
|
128
140
|
if (command?.startsWith("update:"))
|
|
@@ -131,6 +143,20 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
131
143
|
// explicit, and a typo must never start an interactive or maintenance path.
|
|
132
144
|
return { kind: "noop" };
|
|
133
145
|
}
|
|
146
|
+
function parseWorktree(rest) {
|
|
147
|
+
const [action, value, flag, base, extra] = rest;
|
|
148
|
+
if (action === "enter" && value !== undefined && !value.startsWith("-")
|
|
149
|
+
&& (flag === undefined || (flag === "--base" && base !== undefined && !base.startsWith("-"))) && extra === undefined) {
|
|
150
|
+
return { kind: "session-context", request: { action: "enter-worktree", branch: value, ...(base === undefined ? {} : { base }) } };
|
|
151
|
+
}
|
|
152
|
+
if (action === "attach" && value !== undefined && flag === undefined) {
|
|
153
|
+
return { kind: "session-context", request: { action: "link-worktree", path: value } };
|
|
154
|
+
}
|
|
155
|
+
if ((action === "detach" || action === "list") && value === undefined) {
|
|
156
|
+
return { kind: "session-context", request: { action: action === "detach" ? "unlink-worktree" : "worktrees" } };
|
|
157
|
+
}
|
|
158
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("worktree accepts enter <branch> [--base <branch>], attach <path>, detach, or list.") };
|
|
159
|
+
}
|
|
134
160
|
function parseSessionContext(rest) {
|
|
135
161
|
const [action, value, extra] = rest;
|
|
136
162
|
if (action === "link-worktree") {
|
package/dist/cli/packages.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function runPackageCommand(request, environment) {
|
|
|
21
21
|
const port = environment.createPort({
|
|
22
22
|
profileRoot,
|
|
23
23
|
cwd,
|
|
24
|
-
onProgress: progress => stdout(style.dim(`${progress.message}\n`)),
|
|
24
|
+
onProgress: progress => stdout((progress.severity === "warning" ? style.yellow : style.dim)(`${progress.message}\n`)),
|
|
25
25
|
onDiagnostic: diagnostic => {
|
|
26
26
|
stderr(`${style.yellow(diagnostic.message)}\n`);
|
|
27
27
|
if (diagnostic.detail)
|
|
@@ -60,11 +60,27 @@ export function renderPackageOutcome(outcome, style = chalk) {
|
|
|
60
60
|
case "remove":
|
|
61
61
|
return `${style.green(`Removed ${outcome.source}`)}\n`;
|
|
62
62
|
case "update":
|
|
63
|
-
return
|
|
63
|
+
return renderPackageUpdate(outcome, style);
|
|
64
64
|
case "list":
|
|
65
65
|
return renderPackageList(outcome, style);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
function renderPackageUpdate(outcome, style) {
|
|
69
|
+
const summary = outcome.updateSummary;
|
|
70
|
+
if (summary?.offline)
|
|
71
|
+
return `${style.yellow("Skipped package updates (offline mode)")}\n`;
|
|
72
|
+
const changes = summary?.changes ?? [];
|
|
73
|
+
if (changes.length > 0) {
|
|
74
|
+
const heading = `Updated ${changes.length} package${changes.length === 1 ? "" : "s"}:`;
|
|
75
|
+
return `${style.green(heading)}\n${changes.map(change => ` ${change}\n`).join("")}`;
|
|
76
|
+
}
|
|
77
|
+
if (summary?.warningCount) {
|
|
78
|
+
return `${style.yellow(outcome.source
|
|
79
|
+
? `${outcome.source} was not updated, see the warnings above`
|
|
80
|
+
: "No packages were updated, see the warnings above")}\n`;
|
|
81
|
+
}
|
|
82
|
+
return `${style.green(outcome.source ? `${outcome.source} is already up to date` : "Packages are already up to date")}\n`;
|
|
83
|
+
}
|
|
68
84
|
function renderPackageList(outcome, style) {
|
|
69
85
|
if (outcome.packages.length === 0)
|
|
70
86
|
return `${style.dim("No packages installed.")}\n`;
|
|
@@ -16,7 +16,7 @@ export async function runSessionContextCommand(request, options = {}) {
|
|
|
16
16
|
if (outcome.kind === "unlinked")
|
|
17
17
|
stdout("Session worktree context cleared.\n");
|
|
18
18
|
if (outcome.kind === "linked")
|
|
19
|
-
stdout(`Session worktree context linked: ${outcome.cwd}\n`);
|
|
19
|
+
stdout(`Session worktree context linked: ${outcome.cwd} (${outcome.branch})\n`);
|
|
20
20
|
if (outcome.kind === "worktrees") {
|
|
21
21
|
for (const entry of outcome.entries) {
|
|
22
22
|
// Compatibility: the primary agent is the only agent of a single-session runtime, so it stays unnamed.
|
|
@@ -44,7 +44,7 @@ function readIdentity(environment) {
|
|
|
44
44
|
/** Known coordination failures must not expose internal paths or another session's identity. */
|
|
45
45
|
function recoveryDetail(code) {
|
|
46
46
|
const details = {
|
|
47
|
-
"worktree-claim-busy": "
|
|
47
|
+
"worktree-claim-busy": "coordination did not settle within the operation budget; no ownership was changed; retry this link shortly if the system remains busy",
|
|
48
48
|
"worktree-legacy-recovery-required": "an older-format worktree lock requires safe recovery; stop all older A1 processes, move only session-worktree-claims/mutation.lock to a backup while stopped, then start the updated A1; keep all sessions and worktree claims",
|
|
49
49
|
"worktree-coordination-unavailable": "worktree coordination is unavailable; repair or update this A1 installation, then retry",
|
|
50
50
|
"worktree-coordination-io": "worktree coordination could not access its files; check A1 data-directory permissions and filesystem availability, then retry",
|
|
@@ -54,6 +54,10 @@ function recoveryDetail(code) {
|
|
|
54
54
|
"session-runtime-stopped": "this shell belongs to a stopped runtime; resume in a running A1 session before linking",
|
|
55
55
|
"worktree-active-in-another-session": "another live agent owns this worktree; use a different worktree or wait for its owner to release it",
|
|
56
56
|
"worktree-owner-unverifiable": "worktree ownership cannot be verified; retain the claim and use another worktree rather than forcing recovery",
|
|
57
|
+
"worktree-primary-checkout": "attach a task worktree, not the primary checkout; use worktree enter to create one",
|
|
58
|
+
"worktree-git-locked": "Git has locked this worktree; retain it and ask its owner to release the lock",
|
|
59
|
+
"worktree-foreign-repository": "the selected worktree belongs to a different repository; the previous attachment was preserved",
|
|
60
|
+
"worktree-context-changed": "the attachment changed during the operation; no ownership was overridden",
|
|
57
61
|
};
|
|
58
62
|
return code === null ? undefined : details[code];
|
|
59
63
|
}
|
|
@@ -17,22 +17,23 @@ import { createPiTerminalBridge } from "../integrations/pi/tui-runtime/presentat
|
|
|
17
17
|
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
|
-
import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
|
|
21
20
|
import { bindAutomaticSessionNaming } from "./session-naming.js";
|
|
22
21
|
import { deferStartupReleaseUpdateObservation, readPackageVersion, StartupReleaseNote } from "./startup-route.js";
|
|
23
22
|
import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
|
|
24
23
|
/** Production default: without an inspector the registry never registers; loaded lazily. */
|
|
25
24
|
const inspectLiveProcessOnDemand = async (pid) => (await import("../foundation/lifecycle/process-liveness.js")).inspectLiveProcess(pid);
|
|
25
|
+
/** Load coordination only when composing a session, not while evaluating the startup facade. */
|
|
26
|
+
async function createSessionRuntimes(bare, inspectProcess) {
|
|
27
|
+
const { SessionRuntimeRegistry } = await import("./session-runtime-registry.js");
|
|
28
|
+
return new SessionRuntimeRegistry({ runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined, inspectProcess });
|
|
29
|
+
}
|
|
26
30
|
export async function composeOwnedUiApplication(options = {}) {
|
|
27
31
|
return (await composeOwnedUi(options)).application;
|
|
28
32
|
}
|
|
29
33
|
export async function composeOwnedUi(options = {}) {
|
|
30
34
|
const cwd = options.cwd ?? process.cwd();
|
|
31
35
|
const productMode = options.productMode ?? "bare", bare = productMode === "bare";
|
|
32
|
-
const sessionRuntimes =
|
|
33
|
-
runtimeId: bare ? process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] : undefined,
|
|
34
|
-
inspectProcess: options.inspectProcess ?? inspectLiveProcessOnDemand,
|
|
35
|
-
});
|
|
36
|
+
const sessionRuntimes = await createSessionRuntimes(bare, options.inspectProcess ?? inspectLiveProcessOnDemand);
|
|
36
37
|
const productPaths = resolveProductPaths();
|
|
37
38
|
// Rationale: the manager reads the engine's settings port lazily, after the session exists.
|
|
38
39
|
let backend;
|
|
@@ -46,12 +47,14 @@ export async function composeOwnedUi(options = {}) {
|
|
|
46
47
|
const bareSettings = bare ? settings : null;
|
|
47
48
|
// Invariant: process-wide engine state has one owner. The host installs the HTTP dispatcher, applies
|
|
48
49
|
// the theme singletons, and announces startup notices once; every session is created through it.
|
|
49
|
-
const host = await (options.createEngineHost ?? createPiEngineHost)(engineHostOptions(options, productMode, bareSettings));
|
|
50
|
+
const host = await (options.createEngineHost ?? createPiEngineHost)(engineHostOptions(options, productMode, bareSettings, productPaths.runtimeDir));
|
|
50
51
|
const sessionForkPrompt = options.sessionForkPrompt;
|
|
51
52
|
try {
|
|
52
53
|
backend = await host.create({
|
|
53
54
|
cwd,
|
|
54
55
|
...(bare ? { repositoryContextReader: sessionRuntimes.readerFor(undefined) } : {}),
|
|
56
|
+
...(bare && process.env[PRODUCT_IDENTITY.environment.sessionRuntimeId] !== undefined
|
|
57
|
+
? { worktreeCwdReader: sessionRuntimes.toolReaderFor(undefined) } : {}),
|
|
55
58
|
...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
|
|
56
59
|
...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
|
|
57
60
|
...(sessionForkPrompt === undefined ? {} : { sessionForkPrompt }),
|
|
@@ -127,7 +130,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
127
130
|
let presenter;
|
|
128
131
|
try {
|
|
129
132
|
const presenterOptions = {
|
|
130
|
-
presenters: createPiSessionPresenters(backend),
|
|
133
|
+
presenters: createPiSessionPresenters(backend, spinnerPresenterOptions(bareSettings)),
|
|
131
134
|
engine: {
|
|
132
135
|
backend,
|
|
133
136
|
cwd: backend.identity.cwd,
|
|
@@ -201,6 +204,12 @@ export async function composeOwnedUi(options = {}) {
|
|
|
201
204
|
};
|
|
202
205
|
return { application, settings };
|
|
203
206
|
}
|
|
207
|
+
function spinnerPresenterOptions(settings) {
|
|
208
|
+
return settings === null ? {} : { spinner: {
|
|
209
|
+
snapshot: () => settings.value("spinnerStyle"),
|
|
210
|
+
onChange: (listener) => settings.onChange(() => listener()),
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
204
213
|
/** The settings-driven ports only bare A1 receives: viewport, background, quit outro, suggestions, image limit, and skills. */
|
|
205
214
|
function bareShellPorts(settings, generator, diagnostics) {
|
|
206
215
|
const viewportSettings = {
|
|
@@ -235,10 +244,13 @@ function bareShellPorts(settings, generator, diagnostics) {
|
|
|
235
244
|
},
|
|
236
245
|
};
|
|
237
246
|
}
|
|
238
|
-
function engineHostOptions(options, productMode, settings) {
|
|
247
|
+
function engineHostOptions(options, productMode, settings, runtimeDir) {
|
|
239
248
|
const bare = productMode === "bare";
|
|
240
249
|
return {
|
|
241
250
|
productMode,
|
|
251
|
+
deliveryIncident: failure => {
|
|
252
|
+
void import("../foundation/terminal-cleanup/index.js").then(module => module.writeFatalDiagnostic(`${runtimeDir}/crashes`, "delivery", Object.assign(new Error(), { deliveryFailure: failure }), options.packageVersion)).catch(() => undefined);
|
|
253
|
+
},
|
|
242
254
|
availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
|
|
243
255
|
...(bare ? { workflowHost: { reserveExport: async (id, format) => (await import("../foundation/lifecycle/user-exports.js")).reserveUserExport(id, format) } } : {}),
|
|
244
256
|
announceStartupChangelog: !bare,
|
|
@@ -6,7 +6,7 @@ export function bindAutomaticSessionNaming(backend, settings, bare) {
|
|
|
6
6
|
const naming = new SessionNamingController({
|
|
7
7
|
backend, naming: backend.naming,
|
|
8
8
|
enabled: () => settings?.value("sessionAutoName") ?? true,
|
|
9
|
-
format: () => settings?.value("sessionNameFormat") ?? "
|
|
9
|
+
format: () => settings?.value("sessionNameFormat") ?? "sentence",
|
|
10
10
|
withContext: async (consume, signal) => {
|
|
11
11
|
const sessionId = backend.identity.currentHistorySessionId();
|
|
12
12
|
const sessionFile = backend.identity.currentSessionFile();
|
|
@@ -16,6 +16,8 @@ export declare class SessionRuntimeRegistry {
|
|
|
16
16
|
branch: string;
|
|
17
17
|
revision: string;
|
|
18
18
|
} | null>;
|
|
19
|
+
/** Strict tool cwd lookup: transient startup failure never redirects a write to primary. */
|
|
20
|
+
toolReaderFor(agentId: string | undefined): (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<string>;
|
|
19
21
|
/** Drain publication before releasing claims; never republish after disposal. */
|
|
20
22
|
dispose(): Promise<void>;
|
|
21
23
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export class SessionRuntimeRegistry {
|
|
3
3
|
#options;
|
|
4
4
|
#identities = new Map();
|
|
5
|
+
#activated = new Map();
|
|
5
6
|
#abort = new AbortController();
|
|
6
7
|
#owner = null;
|
|
7
8
|
#timer = null;
|
|
@@ -15,7 +16,9 @@ export class SessionRuntimeRegistry {
|
|
|
15
16
|
const { runtimeId, inspectProcess } = this.#options;
|
|
16
17
|
if (this.#disposed || runtimeId === undefined || inspectProcess === undefined)
|
|
17
18
|
return null;
|
|
18
|
-
const
|
|
19
|
+
const previous = this.#identities.get(agentId);
|
|
20
|
+
const identity = previous?.sessionId === sessionId && previous.sessionFile === sessionFile
|
|
21
|
+
? previous : { sessionId, sessionFile };
|
|
19
22
|
this.#identities.set(agentId, identity);
|
|
20
23
|
// Invariant: initial activation or inspection failure must not disable retries.
|
|
21
24
|
this.#arm();
|
|
@@ -23,25 +26,62 @@ export class SessionRuntimeRegistry {
|
|
|
23
26
|
const owner = await this.#ownerIdentity();
|
|
24
27
|
if (owner === null || !this.#current(agentId, identity))
|
|
25
28
|
return null;
|
|
26
|
-
const { activateSessionRepositoryContext,
|
|
29
|
+
const { activateSessionRepositoryContext, readActiveSessionRepositoryContext } = await import("../foundation/lifecycle/session-repository-context.js");
|
|
27
30
|
if (!this.#current(agentId, identity))
|
|
28
31
|
return null;
|
|
29
32
|
const options = {
|
|
33
|
+
runtimeId,
|
|
30
34
|
signal: AbortSignal.any([signal, this.#abort.signal]),
|
|
31
35
|
inspectProcess,
|
|
32
36
|
...(agentId === undefined ? {} : { agentId }),
|
|
33
37
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (this.#activated.get(agentId) !== identity) {
|
|
39
|
+
await activateSessionRepositoryContext(identity, runtimeId, owner, options);
|
|
40
|
+
if (!this.#current(agentId, identity))
|
|
41
|
+
return null;
|
|
42
|
+
this.#activated.set(agentId, identity);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const context = await readActiveSessionRepositoryContext(identity, options);
|
|
46
|
+
if (context === null)
|
|
47
|
+
return null;
|
|
38
48
|
if (context.branch === null)
|
|
39
49
|
throw new Error("associated branch unavailable");
|
|
40
50
|
return { cwd: context.cwd, branch: context.branch, revision: context.revision };
|
|
41
|
-
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const code = error instanceof Error && "code" in error ? error.code : null;
|
|
54
|
+
if ((code === "session-runtime-unavailable" || code === "worktree-context-changed") && this.#current(agentId, identity)) {
|
|
55
|
+
this.#activated.delete(agentId);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
42
59
|
});
|
|
43
60
|
};
|
|
44
61
|
}
|
|
62
|
+
/** Strict tool cwd lookup: transient startup failure never redirects a write to primary. */
|
|
63
|
+
toolReaderFor(agentId) {
|
|
64
|
+
const read = this.readerFor(agentId);
|
|
65
|
+
return async (sessionId, sessionFile, signal) => {
|
|
66
|
+
const { retrySessionCoordination } = await import("../foundation/lifecycle/session-context-retry.js");
|
|
67
|
+
return await retrySessionCoordination(async () => {
|
|
68
|
+
signal.throwIfAborted();
|
|
69
|
+
if (this.#disposed || this.#options.runtimeId === undefined)
|
|
70
|
+
throw new Error("worktree runtime is not active");
|
|
71
|
+
const context = await read(sessionId, sessionFile, signal);
|
|
72
|
+
if (this.#disposed)
|
|
73
|
+
throw new Error("worktree runtime is not active");
|
|
74
|
+
const active = this.#activated.get(agentId);
|
|
75
|
+
if (active?.sessionId !== sessionId || active.sessionFile !== sessionFile) {
|
|
76
|
+
throw Object.assign(new Error("worktree runtime registration is still recovering"), { code: "session-runtime-unavailable" });
|
|
77
|
+
}
|
|
78
|
+
if (context !== null)
|
|
79
|
+
return context.cwd;
|
|
80
|
+
const { readSessionStartupRoot } = await import("../foundation/lifecycle/session-repository-context.js");
|
|
81
|
+
return await readSessionStartupRoot({ sessionId, sessionFile });
|
|
82
|
+
}, 8_000, signal);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
45
85
|
/** Drain publication before releasing claims; never republish after disposal. */
|
|
46
86
|
async dispose() {
|
|
47
87
|
this.#disposed = true;
|
|
@@ -82,7 +122,7 @@ export class SessionRuntimeRegistry {
|
|
|
82
122
|
#arm() {
|
|
83
123
|
if (this.#timer !== null)
|
|
84
124
|
return;
|
|
85
|
-
this.#timer = setInterval(() => this.#queueRefresh(), this.#options.refreshMs ??
|
|
125
|
+
this.#timer = setInterval(() => this.#queueRefresh(), this.#options.refreshMs ?? 1_000);
|
|
86
126
|
this.#timer.unref();
|
|
87
127
|
}
|
|
88
128
|
#queueRefresh() {
|
|
@@ -96,7 +136,7 @@ export class SessionRuntimeRegistry {
|
|
|
96
136
|
return;
|
|
97
137
|
const { registerSessionRepositoryRuntime } = await import("../foundation/lifecycle/session-repository-context.js");
|
|
98
138
|
for (const [agentId, identity] of this.#identities) {
|
|
99
|
-
if (!this.#current(agentId, identity))
|
|
139
|
+
if (!this.#current(agentId, identity) || this.#activated.get(agentId) === identity)
|
|
100
140
|
continue;
|
|
101
141
|
await registerSessionRepositoryRuntime(identity, runtimeId, owner, {
|
|
102
142
|
inspectProcess,
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extension packages are managed outside any session: the command that installs
|
|
3
|
-
* one runs before the runtime exists, against a profile root chosen by the caller
|
|
4
|
-
* rather than by whatever session happens to be open. So this port stands apart
|
|
5
|
-
* from the session-scoped service ports and names only what a package operation
|
|
6
|
-
* needs — a root, a source, and an outcome a caller can render without knowing
|
|
7
|
-
* which engine performed it.
|
|
8
|
-
*/
|
|
1
|
+
/** Non-interactive package operations use a caller-chosen profile root before any session exists. */
|
|
9
2
|
export type AgentPackageOperation = "install" | "remove" | "update" | "refresh-models" | "list";
|
|
10
|
-
/**
|
|
11
|
-
* Why an operation ended, kept separate from the prose so callers can branch on
|
|
12
|
-
* it. `not-found` is the one failure an engine can state structurally — the source
|
|
13
|
-
* is not configured in this profile — rather than only in a message.
|
|
14
|
-
*/
|
|
3
|
+
/** Status is separate from prose; not-found means the source is unconfigured. */
|
|
15
4
|
export type AgentPackageStatus = "completed" | "not-found" | "failed";
|
|
16
5
|
export interface AgentPackageDescriptor {
|
|
17
6
|
readonly source: string;
|
|
@@ -25,10 +14,17 @@ export interface AgentPackageOutcome {
|
|
|
25
14
|
readonly source: string | null;
|
|
26
15
|
readonly packages: readonly AgentPackageDescriptor[];
|
|
27
16
|
readonly detail: string | null;
|
|
17
|
+
/** Update facts distinguish a checked result from skipped or warned work. */
|
|
18
|
+
readonly updateSummary?: {
|
|
19
|
+
readonly offline: boolean;
|
|
20
|
+
readonly warningCount: number;
|
|
21
|
+
readonly changes: readonly string[];
|
|
22
|
+
};
|
|
28
23
|
}
|
|
29
24
|
export interface AgentPackageProgress {
|
|
30
25
|
readonly operation: AgentPackageOperation;
|
|
31
26
|
readonly message: string;
|
|
27
|
+
readonly severity?: "warning";
|
|
32
28
|
}
|
|
33
29
|
export interface AgentPackagesPort {
|
|
34
30
|
readonly capabilities: {
|
|
@@ -57,4 +53,4 @@ export interface AgentPackagesPortInput {
|
|
|
57
53
|
readonly onDiagnostic?: (diagnostic: AgentPackageDiagnostic) => void;
|
|
58
54
|
}
|
|
59
55
|
export declare function assertAgentPackagesPort(port: AgentPackagesPort): void;
|
|
60
|
-
export declare function agentPackageOutcome(operation: AgentPackageOperation, status: AgentPackageStatus, detail?: string | null, source?: string | null, packages?: readonly AgentPackageDescriptor[]): AgentPackageOutcome;
|
|
56
|
+
export declare function agentPackageOutcome(operation: AgentPackageOperation, status: AgentPackageStatus, detail?: string | null, source?: string | null, packages?: readonly AgentPackageDescriptor[], updateSummary?: AgentPackageOutcome["updateSummary"]): AgentPackageOutcome;
|