@timurproko/a1 0.1.8-dev.536 → 0.1.8-dev.546
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/bin/cli.js +4 -0
- package/bin/update-recovery.js +25 -3
- package/dist/app/session-shell/session-shell-root.js +15 -5
- package/dist/cli/dispatch.d.ts +5 -0
- package/dist/cli/dispatch.js +26 -0
- package/dist/cli/session-context.d.ts +9 -0
- package/dist/cli/session-context.js +29 -0
- package/dist/composition/owned-ui.js +6 -2
- package/dist/contracts/owned-ui/model.d.ts +5 -0
- package/dist/contracts/owned-ui/validation.js +6 -0
- package/dist/contracts/presentation/index.d.ts +13 -0
- package/dist/contracts/presentation/index.js +30 -0
- package/dist/features/launch/index.d.ts +2 -0
- package/dist/features/launch/index.js +1 -0
- package/dist/features/launch/session-context.d.ts +8 -0
- package/dist/features/launch/session-context.js +8 -0
- package/dist/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/reference-screen-app.js +7 -8
- package/dist/features/owned-ui/settings-app.js +9 -5
- package/dist/foundation/lifecycle/index.d.ts +2 -0
- package/dist/foundation/lifecycle/index.js +1 -0
- package/dist/foundation/lifecycle/session-repository-context.d.ts +18 -0
- package/dist/foundation/lifecycle/session-repository-context.js +173 -0
- package/dist/foundation/release/index.d.ts +2 -2
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/update-recovery.d.ts +7 -1
- package/dist/foundation/release/update-recovery.js +39 -9
- package/dist/foundation/release/update.d.ts +7 -0
- package/dist/foundation/release/update.js +115 -24
- package/dist/integrations/pi/components/models-dialog.js +9 -9
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +27 -3
- package/dist/integrations/pi/components/shell-extension-ui.js +4 -2
- package/dist/integrations/pi/components/shell-footer-status.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -2
- package/dist/integrations/pi/components/skills-command.d.ts +2 -0
- package/dist/integrations/pi/components/skills-command.js +11 -10
- package/dist/integrations/pi/components/skills-dialog.js +8 -3
- package/dist/integrations/pi/components/theme.d.ts +4 -0
- package/dist/integrations/pi/components/theme.js +10 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +4 -4
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +9 -10
- package/dist/integrations/pi/components/upstream/components/extension-input.d.ts +17 -0
- package/dist/integrations/pi/components/upstream/components/extension-input.js +72 -0
- package/dist/integrations/pi/components/upstream/components/extension-selector.d.ts +15 -0
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +92 -0
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +16 -15
- package/dist/integrations/pi/components/upstream/components/session-footer.js +25 -9
- package/dist/integrations/pi/components/upstream/components/session-selector.js +25 -16
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +6 -4
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +22 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +14 -12
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +11 -5
- package/dist/integrations/pi/engine/adapter.d.ts +2 -1
- package/dist/integrations/pi/engine/adapter.js +2 -0
- package/dist/integrations/pi/engine/message-values.js +1 -0
- package/dist/integrations/pi/engine/session-runtime.d.ts +11 -2
- package/dist/integrations/pi/engine/session-runtime.js +136 -33
- package/dist/integrations/pi/engine/transcript-projection.js +35 -0
- package/dist/integrations/pi/startup-public.manifest.json +2 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +2 -1
- package/dist/ui/components/dialog-panel.d.ts +2 -1
- package/dist/ui/components/dialog-panel.js +6 -1
- package/dist/ui/components/index.d.ts +2 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/shortcut-hints.d.ts +7 -0
- package/dist/ui/components/shortcut-hints.js +12 -0
- package/dist/ui/components/shortcuts.d.ts +2 -0
- package/dist/ui/components/shortcuts.js +5 -2
- package/dist/ui/components/transcript-viewport.d.ts +1 -1
- package/docs/ci-release-runbook.md +9 -7
- package/docs/openspec-archive-automation.md +6 -0
- package/docs/validation.md +5 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -54,6 +54,10 @@ process.exitCode = await dispatchCli(process.argv.slice(2), {
|
|
|
54
54
|
const { createPiPackagesPort } = await import("../dist/integrations/pi/engine/package-integration.js");
|
|
55
55
|
return await runPackageCommand(request, { createPort: createPiPackagesPort });
|
|
56
56
|
},
|
|
57
|
+
sessionContext: async request => {
|
|
58
|
+
const { runSessionContextCommand } = await import("../dist/cli/session-context.js");
|
|
59
|
+
return await runSessionContextCommand(request);
|
|
60
|
+
},
|
|
57
61
|
}, {
|
|
58
62
|
stdout: message => process.stdout.write(message),
|
|
59
63
|
stderr: message => process.stderr.write(message),
|
package/bin/update-recovery.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { chmod, lstat, mkdir, open, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
8
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
|
|
11
11
|
const SCHEMA = "a1-update-recovery-v1";
|
|
@@ -200,9 +200,15 @@ async function readCapsule(path) {
|
|
|
200
200
|
const packageRoot = await realpath(value.packageRoot).catch(() => resolve(value.packageRoot));
|
|
201
201
|
const expectedPackage = resolve(globalRoot, ...value.packageName.split("/"));
|
|
202
202
|
if (!samePath(packageRoot, expectedPackage) || !containedBy(globalRoot, packageRoot)) throw new Error("A1 update recovery package root escapes npm global root");
|
|
203
|
-
const
|
|
204
|
-
|
|
203
|
+
const prefix = npmPrefixForGlobalRoot(globalRoot);
|
|
204
|
+
const expectedNpmArguments = ["install", "--global", "--prefix", prefix, "--loglevel=error", "--no-fund", "--no-audit", `${value.packageName}@${value.targetVersion}`];
|
|
205
|
+
const legacyNpmArguments = ["install", "--global", "--loglevel=error", "--no-fund", "--no-audit", `${value.packageName}@${value.targetVersion}`];
|
|
206
|
+
if (![expectedNpmArguments, legacyNpmArguments].some(arguments_ => sameArguments(value.npmArguments, arguments_))) {
|
|
207
|
+
throw new Error("A1 update recovery npm arguments are invalid");
|
|
208
|
+
}
|
|
205
209
|
const launcherRoot = resolve(value.launcherRoot);
|
|
210
|
+
const expectedLauncherRoot = process.platform === "win32" ? prefix : resolve(prefix, "bin");
|
|
211
|
+
if (!samePath(launcherRoot, expectedLauncherRoot)) throw new Error("A1 update recovery launcher root is invalid");
|
|
206
212
|
const expectedLaunchers = process.platform === "win32"
|
|
207
213
|
? [resolve(launcherRoot, "a1"), resolve(launcherRoot, "a1.cmd"), resolve(launcherRoot, "a1.ps1")]
|
|
208
214
|
: [resolve(launcherRoot, "a1")];
|
|
@@ -306,6 +312,22 @@ async function writeJson(path, value) {
|
|
|
306
312
|
await rename(temporary, path);
|
|
307
313
|
}
|
|
308
314
|
|
|
315
|
+
/** @param {string} globalRoot */
|
|
316
|
+
function npmPrefixForGlobalRoot(globalRoot) {
|
|
317
|
+
const root = resolve(globalRoot);
|
|
318
|
+
const nodeModules = process.platform === "win32" ? basename(root).toLowerCase() === "node_modules" : basename(root) === "node_modules";
|
|
319
|
+
if (!nodeModules) throw new Error("A1 update recovery npm global root layout is invalid");
|
|
320
|
+
if (process.platform === "win32") return dirname(root);
|
|
321
|
+
const libraryRoot = dirname(root);
|
|
322
|
+
if (basename(libraryRoot) !== "lib") throw new Error("A1 update recovery npm global root layout is invalid");
|
|
323
|
+
return dirname(libraryRoot);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @param {string[]} left @param {string[]} right */
|
|
327
|
+
function sameArguments(left, right) {
|
|
328
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
329
|
+
}
|
|
330
|
+
|
|
309
331
|
/**
|
|
310
332
|
* @param {string} left
|
|
311
333
|
* @param {string} right
|
|
@@ -430,7 +430,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
430
430
|
// fitting alignment, and live Working remain transient presentation chrome.
|
|
431
431
|
selectableDocumentRowCount,
|
|
432
432
|
...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
|
|
433
|
-
bottomAlignedTailRowCount: statusRows.length,
|
|
433
|
+
bottomAlignedTailRowCount: steeringRows.length + statusRows.length,
|
|
434
434
|
dockRows,
|
|
435
435
|
promptAnchors: document.promptAnchors,
|
|
436
436
|
width,
|
|
@@ -547,10 +547,14 @@ export class OwnedUiSessionShellRoot {
|
|
|
547
547
|
};
|
|
548
548
|
}
|
|
549
549
|
#renderDockNotice(width) {
|
|
550
|
-
|
|
550
|
+
const notice = this.#dockNotice;
|
|
551
|
+
if (notice === undefined)
|
|
551
552
|
return [];
|
|
552
|
-
|
|
553
|
-
|
|
553
|
+
if (notice.kind === "status") {
|
|
554
|
+
// Compatibility: Pi pads its status text by one cell regardless of the output pad setting.
|
|
555
|
+
return ["", ...renderPiShellStatusText(notice.message, width, PINNED_PI_LAYOUT.outputPad)];
|
|
556
|
+
}
|
|
557
|
+
return renderPiShellCommandMessage({ kind: notice.kind, message: notice.message }, width, this.#outputPad);
|
|
554
558
|
}
|
|
555
559
|
#dismissDockNotice() {
|
|
556
560
|
if (this.#dockNotice === undefined)
|
|
@@ -763,7 +767,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
763
767
|
// reader's eye already is, instead of a transcript row that opens an empty session at the
|
|
764
768
|
// top-left or sinks into a long feed. The pinned route keeps Pi's chat placement.
|
|
765
769
|
if (this.#customViewport) {
|
|
766
|
-
this.#dockNotice = message;
|
|
770
|
+
this.#dockNotice = { kind: "status", message };
|
|
767
771
|
this.#invalidateChrome();
|
|
768
772
|
return;
|
|
769
773
|
}
|
|
@@ -788,6 +792,11 @@ export class OwnedUiSessionShellRoot {
|
|
|
788
792
|
this.appendWorkflowStatus(message.message);
|
|
789
793
|
return;
|
|
790
794
|
}
|
|
795
|
+
if (this.#customViewport && (message.kind === "warning" || message.kind === "error")) {
|
|
796
|
+
this.#dockNotice = { kind: message.kind, message: message.message };
|
|
797
|
+
this.#invalidateChrome();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
791
800
|
this.#lastWorkflowStatusId = undefined;
|
|
792
801
|
const presentation = { kind: message.kind, message: message.message };
|
|
793
802
|
this.#appendAnchoredWorkflowComponent(width => renderPiShellCommandMessage(presentation, width, this.#outputPad));
|
|
@@ -1148,6 +1157,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
1148
1157
|
status: {
|
|
1149
1158
|
...view.status,
|
|
1150
1159
|
footer: {
|
|
1160
|
+
...footer,
|
|
1151
1161
|
branch: footer?.branch ?? null,
|
|
1152
1162
|
sessionName: footer?.sessionName ?? null,
|
|
1153
1163
|
availableProviderCount: footer?.availableProviderCount ?? 1,
|
package/dist/cli/dispatch.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type InteractiveLaunchIntent, type LaunchProfileId, type SessionSelection } from "../features/launch/index.js";
|
|
2
2
|
import type { CliCapabilities } from "./capabilities.js";
|
|
3
3
|
import type { PackageCommandRequest } from "./packages.js";
|
|
4
|
+
import type { SessionContextRequest } from "./session-context.js";
|
|
4
5
|
import { type PackageCliVerb, type PackageSyntaxDiagnostic } from "./package-messages.js";
|
|
5
6
|
export type UpdateChannel = "stable" | "next";
|
|
6
7
|
export interface CliHandlers {
|
|
@@ -8,6 +9,7 @@ export interface CliHandlers {
|
|
|
8
9
|
readonly version: () => Promise<number>;
|
|
9
10
|
readonly update: (channel: UpdateChannel, target?: string) => Promise<number>;
|
|
10
11
|
readonly packages: (request: PackageCommandRequest) => Promise<number>;
|
|
12
|
+
readonly sessionContext: (request: SessionContextRequest) => Promise<number>;
|
|
11
13
|
}
|
|
12
14
|
export interface CliOutput {
|
|
13
15
|
readonly stdout: (message: string) => void;
|
|
@@ -39,6 +41,9 @@ export type CliCommand = {
|
|
|
39
41
|
} | {
|
|
40
42
|
readonly kind: "packages";
|
|
41
43
|
readonly request: PackageCommandRequest;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "session-context";
|
|
46
|
+
readonly request: SessionContextRequest;
|
|
42
47
|
} | {
|
|
43
48
|
readonly kind: "error";
|
|
44
49
|
readonly message: string;
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -14,6 +14,8 @@ export function cliUsage(capabilities) {
|
|
|
14
14
|
"update",
|
|
15
15
|
"update --develop [preview-or-version]",
|
|
16
16
|
"update --models",
|
|
17
|
+
"session link-worktree <path>",
|
|
18
|
+
"session unlink-worktree",
|
|
17
19
|
"pi install <source>",
|
|
18
20
|
"pi remove <source>",
|
|
19
21
|
"pi uninstall <source>",
|
|
@@ -41,6 +43,10 @@ export function cliHelp(capabilities) {
|
|
|
41
43
|
` ${command} update --develop [preview-or-version]`,
|
|
42
44
|
` ${command} update --models`,
|
|
43
45
|
"",
|
|
46
|
+
"Session context:",
|
|
47
|
+
` ${command} session link-worktree <path>`,
|
|
48
|
+
` ${command} session unlink-worktree`,
|
|
49
|
+
"",
|
|
44
50
|
"Pi-compatible packages:",
|
|
45
51
|
` ${command} pi install <source>`,
|
|
46
52
|
` ${command} pi remove <source>`,
|
|
@@ -79,6 +85,8 @@ export async function dispatchCli(arguments_, handlers, output, capabilities) {
|
|
|
79
85
|
return await handlers.version();
|
|
80
86
|
if (command.kind === "packages")
|
|
81
87
|
return await handlers.packages(command.request);
|
|
88
|
+
if (command.kind === "session-context")
|
|
89
|
+
return await handlers.sessionContext(command.request);
|
|
82
90
|
return await handlers.update(command.channel, command.target);
|
|
83
91
|
}
|
|
84
92
|
export function parseCliCommand(arguments_, capabilities) {
|
|
@@ -105,12 +113,30 @@ export function parseCliCommand(arguments_, capabilities) {
|
|
|
105
113
|
}
|
|
106
114
|
if (command === "update")
|
|
107
115
|
return parseUpdate(rest);
|
|
116
|
+
if (command === "session")
|
|
117
|
+
return parseSessionContext(rest);
|
|
108
118
|
if (command?.startsWith("update:"))
|
|
109
119
|
return { kind: "noop" };
|
|
110
120
|
// Rationale: unsupported and reserved command spaces are deliberately quiet. Help is
|
|
111
121
|
// explicit, and a typo must never start an interactive or maintenance path.
|
|
112
122
|
return { kind: "noop" };
|
|
113
123
|
}
|
|
124
|
+
function parseSessionContext(rest) {
|
|
125
|
+
const [action, value, extra] = rest;
|
|
126
|
+
if (action === "link-worktree") {
|
|
127
|
+
if (value === undefined)
|
|
128
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("session link-worktree requires one path.") };
|
|
129
|
+
if (extra !== undefined)
|
|
130
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("session link-worktree accepts one path.") };
|
|
131
|
+
return { kind: "session-context", request: { action, path: value } };
|
|
132
|
+
}
|
|
133
|
+
if (action === "unlink-worktree") {
|
|
134
|
+
if (value !== undefined)
|
|
135
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("session unlink-worktree does not accept arguments.") };
|
|
136
|
+
return { kind: "session-context", request: { action } };
|
|
137
|
+
}
|
|
138
|
+
return { kind: "error", message: PRODUCT_TEXT.diagnostic("session accepts link-worktree <path> or unlink-worktree.") };
|
|
139
|
+
}
|
|
114
140
|
/**
|
|
115
141
|
* Stable update is the empty form. `--develop` selects the moving development
|
|
116
142
|
* head or one immutable numbered preview, while model refresh remains a separate
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SessionContextRequest } from "../features/launch/index.js";
|
|
2
|
+
export type { SessionContextRequest } from "../features/launch/index.js";
|
|
3
|
+
export interface SessionContextCommandOptions {
|
|
4
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
5
|
+
readonly stdout?: (message: string) => void;
|
|
6
|
+
readonly stderr?: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Apply repository presentation context only when invoked by an active Pi session shell tool. */
|
|
9
|
+
export declare function runSessionContextCommand(request: SessionContextRequest, options?: SessionContextCommandOptions): Promise<number>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { updateSessionRepositoryContext, } from "../features/launch/index.js";
|
|
2
|
+
import { PRODUCT_TEXT } from "../product-identity.js";
|
|
3
|
+
/** Apply repository presentation context only when invoked by an active Pi session shell tool. */
|
|
4
|
+
export async function runSessionContextCommand(request, options = {}) {
|
|
5
|
+
const environment = options.environment ?? process.env;
|
|
6
|
+
const stdout = options.stdout ?? (message => process.stdout.write(message));
|
|
7
|
+
const stderr = options.stderr ?? (message => process.stderr.write(message));
|
|
8
|
+
const identity = readIdentity(environment);
|
|
9
|
+
if (identity === null) {
|
|
10
|
+
stderr(`${PRODUCT_TEXT.diagnostic("session context requires an active A1 shell-tool session.")}\n`);
|
|
11
|
+
return 2;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const cwd = await updateSessionRepositoryContext(identity, request);
|
|
15
|
+
stdout(cwd === null ? "Session worktree context cleared.\n" : `Session worktree context linked: ${cwd}\n`);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
stderr(`${PRODUCT_TEXT.diagnostic(`could not update session context: ${error instanceof Error ? error.message : String(error)}`)}\n`);
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readIdentity(environment) {
|
|
24
|
+
const sessionId = environment.PI_SESSION_ID;
|
|
25
|
+
const sessionFile = environment.PI_SESSION_FILE;
|
|
26
|
+
if (!sessionId || !sessionFile)
|
|
27
|
+
return null;
|
|
28
|
+
return { sessionId, sessionFile };
|
|
29
|
+
}
|
|
@@ -5,6 +5,7 @@ import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js"
|
|
|
5
5
|
import { resolvePromptHistoryPath } from "../features/prompt-history/paths.js";
|
|
6
6
|
import { resolvePromptHistoryDataDir } from "../features/launch/profile-paths.js";
|
|
7
7
|
import { resolveProductPaths } from "../foundation/lifecycle/paths.js";
|
|
8
|
+
import { readSessionRepositoryContext } from "../foundation/lifecycle/session-repository-context.js";
|
|
8
9
|
import { applyConfiguredPiTheme, getAvailablePiThemes } from "../integrations/pi/components/upstream/theme/theme.js";
|
|
9
10
|
import { createPiEngineAdapter } from "../integrations/pi/engine/adapter.js";
|
|
10
11
|
import { ClipboardDiagnosticCapture } from "../app/session-shell/clipboard-diagnostics.js";
|
|
@@ -19,18 +20,21 @@ export async function composeOwnedUiApplication(options = {}) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function composeOwnedUi(options = {}) {
|
|
21
22
|
const cwd = options.cwd ?? process.cwd();
|
|
23
|
+
const ownedSurfaces = options.ownedSurfaces !== "off";
|
|
22
24
|
const adapter = options.createPiAdapter
|
|
23
25
|
? await options.createPiAdapter()
|
|
24
26
|
: await createPiEngineAdapter({
|
|
25
27
|
cwd,
|
|
26
28
|
availableThemes: () => getAvailablePiThemes().map(theme => theme.name),
|
|
27
|
-
settingsProductMode:
|
|
29
|
+
settingsProductMode: ownedSurfaces ? "bare" : "comparison",
|
|
30
|
+
...(ownedSurfaces ? {
|
|
31
|
+
repositoryContextReader: async (sessionId, sessionFile, signal) => await readSessionRepositoryContext({ sessionId, sessionFile }, { signal }),
|
|
32
|
+
} : {}),
|
|
28
33
|
...(options.sessionPath === undefined ? {} : { sessionPath: options.sessionPath }),
|
|
29
34
|
...(options.sessionSelection === undefined ? {} : { sessionSelection: options.sessionSelection }),
|
|
30
35
|
...(options.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: options.sessionForkPrompt }),
|
|
31
36
|
...(options.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: options.projectTrustPrompt }),
|
|
32
37
|
});
|
|
33
|
-
const ownedSurfaces = options.ownedSurfaces !== "off";
|
|
34
38
|
const settings = options.profileId === undefined
|
|
35
39
|
? null
|
|
36
40
|
: new OwnedSettingsManager({
|
|
@@ -115,6 +115,7 @@ export interface OwnedUiUsageView {
|
|
|
115
115
|
}
|
|
116
116
|
export interface OwnedUiFooterView {
|
|
117
117
|
readonly branch: string | null;
|
|
118
|
+
readonly repositoryPath?: string | null;
|
|
118
119
|
readonly pullRequest?: {
|
|
119
120
|
readonly number: number;
|
|
120
121
|
readonly url: string;
|
|
@@ -183,6 +184,10 @@ export interface OwnedUiTranscriptBlock {
|
|
|
183
184
|
readonly title: string | null;
|
|
184
185
|
readonly text: string;
|
|
185
186
|
readonly payload: unknown;
|
|
187
|
+
/** Bare-A1-only user presentation derived without mutating persisted/model message text. */
|
|
188
|
+
readonly userPresentation?: {
|
|
189
|
+
readonly visibleText: string;
|
|
190
|
+
};
|
|
186
191
|
/** Tool-call message completion alone must not settle execution. */
|
|
187
192
|
readonly toolState?: OwnedUiToolState;
|
|
188
193
|
/** Authoritative renderer inputs, separate from diagnostic payloads. */
|
|
@@ -278,6 +278,11 @@ export function assertOwnedUiTranscriptBlock(block) {
|
|
|
278
278
|
assertNonNegativeInteger(block.revision, "owned-UI transcript block revision");
|
|
279
279
|
assertOptionalText(block.title, "owned-UI transcript block title", MAX_LABEL_LENGTH);
|
|
280
280
|
assertPossiblyEmptyText(block.text, "owned-UI transcript block text", MAX_TEXT_BYTES);
|
|
281
|
+
if (block.userPresentation !== undefined) {
|
|
282
|
+
if (block.kind !== "user")
|
|
283
|
+
throw new TypeError("owned-UI user presentation requires a user block");
|
|
284
|
+
assertPossiblyEmptyText(block.userPresentation.visibleText, "owned-UI visible user text", MAX_TEXT_BYTES);
|
|
285
|
+
}
|
|
281
286
|
assertJsonValue(block.toolRendering === undefined ? block.payload : {
|
|
282
287
|
payload: block.payload, toolRendering: block.toolRendering,
|
|
283
288
|
}, "owned-UI transcript block payload", MAX_PAYLOAD_BYTES);
|
|
@@ -396,6 +401,7 @@ export function assertOwnedUiStatusView(status) {
|
|
|
396
401
|
}
|
|
397
402
|
if (status.footer !== undefined) {
|
|
398
403
|
assertOptionalText(status.footer.branch, "owned-UI footer branch", MAX_LABEL_LENGTH);
|
|
404
|
+
assertOptionalText(status.footer.repositoryPath ?? null, "owned-UI footer repository path", MAX_MESSAGE_LENGTH);
|
|
399
405
|
if (status.footer.pullRequest !== undefined && status.footer.pullRequest !== null) {
|
|
400
406
|
const pullRequest = status.footer.pullRequest;
|
|
401
407
|
assertIntegerInRange(pullRequest.number, 1, Number.MAX_SAFE_INTEGER, "owned-UI footer pull request number");
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export type PresentationRuntimeState = "idle" | "running" | "stopping" | "stopped" | "failed";
|
|
2
|
+
export interface SemanticShortcutHint {
|
|
3
|
+
readonly key?: string;
|
|
4
|
+
readonly action: string;
|
|
5
|
+
readonly actionFirst?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ShortcutHintRoles {
|
|
8
|
+
key(label: string): string;
|
|
9
|
+
action(name: string): string;
|
|
10
|
+
}
|
|
11
|
+
/** Applies the common display casing to each named key and letter in a shortcut label. */
|
|
12
|
+
export declare function displayShortcutKeyLabel(label: string): string;
|
|
13
|
+
/** Renders the canonical semantic shortcut row independently of either presentation framework. */
|
|
14
|
+
export declare function renderSemanticShortcutHints(entries: readonly SemanticShortcutHint[], roles: ShortcutHintRoles, indent?: number): string;
|
|
2
15
|
export interface PresentationComponentPort {
|
|
3
16
|
render(width: number): readonly string[];
|
|
4
17
|
invalidate(): void;
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
const SHORTCUT_KEY_NAMES = Object.freeze({
|
|
2
|
+
alt: "Alt", backspace: "Backspace", cmd: "Cmd", ctrl: "Ctrl", delete: "Delete", down: "Down", end: "End",
|
|
3
|
+
enter: "Enter", esc: "Esc", escape: "Escape", home: "Home", insert: "Insert", left: "Left", meta: "Meta",
|
|
4
|
+
option: "Option", pagedown: "PageDown", pageup: "PageUp", pgdn: "PgDn", pgup: "PgUp", return: "Return",
|
|
5
|
+
right: "Right", shift: "Shift", space: "Space", tab: "Tab", up: "Up",
|
|
6
|
+
});
|
|
7
|
+
/** Applies the common display casing to each named key and letter in a shortcut label. */
|
|
8
|
+
export function displayShortcutKeyLabel(label) {
|
|
9
|
+
if (label === "/")
|
|
10
|
+
return label;
|
|
11
|
+
return label.split("/").map(chord => chord.split("+").map(part => {
|
|
12
|
+
const named = SHORTCUT_KEY_NAMES[part.toLowerCase()];
|
|
13
|
+
if (named !== undefined)
|
|
14
|
+
return named;
|
|
15
|
+
return /^\p{L}$/u.test(part) ? part.toUpperCase() : part;
|
|
16
|
+
}).join("+")).join("/");
|
|
17
|
+
}
|
|
18
|
+
/** Renders the canonical semantic shortcut row independently of either presentation framework. */
|
|
19
|
+
export function renderSemanticShortcutHints(entries, roles, indent = 0) {
|
|
20
|
+
const rendered = entries.flatMap(entry => {
|
|
21
|
+
if (entry.key === "" || (entry.key !== undefined && entry.key.trim().length === 0))
|
|
22
|
+
return [];
|
|
23
|
+
if (entry.key === undefined)
|
|
24
|
+
return entry.action.length === 0 ? [] : [roles.action(entry.action)];
|
|
25
|
+
const key = roles.key(displayShortcutKeyLabel(entry.key));
|
|
26
|
+
const action = roles.action(entry.action);
|
|
27
|
+
return [entry.actionFirst ? `${action} ${key}` : `${key} ${action}`];
|
|
28
|
+
});
|
|
29
|
+
return `${" ".repeat(Math.max(0, indent))}${rendered.join(" ")}`;
|
|
30
|
+
}
|
|
1
31
|
export function assertPresentationComponent(component) {
|
|
2
32
|
requiredFunctions(component, ["render", "invalidate"], "presentation component");
|
|
3
33
|
const lines = component.render(80);
|
|
@@ -8,5 +8,7 @@ export { configurationRootForProfile, resolveLaunchProfilePaths, resolvePromptHi
|
|
|
8
8
|
export type { LaunchProfilePathOptions, LaunchProfilePaths } from "./profile-paths.js";
|
|
9
9
|
export { prepareInteractiveLaunch } from "./prepare-launch.js";
|
|
10
10
|
export type { PrepareInteractiveLaunchOptions, PreparedInteractiveLaunch } from "./prepare-launch.js";
|
|
11
|
+
export { updateSessionRepositoryContext } from "./session-context.js";
|
|
12
|
+
export type { SessionContextRequest } from "./session-context.js";
|
|
11
13
|
export { runSelectedInteractiveRuntime, selectInteractiveRuntime } from "./runtime-selection.js";
|
|
12
14
|
export type { InteractiveRuntimeRunners, InteractiveRuntimeSelection, OwnedUiProfileId } from "./runtime-selection.js";
|
|
@@ -3,4 +3,5 @@ export { PI_PROFILE_RESOURCE_DIRECTORIES, initializeProductProfile } from "./ini
|
|
|
3
3
|
export { interactiveLaunchIntent, parseSessionSelection } from "./intent.js";
|
|
4
4
|
export { configurationRootForProfile, resolveLaunchProfilePaths, resolvePromptHistoryDataDir } from "./profile-paths.js";
|
|
5
5
|
export { prepareInteractiveLaunch } from "./prepare-launch.js";
|
|
6
|
+
export { updateSessionRepositoryContext } from "./session-context.js";
|
|
6
7
|
export { runSelectedInteractiveRuntime, selectInteractiveRuntime } from "./runtime-selection.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SessionRepositoryIdentity } from "../../foundation/lifecycle/index.js";
|
|
2
|
+
export type SessionContextRequest = {
|
|
3
|
+
readonly action: "link-worktree";
|
|
4
|
+
readonly path: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly action: "unlink-worktree";
|
|
7
|
+
};
|
|
8
|
+
export declare function updateSessionRepositoryContext(identity: SessionRepositoryIdentity, request: SessionContextRequest): Promise<string | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { clearSessionRepositoryContext, setSessionRepositoryContext, } from "../../foundation/lifecycle/index.js";
|
|
2
|
+
export async function updateSessionRepositoryContext(identity, request) {
|
|
3
|
+
if (request.action === "unlink-worktree") {
|
|
4
|
+
await clearSessionRepositoryContext(identity);
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return (await setSessionRepositoryContext(identity, request.path)).cwd;
|
|
8
|
+
}
|
|
@@ -42,7 +42,7 @@ export function createConsoleProjectTrustPrompt(options = {}) {
|
|
|
42
42
|
optionRow("Trust", selected === 0),
|
|
43
43
|
optionRow("Do not trust", selected === 1),
|
|
44
44
|
"",
|
|
45
|
-
`${DIM}
|
|
45
|
+
`${DIM} ↑/↓${MUTED} to navigate ${DIM}Enter${MUTED} to select ${DIM}Esc${MUTED} to cancel${RESET_FG}`,
|
|
46
46
|
];
|
|
47
47
|
output.write(`${CLEAR_HOME}${lines.map(line => clipAnsiSafe(line, width)).join("\n")}`);
|
|
48
48
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GLOBAL_SCOPE, PLAIN_THEME, RAIL_COLUMNS, ShortcutRegistry, ScrollbarRails, assertNoShortcutConflicts, displayWidth, layoutList, padToWidth, renderGroupHeader, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarWheelRows, truncateToWidth, withScrollbarRail, } from "../../ui/components/index.js";
|
|
1
|
+
import { GLOBAL_SCOPE, PLAIN_THEME, RAIL_COLUMNS, ShortcutRegistry, ScrollbarRails, assertNoShortcutConflicts, displayWidth, layoutList, padToWidth, renderGroupHeader, renderShortcutHints, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarWheelRows, truncateToWidth, withScrollbarRail, } from "../../ui/components/index.js";
|
|
2
2
|
const SCOPE = "reference-screen";
|
|
3
3
|
/** Rows above the document: the top rule. */
|
|
4
4
|
const HEADER_ROWS = 1;
|
|
@@ -9,8 +9,7 @@ const SCROLL_LINGER_MS = 900;
|
|
|
9
9
|
const SCROLL_LINGER_REPAINT_MS = 925;
|
|
10
10
|
const LOADING_NOTICE = "Loading…";
|
|
11
11
|
export const REFERENCE_SCREEN_SHORTCUTS = new ShortcutRegistry();
|
|
12
|
-
// Compatibility:
|
|
13
|
-
// the A1 interrupt chord here rather than a close key.
|
|
12
|
+
// Compatibility: Ctrl+C remains the A1 interrupt chord here rather than a close key.
|
|
14
13
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "escape", scope: GLOBAL_SCOPE, description: "Close", section: "Screen", hint: { keys: "esc", does: "close" } }, "close");
|
|
15
14
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "up", scope: SCOPE, description: "Scroll up", section: "Navigate", hint: { keys: "↑↓", does: "scroll" } }, "up");
|
|
16
15
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "down", scope: SCOPE, description: "Scroll down", section: "Navigate", hint: { keys: "↑↓", does: "scroll" } }, "down");
|
|
@@ -18,8 +17,6 @@ REFERENCE_SCREEN_SHORTCUTS.declare({ key: "pageUp", scope: SCOPE, description: "
|
|
|
18
17
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "pageDown", scope: SCOPE, description: "Down a page", section: "Navigate" }, "page-down");
|
|
19
18
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "home", scope: SCOPE, description: "First row", section: "Navigate" }, "first");
|
|
20
19
|
REFERENCE_SCREEN_SHORTCUTS.declare({ key: "end", scope: SCOPE, description: "Last row", section: "Navigate" }, "last");
|
|
21
|
-
/** The separator v2's footer puts between its hints. */
|
|
22
|
-
const HINT_SEPARATOR = " • ";
|
|
23
20
|
assertNoShortcutConflicts(REFERENCE_SCREEN_SHORTCUTS.assemble());
|
|
24
21
|
const KEYS = {
|
|
25
22
|
"\u001b[A": "up",
|
|
@@ -136,11 +133,13 @@ export class ReferenceScreenApp {
|
|
|
136
133
|
? { rail: { key: this.id, column: rect.width, rowStart: HEADER_ROWS, trackHeight: geometry.trackHeight }, geometry, page: viewportLength }
|
|
137
134
|
: null;
|
|
138
135
|
const withRail = withScrollbarRail(body.slice(0, bodyHeight), geometry, contentWidth, theme, { presentation });
|
|
139
|
-
const hint = this.#interruptArmed
|
|
136
|
+
const hint = this.#interruptArmed
|
|
137
|
+
? theme.fg("dim", "press ctrl+c again to exit a1")
|
|
138
|
+
: renderShortcutHints(REFERENCE_SCREEN_SHORTCUTS.hintEntries(SCOPE), theme);
|
|
140
139
|
// Compatibility: the v2 reference screen frames its document between two border-coloured rules.
|
|
141
140
|
const rule = theme.fg("border", "─".repeat(rect.width));
|
|
142
|
-
// Compatibility:
|
|
143
|
-
const frame = [rule, ...withRail, rule, padToWidth(
|
|
141
|
+
// Compatibility: the footer remains against the left edge, unlike the settings status line.
|
|
142
|
+
const frame = [rule, ...withRail, rule, padToWidth(hint, rect.width)];
|
|
144
143
|
// Invariant: a rectangle too small for the chrome still gets exactly its rows, top first.
|
|
145
144
|
return frame.slice(0, rect.height).concat(Array(Math.max(0, rect.height - frame.length)).fill(""));
|
|
146
145
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote,
|
|
1
|
+
import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderShortcutHints, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
|
|
2
2
|
import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
|
|
3
3
|
export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
|
|
4
4
|
const SCOPE = SETTINGS_APP_ID;
|
|
@@ -786,15 +786,19 @@ export class SettingsApp {
|
|
|
786
786
|
});
|
|
787
787
|
this.#dialogValueColumn = dialogValueColumn(rows);
|
|
788
788
|
this.#panelTop = this.#panelTopForFrame;
|
|
789
|
-
return renderDialogPanel({ rows, index: open.index, hint: SETTINGS_SHORTCUTS.
|
|
789
|
+
return renderDialogPanel({ rows, index: open.index, hint: SETTINGS_SHORTCUTS.hintEntries(DIALOG_SCOPE) }, width, theme);
|
|
790
790
|
}
|
|
791
791
|
#footerLines(width, theme) {
|
|
792
792
|
const open = this.#structured;
|
|
793
793
|
if (open !== null)
|
|
794
794
|
return this.#dialogLines(open, width, theme);
|
|
795
|
-
const
|
|
796
|
-
const
|
|
797
|
-
|
|
795
|
+
const notice = this.#notice;
|
|
796
|
+
const plainStatus = notice !== null && notice.length > 0
|
|
797
|
+
? notice
|
|
798
|
+
: this.#interruptArmed ? "press ctrl+c again to exit a1" : null;
|
|
799
|
+
const status = plainStatus === null
|
|
800
|
+
? truncateToWidth(renderShortcutHints(SETTINGS_SHORTCUTS.hintEntries(SCOPE), theme, width > 0 ? 1 : 0), width)
|
|
801
|
+
: truncateToWidth(`${width > 0 ? " " : ""}${theme.fg("dim", plainStatus)}`, width);
|
|
798
802
|
const input = this.#filter;
|
|
799
803
|
if (input === null)
|
|
800
804
|
return [status];
|
|
@@ -7,5 +7,7 @@ export { resolveCohortEndpoint, resolveProductPaths } from "./paths.js";
|
|
|
7
7
|
export type { CohortEndpointPaths, ProductPaths } from "./paths.js";
|
|
8
8
|
export { parseSessionSelection, sessionSelectionArguments } from "./session-selection.js";
|
|
9
9
|
export type { SessionSelection } from "./session-selection.js";
|
|
10
|
+
export { clearSessionRepositoryContext, readSessionRepositoryContext, setSessionRepositoryContext } from "./session-repository-context.js";
|
|
11
|
+
export type { SessionRepositoryContext, SessionRepositoryContextOptions, SessionRepositoryIdentity } from "./session-repository-context.js";
|
|
10
12
|
export { createSupervisorStartupAttempt, publishSupervisorStartupResult, readSupervisorStartupResult, supervisorStartupFailure, supervisorStartupReady, supervisorStartupResultPath, } from "./supervisor-startup.js";
|
|
11
13
|
export type { SupervisorStartupAttemptIdentity, SupervisorStartupResult } from "./supervisor-startup.js";
|
|
@@ -2,4 +2,5 @@ export { assertLaunchInstance, assertLaunchInstanceOutcome, assertLaunchInstance
|
|
|
2
2
|
export { assertDimensions, assertLaunchProfileId, assertNativeProcessIdentity, assertTransparentTerminalLaunchProfile } from "./model.js";
|
|
3
3
|
export { resolveCohortEndpoint, resolveProductPaths } from "./paths.js";
|
|
4
4
|
export { parseSessionSelection, sessionSelectionArguments } from "./session-selection.js";
|
|
5
|
+
export { clearSessionRepositoryContext, readSessionRepositoryContext, setSessionRepositoryContext } from "./session-repository-context.js";
|
|
5
6
|
export { createSupervisorStartupAttempt, publishSupervisorStartupResult, readSupervisorStartupResult, supervisorStartupFailure, supervisorStartupReady, supervisorStartupResultPath, } from "./supervisor-startup.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SessionRepositoryIdentity {
|
|
2
|
+
readonly sessionId: string;
|
|
3
|
+
readonly sessionFile: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SessionRepositoryContext {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly branch: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionRepositoryContextOptions {
|
|
10
|
+
readonly dataDir?: string;
|
|
11
|
+
readonly signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
/** Persist one explicit same-repository worktree association for a stable Pi session. */
|
|
14
|
+
export declare function setSessionRepositoryContext(identity: SessionRepositoryIdentity, worktreePath: string, options?: SessionRepositoryContextOptions): Promise<SessionRepositoryContext>;
|
|
15
|
+
/** Remove only the association belonging to the supplied stable session. */
|
|
16
|
+
export declare function clearSessionRepositoryContext(identity: SessionRepositoryIdentity, options?: SessionRepositoryContextOptions): Promise<void>;
|
|
17
|
+
/** Read and revalidate a persisted association; malformed or stale state fails closed to absence. */
|
|
18
|
+
export declare function readSessionRepositoryContext(identity: SessionRepositoryIdentity, options?: SessionRepositoryContextOptions): Promise<SessionRepositoryContext | null>;
|