@timurproko/a1 0.1.8-dev.226 → 0.1.8-dev.259
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/guardian.js +4 -2
- package/bin/supervisor.js +1 -1
- package/bin/ui.js +44 -36
- package/bin/warmup.js +18 -9
- package/dist/cli/dispatch.d.ts +9 -1
- package/dist/cli/dispatch.js +73 -14
- package/dist/cli/package-messages.d.ts +26 -0
- package/dist/cli/package-messages.js +71 -0
- package/dist/cli/packages.d.ts +2 -1
- package/dist/cli/packages.js +12 -19
- package/dist/composition/owned-ui.d.ts +5 -2
- package/dist/composition/owned-ui.js +3 -1
- package/dist/contracts/agent-engine/package-ports.d.ts +6 -0
- package/dist/features/launch/intent.d.ts +5 -3
- package/dist/features/launch/intent.js +7 -2
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/session-fork-prompt.d.ts +8 -0
- package/dist/features/owned-ui/session-fork-prompt.js +34 -0
- package/dist/foundation/launch-guardian/main.d.ts +2 -0
- package/dist/foundation/launch-guardian/main.js +8 -3
- package/dist/foundation/lifecycle/index.d.ts +2 -0
- package/dist/foundation/lifecycle/index.js +2 -0
- package/dist/foundation/lifecycle/paths.d.ts +2 -0
- package/dist/foundation/lifecycle/paths.js +9 -2
- package/dist/foundation/lifecycle/session-selection.d.ts +9 -0
- package/dist/foundation/lifecycle/session-selection.js +39 -0
- package/dist/foundation/lifecycle/supervisor-startup.d.ts +30 -0
- package/dist/foundation/lifecycle/supervisor-startup.js +106 -0
- package/dist/foundation/process-containment/darwin-process-inspector.d.ts +5 -0
- package/dist/foundation/process-containment/darwin-process-inspector.js +7 -0
- package/dist/foundation/process-containment/index.d.ts +1 -0
- package/dist/foundation/process-containment/index.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +2 -1
- package/dist/foundation/process-containment/windows-process-inspector.js +4 -2
- package/dist/foundation/release/bootstrap.d.ts +10 -3
- package/dist/foundation/release/bootstrap.js +58 -25
- package/dist/foundation/release/dependency-layer.js +11 -10
- package/dist/foundation/release/immutable-platform.d.ts +6 -0
- package/dist/foundation/release/immutable-platform.js +13 -0
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/release-store.d.ts +1 -1
- package/dist/foundation/release/release-store.js +4 -2
- package/dist/foundation/release/restart-certification.d.ts +67 -0
- package/dist/foundation/release/restart-certification.js +211 -0
- package/dist/foundation/release/update.js +4 -4
- package/dist/foundation/release/warmup.js +1 -1
- package/dist/foundation/startup/startup-runtime.d.ts +2 -2
- package/dist/foundation/startup/startup-runtime.js +1 -1
- package/dist/foundation/supervision/main.d.ts +1 -1
- package/dist/foundation/supervision/main.js +60 -30
- package/dist/foundation/supervision/server.js +14 -1
- package/dist/integrations/pi/components/shell-footer-status.js +28 -12
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +2 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +5 -0
- package/dist/integrations/pi/engine/adapter.d.ts +6 -0
- package/dist/integrations/pi/engine/adapter.js +354 -53
- package/dist/integrations/pi/engine/index.d.ts +1 -0
- package/dist/integrations/pi/engine/index.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +31 -25
- package/dist/integrations/pi/engine/runtime-integration.d.ts +3 -0
- package/dist/integrations/pi/engine/runtime-integration.js +11 -4
- package/dist/integrations/pi/engine/session-selection.d.ts +28 -0
- package/dist/integrations/pi/engine/session-selection.js +90 -0
- package/dist/integrations/pi/engine/workflows.d.ts +18 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +5 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +76 -24
- package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell.js +46 -14
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -1
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +47 -18
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +12 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +166 -26
- package/dist/native/darwin-arm64/manifest.json +4 -4
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/text-selection.js +8 -35
- package/dist/ui/components/transcript-viewport.d.ts +8 -2
- package/dist/ui/components/transcript-viewport.js +8 -6
- package/dist/ui/components/visible-hyperlinks.d.ts +19 -0
- package/dist/ui/components/visible-hyperlinks.js +64 -0
- package/docs/architecture/process-guardian-provenance.md +3 -1
- package/docs/architecture/ui-reference-provenance.md +2 -0
- package/docs/ci-release-runbook.md +1 -1
- package/docs/features/launch-profiles.md +17 -1
- package/docs/manual-ghost-link-baseline.md +89 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./adapter.js";
|
|
2
2
|
export * from "./conformance.js";
|
|
3
3
|
export * from "./runtime-integration.js";
|
|
4
|
+
export * from "./session-selection.js";
|
|
4
5
|
export * from "./session-integration.js";
|
|
5
6
|
export * from "./model-auth-integration.js";
|
|
6
7
|
export * from "./settings-integration.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./adapter.js";
|
|
2
2
|
export * from "./conformance.js";
|
|
3
3
|
export * from "./runtime-integration.js";
|
|
4
|
+
export * from "./session-selection.js";
|
|
4
5
|
export * from "./session-integration.js";
|
|
5
6
|
export * from "./model-auth-integration.js";
|
|
6
7
|
export * from "./settings-integration.js";
|
|
@@ -15,21 +15,33 @@ const MODEL_REFRESH_TIMEOUT_MS = 15_000;
|
|
|
15
15
|
*/
|
|
16
16
|
export function createPiPackagesPort(input) {
|
|
17
17
|
const { profileRoot, cwd } = input;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// Compatibility: model-only refresh bypasses package settings and their diagnostics, as Pi does.
|
|
19
|
+
let packageManager;
|
|
20
|
+
const manager = () => {
|
|
21
|
+
if (packageManager)
|
|
22
|
+
return packageManager;
|
|
23
|
+
const settingsManager = SettingsManager.create(cwd, profileRoot, { projectTrusted: false });
|
|
24
|
+
for (const { scope, error } of settingsManager.drainErrors()) {
|
|
25
|
+
input.onDiagnostic?.({
|
|
26
|
+
message: `Warning (package command, ${scope} settings): ${error.message}`,
|
|
27
|
+
...(error.stack ? { detail: error.stack } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
packageManager = new DefaultPackageManager({ cwd, agentDir: profileRoot, settingsManager });
|
|
31
|
+
packageManager.setProgressCallback(event => {
|
|
32
|
+
if (event.type !== "start")
|
|
33
|
+
return;
|
|
34
|
+
input.onProgress?.({ operation: operationFor(event.action), message: String(event.message) });
|
|
35
|
+
});
|
|
36
|
+
return packageManager;
|
|
37
|
+
};
|
|
38
|
+
const configured = () => manager().listConfiguredPackages()
|
|
26
39
|
.filter(entry => entry.scope === "user")
|
|
27
40
|
.map(entry => Object.freeze({
|
|
28
41
|
source: entry.source,
|
|
29
42
|
installedPath: entry.installedPath ?? null,
|
|
30
43
|
filtered: entry.filtered,
|
|
31
44
|
}));
|
|
32
|
-
const isConfigured = (source) => configured().some(entry => entry.source === source || entry.source.startsWith(`${source}@`));
|
|
33
45
|
return Object.freeze({
|
|
34
46
|
capabilities: Object.freeze({ install: true, remove: true, update: true, refreshModels: true }),
|
|
35
47
|
profileRoot,
|
|
@@ -38,13 +50,13 @@ export function createPiPackagesPort(input) {
|
|
|
38
50
|
},
|
|
39
51
|
async install(source) {
|
|
40
52
|
return await attempt("install", source, async () => {
|
|
41
|
-
await
|
|
53
|
+
await manager().installAndPersist(source);
|
|
42
54
|
return agentPackageOutcome("install", "completed", null, source, configured());
|
|
43
55
|
});
|
|
44
56
|
},
|
|
45
57
|
async remove(source) {
|
|
46
58
|
return await attempt("remove", source, async () => {
|
|
47
|
-
const removed = await
|
|
59
|
+
const removed = await manager().removeAndPersist(source);
|
|
48
60
|
if (!removed)
|
|
49
61
|
return agentPackageOutcome("remove", "not-found", null, source);
|
|
50
62
|
return agentPackageOutcome("remove", "completed", null, source, configured());
|
|
@@ -52,11 +64,8 @@ export function createPiPackagesPort(input) {
|
|
|
52
64
|
},
|
|
53
65
|
async update(source) {
|
|
54
66
|
return await attempt("update", source ?? null, async () => {
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
if (source !== undefined && !isConfigured(source))
|
|
58
|
-
return agentPackageOutcome("update", "not-found", null, source);
|
|
59
|
-
await packageManager.update(source);
|
|
67
|
+
// Compatibility: Pi owns source identity and its contextual no-match suggestions.
|
|
68
|
+
await manager().update(source);
|
|
60
69
|
return agentPackageOutcome("update", "completed", null, source ?? null, configured());
|
|
61
70
|
});
|
|
62
71
|
},
|
|
@@ -73,7 +82,9 @@ async function attempt(operation, source, run) {
|
|
|
73
82
|
return await run();
|
|
74
83
|
}
|
|
75
84
|
catch (error) {
|
|
76
|
-
|
|
85
|
+
const detail = error instanceof Error ? error.message : operation === "refresh-models"
|
|
86
|
+
? "Unknown model catalog refresh error" : "Unknown package command error";
|
|
87
|
+
return agentPackageOutcome(operation, "failed", detail, source);
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
async function refreshModelCatalogs(profileRoot) {
|
|
@@ -88,9 +99,10 @@ async function refreshModelCatalogs(profileRoot) {
|
|
|
88
99
|
});
|
|
89
100
|
const result = await modelRuntime.refresh({ allowNetwork: true, force: true, signal: controller.signal });
|
|
90
101
|
if (result.aborted)
|
|
91
|
-
throw new Error(
|
|
102
|
+
throw new Error("Model catalog refresh timed out.");
|
|
92
103
|
if (result.errors.size > 0) {
|
|
93
|
-
|
|
104
|
+
const details = Array.from(result.errors, ([provider, error]) => `${provider}: ${error.message}`).join("; ");
|
|
105
|
+
throw new Error(`Could not refresh model catalogs: ${details}`);
|
|
94
106
|
}
|
|
95
107
|
}
|
|
96
108
|
finally {
|
|
@@ -104,9 +116,3 @@ function operationFor(action) {
|
|
|
104
116
|
return "remove";
|
|
105
117
|
return "update";
|
|
106
118
|
}
|
|
107
|
-
function describe(error) {
|
|
108
|
-
const message = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
|
|
109
|
-
if (message.length === 0)
|
|
110
|
-
return "unknown package failure";
|
|
111
|
-
return message.length > 600 ? `${message.slice(0, 597)}...` : message;
|
|
112
|
-
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { createAgentSessionServices, SettingsManager, type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type ScopedModel } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolvePiProjectTrustPreflight, type PiProjectTrustPreflightPrompt } from "./project-trust-preflight.js";
|
|
3
|
+
import { type PiSessionForkPrompt, type PiSessionSelection } from "./session-selection.js";
|
|
3
4
|
export interface PiRuntimeIntegrationOptions {
|
|
4
5
|
readonly cwd: string;
|
|
5
6
|
readonly agentDir: string;
|
|
6
7
|
readonly sessionDir?: string;
|
|
7
8
|
readonly sessionPath?: string;
|
|
9
|
+
readonly sessionSelection?: PiSessionSelection;
|
|
10
|
+
readonly sessionForkPrompt?: PiSessionForkPrompt;
|
|
8
11
|
readonly projectTrustPrompt?: PiProjectTrustPreflightPrompt;
|
|
9
12
|
readonly preflightDependencies?: PiRuntimePreflightDependencies;
|
|
10
13
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, resolveModelScopeWithDiagnostics, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
|
|
3
|
+
import { openSelectedPiSession, resolveSessionArgumentPath } from "./session-selection.js";
|
|
3
4
|
import { markStartupPhase } from "../../../foundation/startup/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
|
|
@@ -49,10 +50,16 @@ export async function createPiRuntimeServicesAfterTrust(options) {
|
|
|
49
50
|
return { services, trust };
|
|
50
51
|
}
|
|
51
52
|
export async function createPiRuntimeIntegration(options) {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
53
|
+
const directory = options.sessionDir ?? process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
54
|
+
const sessionDir = directory === undefined ? undefined : resolveSessionArgumentPath(directory, options.cwd);
|
|
55
|
+
const selection = options.sessionSelection ?? (options.sessionPath === undefined ? undefined : { target: resolveSessionArgumentPath(options.sessionPath, options.cwd) });
|
|
56
|
+
const sessionManager = selection === undefined
|
|
54
57
|
? SessionManager.create(options.cwd, sessionDir)
|
|
55
|
-
:
|
|
58
|
+
: await openSelectedPiSession({
|
|
59
|
+
cwd: options.cwd, selection,
|
|
60
|
+
...(sessionDir === undefined ? {} : { sessionDir }),
|
|
61
|
+
...(options.sessionForkPrompt === undefined ? {} : { forkPrompt: options.sessionForkPrompt }),
|
|
62
|
+
});
|
|
56
63
|
const createRuntime = async ({ cwd, sessionManager: targetSessionManager, sessionStartEvent, }) => {
|
|
57
64
|
const { services, trust } = await createPiRuntimeServicesAfterTrust({
|
|
58
65
|
cwd,
|
|
@@ -81,7 +88,7 @@ export async function createPiRuntimeIntegration(options) {
|
|
|
81
88
|
};
|
|
82
89
|
};
|
|
83
90
|
return createAgentSessionRuntime(createRuntime, {
|
|
84
|
-
cwd:
|
|
91
|
+
cwd: sessionManager.getCwd(),
|
|
85
92
|
agentDir: options.agentDir,
|
|
86
93
|
sessionManager,
|
|
87
94
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SessionManager, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export interface PiSessionSelection {
|
|
3
|
+
readonly target: string;
|
|
4
|
+
readonly sessionDir?: string;
|
|
5
|
+
}
|
|
6
|
+
export type PiSessionForkPrompt = (request: {
|
|
7
|
+
readonly sourceCwd: string;
|
|
8
|
+
}) => Promise<boolean>;
|
|
9
|
+
/** Expected resume outcomes are concise CLI diagnostics, not engine crashes. */
|
|
10
|
+
export declare class PiSessionSelectionError extends Error {
|
|
11
|
+
readonly exitCode: number;
|
|
12
|
+
constructor(message: string, exitCode?: number);
|
|
13
|
+
}
|
|
14
|
+
interface SessionSelectionOptions {
|
|
15
|
+
readonly cwd: string;
|
|
16
|
+
readonly selection: PiSessionSelection;
|
|
17
|
+
readonly sessionDir?: string;
|
|
18
|
+
readonly forkPrompt?: PiSessionForkPrompt;
|
|
19
|
+
/** Public API seam for deterministic lookup/open race and listing-order evidence. */
|
|
20
|
+
readonly sessions?: Pick<typeof SessionManager, "list" | "open" | "forkFrom"> & {
|
|
21
|
+
readonly listAll: (sessionDir?: string) => Promise<SessionInfo[]>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
|
|
25
|
+
export declare function openSelectedPiSession(options: SessionSelectionOptions): Promise<SessionManager>;
|
|
26
|
+
/** Resolve before switching effective cwd, including quoted home-relative paths. */
|
|
27
|
+
export declare function resolveSessionArgumentPath(path: string, cwd: string): string;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
/** Expected resume outcomes are concise CLI diagnostics, not engine crashes. */
|
|
6
|
+
export class PiSessionSelectionError extends Error {
|
|
7
|
+
exitCode;
|
|
8
|
+
constructor(message, exitCode = 1) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.exitCode = exitCode;
|
|
11
|
+
this.name = "PiSessionSelectionError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Pinned Pi 0.84.2 main.ts policy, using SDK APIs rather than importing the CLI. */
|
|
15
|
+
export async function openSelectedPiSession(options) {
|
|
16
|
+
try {
|
|
17
|
+
const sessions = options.sessions ?? SessionManager;
|
|
18
|
+
const cwd = resolve(options.cwd);
|
|
19
|
+
const directory = options.selection.sessionDir ?? options.sessionDir ?? process.env.PI_CODING_AGENT_SESSION_DIR;
|
|
20
|
+
const sessionDir = directory === undefined ? undefined : resolveSessionArgumentPath(directory, cwd);
|
|
21
|
+
const target = options.selection.target;
|
|
22
|
+
let path;
|
|
23
|
+
let global;
|
|
24
|
+
if (target.includes("/") || target.includes("\\") || target.endsWith(".jsonl")) {
|
|
25
|
+
path = resolveSessionArgumentPath(target, cwd);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const local = matchSession(await sessions.list(cwd, sessionDir), target);
|
|
29
|
+
const globalMatch = local ? undefined : matchSession(await sessions.listAll(sessionDir), target);
|
|
30
|
+
// Compatibility: macOS may expose one temporary directory as /var to one process
|
|
31
|
+
// and /private/var to another. Physical path identity must not turn that alias into
|
|
32
|
+
// a cross-project fork prompt.
|
|
33
|
+
global = globalMatch && !sameProjectDirectory(globalMatch.cwd, cwd) ? globalMatch : undefined;
|
|
34
|
+
const matched = local ?? globalMatch;
|
|
35
|
+
if (!matched)
|
|
36
|
+
throw new PiSessionSelectionError(`No session found matching '${target}'`);
|
|
37
|
+
path = matched.path;
|
|
38
|
+
}
|
|
39
|
+
assertResumeFile(path);
|
|
40
|
+
if (global) {
|
|
41
|
+
if (!options.forkPrompt || !await options.forkPrompt({ sourceCwd: global.cwd })) {
|
|
42
|
+
throw new PiSessionSelectionError("Session resume cancelled.", 0);
|
|
43
|
+
}
|
|
44
|
+
assertResumeFile(path);
|
|
45
|
+
assertCwd(cwd);
|
|
46
|
+
return sessions.forkFrom(path, cwd, sessionDir);
|
|
47
|
+
}
|
|
48
|
+
// Invariant: no await between the final guard and SDK open. A disappeared target
|
|
49
|
+
// can otherwise produce a new in-memory header instead of an existing session.
|
|
50
|
+
const manager = sessions.open(path, sessionDir);
|
|
51
|
+
assertResumeFile(path);
|
|
52
|
+
assertCwd(manager.getCwd());
|
|
53
|
+
return manager;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error instanceof PiSessionSelectionError)
|
|
57
|
+
throw error;
|
|
58
|
+
throw new PiSessionSelectionError(`Could not resume session: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function sameProjectDirectory(left, right) {
|
|
62
|
+
try {
|
|
63
|
+
const first = realpathSync.native(resolve(left));
|
|
64
|
+
const second = realpathSync.native(resolve(right));
|
|
65
|
+
return process.platform === "win32" ? first.toLowerCase() === second.toLowerCase() : first === second;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return resolve(left) === resolve(right);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function matchSession(sessions, target) {
|
|
72
|
+
return sessions.find(session => session.id === target) ?? sessions.find(session => session.id.startsWith(target));
|
|
73
|
+
}
|
|
74
|
+
/** Resolve before switching effective cwd, including quoted home-relative paths. */
|
|
75
|
+
export function resolveSessionArgumentPath(path, cwd) {
|
|
76
|
+
const expanded = path === "~" ? homedir() : /^~[/\\]/.test(path) ? resolve(homedir(), path.slice(2)) : path;
|
|
77
|
+
return resolve(cwd, expanded);
|
|
78
|
+
}
|
|
79
|
+
function assertResumeFile(path) {
|
|
80
|
+
if (!existsSync(path))
|
|
81
|
+
throw new PiSessionSelectionError(`Session file does not exist: ${path}`);
|
|
82
|
+
const metadata = statSync(path);
|
|
83
|
+
if (!metadata.isFile() || metadata.size === 0)
|
|
84
|
+
throw new PiSessionSelectionError(`Session file is empty or not a regular file: ${path}`);
|
|
85
|
+
accessSync(path, constants.R_OK);
|
|
86
|
+
}
|
|
87
|
+
function assertCwd(cwd) {
|
|
88
|
+
if (!existsSync(cwd) || !statSync(cwd).isDirectory())
|
|
89
|
+
throw new PiSessionSelectionError(`Session working directory does not exist: ${cwd}`);
|
|
90
|
+
}
|
|
@@ -62,6 +62,10 @@ export interface PiWorkflowRequest {
|
|
|
62
62
|
readonly argument: string;
|
|
63
63
|
readonly selection?: string;
|
|
64
64
|
readonly confirmed?: boolean;
|
|
65
|
+
/** Recovery cwd selected after an import/resume source cwd is unavailable. */
|
|
66
|
+
readonly cwdOverride?: string;
|
|
67
|
+
/** Cancellation for an owned operation surface such as `/share`. */
|
|
68
|
+
readonly signal?: AbortSignal;
|
|
65
69
|
readonly treeSummary?: {
|
|
66
70
|
readonly summarize: boolean;
|
|
67
71
|
readonly customInstructions?: string;
|
|
@@ -100,10 +104,19 @@ export interface PiSessionInfoPresentation {
|
|
|
100
104
|
}[];
|
|
101
105
|
}
|
|
102
106
|
export type PiWorkflowPresentation = PiSessionInfoPresentation;
|
|
107
|
+
export type PiWorkflowMessageKind = "status" | "warning" | "error" | "accent" | "silent";
|
|
108
|
+
export interface PiWorkflowMessage {
|
|
109
|
+
readonly kind: Exclude<PiWorkflowMessageKind, "silent">;
|
|
110
|
+
readonly message: string;
|
|
111
|
+
}
|
|
103
112
|
export interface PiWorkflowResult {
|
|
104
113
|
readonly command: PiWorkflowRoute;
|
|
105
114
|
readonly outcome: PiWorkflowOutcome;
|
|
106
115
|
readonly message: string;
|
|
116
|
+
/** Explicit presentation semantics; never inferred from message prefixes. */
|
|
117
|
+
readonly messageKind?: PiWorkflowMessageKind;
|
|
118
|
+
/** Ordered messages for truthful partial-success outcomes. */
|
|
119
|
+
readonly messages?: readonly PiWorkflowMessage[];
|
|
107
120
|
readonly detail?: string;
|
|
108
121
|
readonly selectorTitle?: string;
|
|
109
122
|
readonly options?: readonly PiWorkflowOption[];
|
|
@@ -153,11 +166,15 @@ export interface PiWorkflowInteractionHost {
|
|
|
153
166
|
startLogin?(request: PiWorkflowLoginStart): void;
|
|
154
167
|
prompt(request: PiWorkflowInteractionRequest): Promise<string | null>;
|
|
155
168
|
notify(event: PiWorkflowLoginNotification): void;
|
|
169
|
+
/** Deliver a post-login status or warning after the authentication dialog closes. */
|
|
170
|
+
publish?(message: PiWorkflowMessage): void;
|
|
156
171
|
finishLogin?(): void;
|
|
157
172
|
}
|
|
158
173
|
export interface PiWorkflowHost {
|
|
159
174
|
copyText(text: string): Promise<void>;
|
|
160
|
-
runCommand(command: string, arguments_: readonly string[]
|
|
175
|
+
runCommand(command: string, arguments_: readonly string[], options?: {
|
|
176
|
+
readonly signal?: AbortSignal;
|
|
177
|
+
}): Promise<{
|
|
161
178
|
readonly stdout: string;
|
|
162
179
|
readonly stderr: string;
|
|
163
180
|
}>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel, OwnedUiViewportSettings, OwnedUiViewportSettingsPort } from "../../../contracts/owned-ui/index.js";
|
|
2
2
|
import type { UiRouteHost } from "../../../ui/apps/index.js";
|
|
3
3
|
import { type TranscriptViewportFrame, type TranscriptViewportFrameDescriptor } from "../../../ui/components/index.js";
|
|
4
|
-
import { type PiEngineAdapter, type PiWorkflowResult } from "../engine/index.js";
|
|
4
|
+
import { type PiEngineAdapter, type PiWorkflowMessage, type PiWorkflowResult } from "../engine/index.js";
|
|
5
5
|
import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellImageAssetResolver, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../components/index.js";
|
|
6
6
|
import { type PiTuiComponentPort, type PiTuiInputSurfaceKind, type PiTuiInputCoordinationScheduler, type PiTuiInputDiagnosticsEvent, type PiTuiLayoutNode, type PiTuiTerminalPort } from "../tui-runtime/index.js";
|
|
7
7
|
import { type PreparedPrompt } from "./prompt-chips.js";
|
|
@@ -60,6 +60,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
60
60
|
readonly getRows: () => number;
|
|
61
61
|
readonly requestRender: (force?: boolean) => void;
|
|
62
62
|
readonly onViewportFrame?: (frame: TranscriptViewportFrame) => void;
|
|
63
|
+
readonly requestHyperlinkCleanup?: () => void;
|
|
63
64
|
readonly enableDockInputReuse?: boolean;
|
|
64
65
|
readonly onSubmit: (text: string) => void;
|
|
65
66
|
readonly onInterrupt: () => void;
|
|
@@ -91,6 +92,8 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
91
92
|
applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
|
|
92
93
|
render(width: number): readonly string[];
|
|
93
94
|
viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
|
|
95
|
+
/** Visible rows of the transient working-status tail, for rendering evidence. */
|
|
96
|
+
viewportTransientTailRowCount(): number;
|
|
94
97
|
hasActiveSelection(): boolean;
|
|
95
98
|
setViewportConfig(config: OwnedUiViewportSettings): void;
|
|
96
99
|
resumeViewportFollowing(): void;
|
|
@@ -106,6 +109,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
106
109
|
transcriptComponent(id: string): PiShellTranscriptComponentPort | undefined;
|
|
107
110
|
exitTranscript(width: number): string;
|
|
108
111
|
appendWorkflowStatus(message: string): void;
|
|
112
|
+
appendWorkflowMessage(message: PiWorkflowMessage): void;
|
|
109
113
|
appendWorkflowResult(result: PiWorkflowResult): void;
|
|
110
114
|
appendDaxnuts(): void;
|
|
111
115
|
toggleThinkingVisibility(): void;
|
|
@@ -44,6 +44,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
44
44
|
#inputSurfaceCoordination = "editor";
|
|
45
45
|
#dockInputReuseEnabled;
|
|
46
46
|
#dockInputCandidate = false;
|
|
47
|
+
#dockInputSnapshot;
|
|
48
|
+
#dockInputStatusSignature;
|
|
47
49
|
#visibleViewportSnapshot;
|
|
48
50
|
#fullViewportCompositions = 0;
|
|
49
51
|
#dockOnlyViewportCompositions = 0;
|
|
@@ -111,6 +113,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
111
113
|
enabled: this.#customViewport,
|
|
112
114
|
editor: this.editor,
|
|
113
115
|
requestRender: force => this.#componentRuntime.requestRender(force),
|
|
116
|
+
requestHyperlinkCleanup: () => handlers.requestHyperlinkCleanup?.(),
|
|
114
117
|
hasEditorLinks: () => this.#promptChips.hyperlinkRanges(this.editor.getText()).length > 0,
|
|
115
118
|
});
|
|
116
119
|
// Performance: stable painter identities let the neutral viewport retain row-level
|
|
@@ -229,9 +232,14 @@ export class OwnedUiSessionShellRoot {
|
|
|
229
232
|
// intentional final gutter after their right-aligned timestamp.
|
|
230
233
|
const documentWidth = width;
|
|
231
234
|
const document = this.#renderDocumentLayout(documentWidth);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
const statusRows = this.#renderStatus(width);
|
|
236
|
+
const statusSignature = statusRows.length === 0 ? undefined : `${statusRows.length}\u0000${statusRows.join("\u0000")}`;
|
|
237
|
+
const snapshot = this.#visibleViewportSnapshot;
|
|
238
|
+
const dockInputCandidate = this.#dockInputCandidate;
|
|
239
|
+
const dockInputSnapshot = dockInputCandidate ? this.#dockInputSnapshot : undefined;
|
|
240
|
+
const dockInputStatusSignature = dockInputCandidate ? this.#dockInputStatusSignature : statusSignature;
|
|
241
|
+
// Invariant: queued rows stay docked while live Working rows form one scrollable tail.
|
|
242
|
+
const scrollRows = [...document.rows, ...statusRows];
|
|
235
243
|
const dockRows = dock.rows;
|
|
236
244
|
const selectableDocumentRowCount = document.rows.length;
|
|
237
245
|
const dockStartRow = height - dockRows.length + 1;
|
|
@@ -242,29 +250,32 @@ export class OwnedUiSessionShellRoot {
|
|
|
242
250
|
rowEnd: dockStartRow + editorOffset + dock.inputRows - 1,
|
|
243
251
|
}
|
|
244
252
|
: undefined);
|
|
245
|
-
const snapshot = this.#visibleViewportSnapshot;
|
|
246
|
-
const dockInputCandidate = this.#dockInputCandidate;
|
|
247
253
|
this.#dockInputCandidate = false;
|
|
248
254
|
let frame = dockInputCandidate
|
|
249
255
|
&& snapshot !== undefined
|
|
250
256
|
&& snapshot.width === width
|
|
251
257
|
&& snapshot.height === height
|
|
252
|
-
&& snapshot.documentRows ===
|
|
253
|
-
&& snapshot.
|
|
258
|
+
&& snapshot.documentRows === (dockInputSnapshot?.documentRows ?? scrollRows)
|
|
259
|
+
&& snapshot.statusSignature === dockInputStatusSignature
|
|
260
|
+
&& snapshot.promptAnchors === (dockInputSnapshot?.promptAnchors ?? document.promptAnchors)
|
|
254
261
|
&& snapshot.dockLength === dockRows.length
|
|
255
|
-
&& snapshot.inputSurface === this.#inputSurface
|
|
256
|
-
&& snapshot.viewportRevision === this.#viewportController.presentationRevision
|
|
257
|
-
&& snapshot.selectionRevision === this.#viewportController.selectionRevision
|
|
262
|
+
&& snapshot.inputSurface === (dockInputSnapshot?.inputSurface ?? this.#inputSurface)
|
|
263
|
+
&& snapshot.viewportRevision === (dockInputSnapshot?.viewportRevision ?? this.#viewportController.presentationRevision)
|
|
264
|
+
&& snapshot.selectionRevision === (dockInputSnapshot?.selectionRevision ?? this.#viewportController.selectionRevision)
|
|
258
265
|
? this.#viewportController.composeDockOnly(dockRows, width, height)
|
|
259
266
|
: null;
|
|
267
|
+
// Performance: a still-active spinner can tick before its first delayed presentation.
|
|
268
|
+
// Its newest tail signature is recomputed below and therefore cannot hide that change.
|
|
269
|
+
if (frame !== null && dockInputStatusSignature !== statusSignature)
|
|
270
|
+
frame = null;
|
|
260
271
|
if (frame === null) {
|
|
261
272
|
frame = this.#viewportController.compose({
|
|
262
|
-
documentRows:
|
|
273
|
+
documentRows: scrollRows,
|
|
263
274
|
...(this.#viewportController.transcriptPointerSelecting
|
|
264
275
|
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
265
276
|
: {}),
|
|
266
|
-
// Invariant: selection and copying stop at the real document tail;
|
|
267
|
-
//
|
|
277
|
+
// Invariant: selection and copying stop at the real document tail; live Working
|
|
278
|
+
// rows remain transient presentation chrome at every fit boundary.
|
|
268
279
|
selectableDocumentRowCount,
|
|
269
280
|
dockRows,
|
|
270
281
|
promptAnchors: document.promptAnchors,
|
|
@@ -282,7 +293,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
282
293
|
this.#visibleViewportSnapshot = {
|
|
283
294
|
width,
|
|
284
295
|
height,
|
|
285
|
-
documentRows:
|
|
296
|
+
documentRows: scrollRows,
|
|
297
|
+
statusSignature,
|
|
298
|
+
dockInputStatusSignature,
|
|
286
299
|
promptAnchors: document.promptAnchors,
|
|
287
300
|
dockLength: dockRows.length,
|
|
288
301
|
inputSurface: this.#inputSurface,
|
|
@@ -295,6 +308,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
295
308
|
viewportFrameDescriptor() {
|
|
296
309
|
return this.#viewportController.frame?.descriptor ?? null;
|
|
297
310
|
}
|
|
311
|
+
/** Visible rows of the transient working-status tail, for rendering evidence. */
|
|
312
|
+
viewportTransientTailRowCount() {
|
|
313
|
+
return this.#viewportController.frame?.hits.transientTail.length ?? 0;
|
|
314
|
+
}
|
|
298
315
|
hasActiveSelection() {
|
|
299
316
|
return this.#viewportController.hasSelection || this.editor.hasSelection();
|
|
300
317
|
}
|
|
@@ -322,7 +339,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
322
339
|
}
|
|
323
340
|
#renderDockLayout(width) {
|
|
324
341
|
const queued = this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
|
|
325
|
-
const statusRows = this.#renderStatus(width);
|
|
342
|
+
const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
|
|
326
343
|
const transientRows = [...queued, ...statusRows];
|
|
327
344
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
328
345
|
const input = this.#inputSurface.render(width);
|
|
@@ -535,7 +552,25 @@ export class OwnedUiSessionShellRoot {
|
|
|
535
552
|
this.#workflowStatusMessages.set(id, message);
|
|
536
553
|
this.#lastWorkflowStatusId = id;
|
|
537
554
|
}
|
|
555
|
+
appendWorkflowMessage(message) {
|
|
556
|
+
if (message.kind === "status") {
|
|
557
|
+
this.appendWorkflowStatus(message.message);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
this.#lastWorkflowStatusId = undefined;
|
|
561
|
+
const prefix = message.kind === "warning" ? "Warning" : message.kind === "error" ? "Error" : undefined;
|
|
562
|
+
const color = message.kind === "accent" ? "accent" : message.kind;
|
|
563
|
+
const text = prefix === undefined ? message.message : `${prefix}: ${message.message}`;
|
|
564
|
+
this.#appendAnchoredWorkflowComponent(() => ["", ` ${piTheme().fg(color, text)}`]);
|
|
565
|
+
}
|
|
538
566
|
appendWorkflowResult(result) {
|
|
567
|
+
if (result.messages !== undefined) {
|
|
568
|
+
for (const message of result.messages)
|
|
569
|
+
this.appendWorkflowMessage(message);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (result.messageKind === "silent" || (result.outcome === "cancelled" && result.messageKind === undefined))
|
|
573
|
+
return;
|
|
539
574
|
if (result.command === "reload" && result.outcome === "completed") {
|
|
540
575
|
this.appendWorkflowStatus(result.message);
|
|
541
576
|
return;
|
|
@@ -559,19 +594,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
559
594
|
return;
|
|
560
595
|
}
|
|
561
596
|
if (result.outcome === "failed") {
|
|
562
|
-
this
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const color = warning ? "warning" : "error";
|
|
567
|
-
this.#appendAnchoredWorkflowComponent(() => ["", ` ${piTheme().fg(color, `${prefix}: ${result.message}`)}`]);
|
|
597
|
+
this.appendWorkflowMessage({
|
|
598
|
+
kind: result.messageKind === "warning" ? "warning" : "error",
|
|
599
|
+
message: result.message,
|
|
600
|
+
});
|
|
568
601
|
return;
|
|
569
602
|
}
|
|
570
603
|
if (result.outcome === "completed" && (result.command === "quit" || result.command === "compact"))
|
|
571
604
|
return;
|
|
572
605
|
if (result.command === "new" && result.outcome === "completed") {
|
|
573
|
-
this
|
|
574
|
-
this.#appendAnchoredWorkflowComponent(() => ["", ` ${piTheme().fg("accent", result.message)}`]);
|
|
606
|
+
this.appendWorkflowMessage({ kind: "accent", message: result.message });
|
|
575
607
|
return;
|
|
576
608
|
}
|
|
577
609
|
if (result.command === "name" && result.outcome === "completed") {
|
|
@@ -737,6 +769,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
737
769
|
handleInput(data) {
|
|
738
770
|
this.#dockInputCandidate = this.#customViewport && this.#dockInputReuseEnabled
|
|
739
771
|
&& classifyPiTuiInput(data, this.#inputSurfaceCoordination) === "safe";
|
|
772
|
+
if (this.#dockInputCandidate)
|
|
773
|
+
this.#captureDockInputSnapshot();
|
|
740
774
|
try {
|
|
741
775
|
this.#inputSurface.handleInput?.(data);
|
|
742
776
|
}
|
|
@@ -745,6 +779,22 @@ export class OwnedUiSessionShellRoot {
|
|
|
745
779
|
throw error;
|
|
746
780
|
}
|
|
747
781
|
}
|
|
782
|
+
#captureDockInputSnapshot() {
|
|
783
|
+
const width = Math.max(1, this.#componentRuntime.getColumns());
|
|
784
|
+
const statusRows = this.#renderStatus(width);
|
|
785
|
+
this.#dockInputStatusSignature = statusRows.length === 0 ? undefined : `${statusRows.length}\u0000${statusRows.join("\u0000")}`;
|
|
786
|
+
const snapshot = this.#visibleViewportSnapshot;
|
|
787
|
+
this.#dockInputSnapshot = snapshot === undefined
|
|
788
|
+
? undefined
|
|
789
|
+
: {
|
|
790
|
+
documentRows: snapshot.documentRows,
|
|
791
|
+
statusSignature: this.#dockInputStatusSignature,
|
|
792
|
+
promptAnchors: snapshot.promptAnchors,
|
|
793
|
+
inputSurface: this.#inputSurface,
|
|
794
|
+
viewportRevision: this.#viewportController.presentationRevision,
|
|
795
|
+
selectionRevision: this.#viewportController.selectionRevision,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
748
798
|
invalidate() {
|
|
749
799
|
this.header.invalidate();
|
|
750
800
|
this.resources.invalidate();
|
|
@@ -773,6 +823,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
773
823
|
this.#documentLayouts.clear();
|
|
774
824
|
this.#visibleViewportSnapshot = undefined;
|
|
775
825
|
this.#dockInputCandidate = false;
|
|
826
|
+
this.#dockInputSnapshot = undefined;
|
|
827
|
+
this.#dockInputStatusSignature = undefined;
|
|
776
828
|
if (this.#inputSurface !== this.editor)
|
|
777
829
|
this.#inputSurface.dispose?.();
|
|
778
830
|
this.#extensionHeader?.dispose?.();
|
|
@@ -869,7 +921,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
869
921
|
return placement === "aboveEditor" ? ["", ...rows] : rows;
|
|
870
922
|
}
|
|
871
923
|
#renderStatus(width) {
|
|
872
|
-
return this.#status.render(width);
|
|
924
|
+
return this.#customViewport ? this.#status.renderLive(width) : this.#status.render(width);
|
|
873
925
|
}
|
|
874
926
|
#renderFooter(width) {
|
|
875
927
|
return (this.#extensionFooter ?? this.#footer).render(width);
|
|
@@ -31,7 +31,7 @@ export declare class OwnedUiSessionShell {
|
|
|
31
31
|
queueFollowUp(): Promise<AdapterCommandResult>;
|
|
32
32
|
restoreQueuedInput(): void;
|
|
33
33
|
showSelector(title: string, options: readonly PiShellSelectorOption[], onSelect: (id: string) => void, onCancel?: () => void): PiTuiOverlayHandle;
|
|
34
|
-
showModelSelector(): void;
|
|
34
|
+
showModelSelector(initialSearchInput?: string): void;
|
|
35
35
|
showForkSelector(): void;
|
|
36
36
|
showLogoutSelector(): Promise<void>;
|
|
37
37
|
showLoginMethodSelector(providerReference: string): void;
|