@timurproko/a1 0.1.8-dev.322 → 0.1.8-dev.335
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 +22 -11
- package/dist/cli/dispatch.js +1 -1
- package/dist/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/service.d.ts +31 -1
- package/dist/features/prompt-history/service.js +294 -129
- package/dist/features/prompt-history/store.d.ts +2 -1
- package/dist/features/prompt-history/store.js +17 -4
- package/dist/features/prompt-history/worker.js +6 -3
- package/dist/foundation/launch-context/index.d.ts +36 -6
- package/dist/foundation/launch-context/index.js +64 -11
- package/dist/foundation/launch-guardian/main.js +2 -1
- package/dist/foundation/release/bootstrap.d.ts +7 -0
- package/dist/foundation/release/bootstrap.js +95 -19
- package/dist/foundation/release/cohort-state.js +0 -3
- package/dist/foundation/release/dependency-certification-retention.d.ts +7 -0
- package/dist/foundation/release/dependency-certification-retention.js +88 -0
- package/dist/foundation/release/dependency-certification.d.ts +24 -0
- package/dist/foundation/release/dependency-certification.js +208 -0
- package/dist/foundation/release/dependency-layer.d.ts +3 -4
- package/dist/foundation/release/dependency-layer.js +6 -37
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/release-gc.js +72 -44
- package/dist/foundation/release/release-store.d.ts +2 -0
- package/dist/foundation/release/release-store.js +4 -3
- package/dist/foundation/release/restart-certification.js +16 -5
- package/dist/foundation/release/update-launch.d.ts +6 -0
- package/dist/foundation/release/update-launch.js +17 -0
- package/dist/foundation/release/update.d.ts +2 -0
- package/dist/foundation/release/update.js +21 -27
- package/dist/foundation/release/warmup.js +16 -5
- package/dist/foundation/supervision/main.js +5 -2
- package/dist/foundation/supervision/server.js +40 -13
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +51 -27
- package/dist/integrations/pi/engine/adapter.d.ts +20 -0
- package/dist/integrations/pi/engine/adapter.js +223 -87
- package/dist/integrations/pi/engine/pending-delivery.d.ts +26 -0
- package/dist/integrations/pi/engine/pending-delivery.js +149 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +38 -25
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +0 -2
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +3 -8
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell.js +9 -6
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -2
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +4 -5
- package/dist/integrations/pi/session-ui/text-paste.d.ts +5 -0
- package/dist/integrations/pi/session-ui/text-paste.js +16 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +2 -2
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +82 -39
- 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.json +1 -1
- package/docs/architecture/internal-naming.md +5 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +45 -7
- package/docs/manual-code-streaming-cleanup.md +88 -0
- package/package.json +1 -1
|
@@ -112,5 +112,7 @@ export type UpdateOwnershipAction = "clean-dead-record" | "leave-running" | "end
|
|
|
112
112
|
*/
|
|
113
113
|
export declare function planUpdateOwnership(ownership: "live-verified" | "dead", runsFromRetainedRelease: boolean): UpdateOwnershipAction;
|
|
114
114
|
export declare function createUpdateLifecycleCoordinator(environment?: NodeJS.ProcessEnv, fileSystem?: UpdateFileSystem, output?: UpdateOutput): UpdateLifecycleCoordinator;
|
|
115
|
+
/** Renders the terminal-only update meter; the visual preview shares this exact frame. */
|
|
116
|
+
export declare function renderUpdateProgressBar(percent: number): string;
|
|
115
117
|
export declare function runSelfUpdate(options: SelfUpdateOptions): Promise<number>;
|
|
116
118
|
export declare function assertUpdatePerformanceBudget(evidence: UpdatePerformanceEvidence, maximumPostNpmDurationMs?: number): void;
|
|
@@ -5,7 +5,7 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
|
5
5
|
import crossSpawn from "cross-spawn";
|
|
6
6
|
import { valid as validSemver } from "semver";
|
|
7
7
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
8
|
-
import { certifyMaterializedRelease, probeOwnership, readEndpointMetadata, removeEndpointArtifacts,
|
|
8
|
+
import { certifyMaterializedRelease, probeOwnership, readEndpointMetadata, removeEndpointArtifacts, ensureSupervisor, waitForProcessExit, } from "./bootstrap.js";
|
|
9
9
|
import { resolveCohortEndpoint, resolveProductPaths } from "../lifecycle/index.js";
|
|
10
10
|
import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
|
|
11
11
|
import { CohortStateStore } from "./cohort-state.js";
|
|
@@ -13,7 +13,6 @@ import { cleanupVerifiedOwner, processIsAlive } from "./process-cleanup.js";
|
|
|
13
13
|
import { materializeRelease, readMaterializedRelease } from "./release-store.js";
|
|
14
14
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
15
15
|
import { warmMaterializedRelease } from "./warmup.js";
|
|
16
|
-
import { assertCurrentLaunchContract } from "../launch-context/index.js";
|
|
17
16
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
18
17
|
import { removeUpdateRecoveryCapsule, runProtectedPackageReplacement } from "./update-recovery.js";
|
|
19
18
|
export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
|
|
@@ -78,8 +77,6 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
78
77
|
async targetIsActive(targetVersion) {
|
|
79
78
|
const state = await stateStore.read();
|
|
80
79
|
const activeId = state.references.active;
|
|
81
|
-
if (activeId)
|
|
82
|
-
assertCurrentLaunchContract(state.releases[activeId] ?? {});
|
|
83
80
|
if (!activeId || state.releases[activeId]?.packageVersion !== targetVersion)
|
|
84
81
|
return false;
|
|
85
82
|
const endpoint = await readActiveEndpoint(paths, activeId, environment);
|
|
@@ -88,8 +85,6 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
88
85
|
async shutdownVerifiedOwners(targetVersion) {
|
|
89
86
|
const state = await stateStore.read();
|
|
90
87
|
const activeId = state.references.active;
|
|
91
|
-
if (activeId)
|
|
92
|
-
assertCurrentLaunchContract(state.releases[activeId] ?? {});
|
|
93
88
|
const priorActiveVersion = activeId ? state.releases[activeId]?.packageVersion ?? null : null;
|
|
94
89
|
const owner = await readActiveEndpoint(paths, activeId, environment);
|
|
95
90
|
if (!owner)
|
|
@@ -168,17 +163,17 @@ export function createUpdateLifecycleCoordinator(environment = process.env, file
|
|
|
168
163
|
const diagnostics = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
169
164
|
await stateStore.approve(candidate.releaseId, diagnostics);
|
|
170
165
|
await phase("certified");
|
|
171
|
-
await stateStore.activate(candidate.releaseId);
|
|
172
|
-
await phase("active-reference-committed");
|
|
173
166
|
onWarmup?.("started");
|
|
174
167
|
await warmMaterializedRelease(candidate, environment);
|
|
175
168
|
onWarmup?.("completed");
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
await ensureSupervisor(candidate, environment);
|
|
170
|
+
// Invariant: warmup and authenticated readiness precede changing the active reference.
|
|
171
|
+
await stateStore.activate(candidate.releaseId);
|
|
172
|
+
await phase("active-reference-committed");
|
|
178
173
|
},
|
|
179
174
|
};
|
|
180
175
|
}
|
|
181
|
-
const PROGRESS_BAR_WIDTH =
|
|
176
|
+
const PROGRESS_BAR_WIDTH = 40;
|
|
182
177
|
const PROGRESS_TICK_MS = 200;
|
|
183
178
|
/**
|
|
184
179
|
* Copying the release owns 78–92 and is reported file by file, so the bar crosses
|
|
@@ -191,10 +186,15 @@ const ACTIVATION_PROGRESS = {
|
|
|
191
186
|
certified: { at: 94, creepTo: 96 },
|
|
192
187
|
"active-reference-committed": { at: 96, creepTo: 99 },
|
|
193
188
|
};
|
|
194
|
-
|
|
189
|
+
/** Renders the terminal-only update meter; the visual preview shares this exact frame. */
|
|
190
|
+
export function renderUpdateProgressBar(percent) {
|
|
195
191
|
const bounded = Math.min(100, Math.max(0, Math.round(percent)));
|
|
196
192
|
const filled = Math.round((bounded / 100) * PROGRESS_BAR_WIDTH);
|
|
197
|
-
|
|
193
|
+
// Rationale: a gray line, a darker gray track, and one space before the percentage.
|
|
194
|
+
// Explicit RGB keeps both grays neutral even when the terminal remaps its ANSI palette.
|
|
195
|
+
const gray = "\u001b[38;2;128;128;128m";
|
|
196
|
+
const track = "\u001b[38;2;102;102;102m";
|
|
197
|
+
return `${gray}${"━".repeat(filled)}${track}${"─".repeat(PROGRESS_BAR_WIDTH - filled)}${gray} ${bounded}%\u001b[39m`;
|
|
198
198
|
}
|
|
199
199
|
function createUpdateProgress(output, enabled) {
|
|
200
200
|
let visible = false;
|
|
@@ -207,7 +207,7 @@ function createUpdateProgress(output, enabled) {
|
|
|
207
207
|
return;
|
|
208
208
|
shown = rounded;
|
|
209
209
|
visible = true;
|
|
210
|
-
output.stdout(`\r${
|
|
210
|
+
output.stdout(`\r${renderUpdateProgressBar(rounded)}`);
|
|
211
211
|
};
|
|
212
212
|
const stopCreep = () => {
|
|
213
213
|
if (timer !== null) {
|
|
@@ -380,11 +380,7 @@ export async function runSelfUpdate(options) {
|
|
|
380
380
|
: async () => await scheduleReleaseCleanup(paths.dataDir, paths));
|
|
381
381
|
let transaction = await transactionStore.read();
|
|
382
382
|
try {
|
|
383
|
-
if (await lifecycle.targetIsActive(targetVersion)) {
|
|
384
|
-
if (transaction?.status === "active") {
|
|
385
|
-
await transactionStore.advance("supervisor-verified");
|
|
386
|
-
await transactionStore.finish("completed");
|
|
387
|
-
}
|
|
383
|
+
if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
|
|
388
384
|
await maintenance();
|
|
389
385
|
await transactionStore.clearCompleted();
|
|
390
386
|
output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
|
|
@@ -453,7 +449,7 @@ export async function runSelfUpdate(options) {
|
|
|
453
449
|
transaction = await transactionStore.advance("package-installed");
|
|
454
450
|
if (replacement.cancelled) {
|
|
455
451
|
progress.clear();
|
|
456
|
-
output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the
|
|
452
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic("update cancelled safely; the launcher is available and the transaction can be resumed.")}\n`);
|
|
457
453
|
return 130;
|
|
458
454
|
}
|
|
459
455
|
if (replacement.outcome !== "installed") {
|
|
@@ -515,13 +511,14 @@ export async function runSelfUpdate(options) {
|
|
|
515
511
|
options.onPhaseTiming?.({ phase: "supervisor-verified", durationMs: Math.max(0, now() - activationPhaseStartedAt) });
|
|
516
512
|
const transactionStartedAt = now();
|
|
517
513
|
await transactionStore.advance("supervisor-verified");
|
|
518
|
-
await transactionStore.finish("completed");
|
|
519
514
|
if (transaction.recovery?.capsulePath)
|
|
520
515
|
await removeUpdateRecoveryCapsule(paths.dataDir, transaction.transactionId);
|
|
521
516
|
// Invariant: successful output follows the durable cleanup disposition. Slow recursive
|
|
522
517
|
// removal belongs to the detached worker started by this maintenance coordinator.
|
|
523
518
|
await maintenance();
|
|
524
|
-
|
|
519
|
+
// Concurrency: this is the launch cutover. No failure-prone update work follows success.
|
|
520
|
+
await transactionStore.finish("completed");
|
|
521
|
+
await transactionStore.clearCompleted().catch(() => undefined);
|
|
525
522
|
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
526
523
|
progress.finish();
|
|
527
524
|
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}\n`);
|
|
@@ -614,11 +611,8 @@ async function rollbackPriorCohort(dataDir, environment, priorReleaseId) {
|
|
|
614
611
|
throw new Error(`prior release ${priorReleaseId} is not the recorded rollback cohort`);
|
|
615
612
|
}
|
|
616
613
|
const release = await readMaterializedRelease(prior.releaseRoot);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
// a cohort that survived the update keeps serving the work it already had.
|
|
620
|
-
const startup = await startSupervisor(release, environment);
|
|
621
|
-
await waitForVerifiedEndpoint(resolveCohortEndpoint(paths, release.releaseId, environment).endpointMetadataPath, release, 8_000, startup);
|
|
614
|
+
// Invariant: rollback reuses a surviving prior cohort rather than racing its occupied endpoint.
|
|
615
|
+
await ensureSupervisor(release, environment);
|
|
622
616
|
return "rolled back";
|
|
623
617
|
}
|
|
624
618
|
function phaseBefore(current, target) {
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { resolveReleaseEntryPoint } from "./release-store.js";
|
|
3
3
|
import { releaseEnvironment } from "./bootstrap.js";
|
|
4
|
-
import {
|
|
4
|
+
import { launchContractTarget, launchEnvironmentKeys } from "../launch-context/index.js";
|
|
5
|
+
const WARMUP_DIAGNOSTIC_LIMIT = 2_000;
|
|
6
|
+
/** Summarize the child's own failure text without letting an unbounded graph error escape. */
|
|
7
|
+
function warmupDiagnostics(text) {
|
|
8
|
+
const lines = text.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0);
|
|
9
|
+
return lines.length === 0 ? "" : `: ${lines.slice(0, 4).join(" | ").slice(0, WARMUP_DIAGNOSTIC_LIMIT)}`;
|
|
10
|
+
}
|
|
5
11
|
/** Import the exact immutable startup graph in a terminal-free bounded child process. */
|
|
6
12
|
export async function warmMaterializedRelease(release, environment, timeoutMs = 30_000) {
|
|
7
|
-
assertCurrentLaunchContract(release);
|
|
8
13
|
const entry = await resolveReleaseEntryPoint(release, "bin/warmup.js");
|
|
9
14
|
await new Promise((resolvePromise, rejectPromise) => {
|
|
15
|
+
// Rationale: the child still gets no terminal, but a discarded failure reason leaves an
|
|
16
|
+
// update reporting only an exit status. Its own diagnostics are read and bounded so the
|
|
17
|
+
// transaction journal records why the graph could not be imported.
|
|
10
18
|
const child = spawn(process.execPath, [entry], {
|
|
11
|
-
env: { ...releaseEnvironment(environment, release), [
|
|
12
|
-
stdio: "ignore",
|
|
19
|
+
env: { ...releaseEnvironment(environment, release), [launchEnvironmentKeys(launchContractTarget(release)).immutableWarmup]: "1" },
|
|
20
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
13
21
|
windowsHide: true,
|
|
14
22
|
});
|
|
23
|
+
let diagnostics = "";
|
|
24
|
+
child.stderr?.setEncoding("utf8");
|
|
25
|
+
child.stderr?.on("data", chunk => { diagnostics = `${diagnostics}${chunk}`.slice(-WARMUP_DIAGNOSTIC_LIMIT); });
|
|
15
26
|
let settled = false;
|
|
16
27
|
const finish = (error) => {
|
|
17
28
|
if (settled)
|
|
@@ -26,7 +37,7 @@ export async function warmMaterializedRelease(release, environment, timeoutMs =
|
|
|
26
37
|
child.once("error", error => finish(error));
|
|
27
38
|
child.once("close", (code, signal) => finish(code === 0
|
|
28
39
|
? undefined
|
|
29
|
-
: new Error(`immutable startup warmup exited with ${code === null ? signal ?? "unknown status" : `status ${code}`}`)));
|
|
40
|
+
: new Error(`immutable startup warmup exited with ${code === null ? signal ?? "unknown status" : `status ${code}`}${warmupDiagnostics(diagnostics)}`)));
|
|
30
41
|
const timer = setTimeout(() => {
|
|
31
42
|
child.kill();
|
|
32
43
|
finish(new Error(`immutable startup warmup exceeded ${timeoutMs}ms`));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { assertImmutableExecutionRoot, CohortStateStore, readCertifiedReleaseManifest, recordParentCertifiedRelease } from "../release/index.js";
|
|
4
|
+
import { assertImmutableExecutionRoot, CohortStateStore, readCertifiedReleaseManifest, recordParentCertifiedRelease, selectSupervisorLaunchReleaseId, UpdateTransactionStore } from "../release/index.js";
|
|
5
5
|
import { publishSupervisorStartupResult, supervisorStartupFailure, supervisorStartupReady, supervisorStartupResultPath } from "../lifecycle/index.js";
|
|
6
6
|
import { ControlStore } from "../storage/index.js";
|
|
7
7
|
import { resolveCohortEndpoint, resolveProductPaths } from "./paths.js";
|
|
@@ -38,7 +38,10 @@ export async function runSupervisor(arguments_ = []) {
|
|
|
38
38
|
const bootNonce = randomUUID();
|
|
39
39
|
const store = new ControlStore(paths.databasePath, bootNonce);
|
|
40
40
|
const cohortState = new CohortStateStore(paths.dataDir);
|
|
41
|
-
server = new SupervisorServer(store, paths, release, bootNonce, undefined, undefined, undefined, async () =>
|
|
41
|
+
server = new SupervisorServer(store, paths, release, bootNonce, undefined, undefined, undefined, async () => {
|
|
42
|
+
const transaction = await new UpdateTransactionStore(paths.dataDir).read();
|
|
43
|
+
return selectSupervisorLaunchReleaseId(await cohortState.read(), transaction);
|
|
44
|
+
});
|
|
42
45
|
stage = "endpoint-listen";
|
|
43
46
|
await server.listen();
|
|
44
47
|
if (resultPath && attemptId)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { chmod, lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { chmod, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createConnection, createServer } from "node:net";
|
|
4
4
|
import { platform } from "node:os";
|
|
5
5
|
import { dirname } from "node:path";
|
|
@@ -54,6 +54,7 @@ export class SupervisorServer {
|
|
|
54
54
|
startedAt = new Date().toISOString();
|
|
55
55
|
paths;
|
|
56
56
|
#server = null;
|
|
57
|
+
#ownsEndpoint = false;
|
|
57
58
|
#instances = new Map();
|
|
58
59
|
#instanceOwners = new Map();
|
|
59
60
|
#clientIds = new Map();
|
|
@@ -104,15 +105,24 @@ export class SupervisorServer {
|
|
|
104
105
|
if (platform() !== "win32") {
|
|
105
106
|
if (this.paths.endpointDirectory)
|
|
106
107
|
await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const existing = await lstat(this.paths.endpoint).catch(error => {
|
|
109
|
+
if (error.code === "ENOENT")
|
|
110
|
+
return null;
|
|
111
|
+
throw error;
|
|
112
|
+
});
|
|
113
|
+
if (existing) {
|
|
114
|
+
if (await endpointIsLive(this.paths.endpoint)) {
|
|
115
|
+
throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
|
|
116
|
+
}
|
|
117
|
+
await rm(this.paths.endpoint, { force: true });
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
const server = createServer(socket => this.#attach(socket));
|
|
112
121
|
this.#server = server;
|
|
113
122
|
await new Promise((resolve, reject) => {
|
|
114
123
|
server.once("error", reject);
|
|
115
124
|
server.listen(this.paths.endpoint, () => {
|
|
125
|
+
this.#ownsEndpoint = true;
|
|
116
126
|
server.off("error", reject);
|
|
117
127
|
resolve();
|
|
118
128
|
});
|
|
@@ -146,7 +156,9 @@ export class SupervisorServer {
|
|
|
146
156
|
await this.close(false);
|
|
147
157
|
}
|
|
148
158
|
async close(stopAgents = false) {
|
|
149
|
-
if (
|
|
159
|
+
if (this.#closing)
|
|
160
|
+
return;
|
|
161
|
+
if (stopAgents && this.#ownsEndpoint && !await this.#drainInstances("update", this.shutdownDeadlineMs)) {
|
|
150
162
|
throw new Error("active launch instances did not release ownership within the shutdown deadline");
|
|
151
163
|
}
|
|
152
164
|
this.#closing = true;
|
|
@@ -155,14 +167,27 @@ export class SupervisorServer {
|
|
|
155
167
|
this.#supersededPoll = null;
|
|
156
168
|
for (const client of this.#clients)
|
|
157
169
|
client.destroy();
|
|
158
|
-
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
try {
|
|
171
|
+
await this.#metadataWrites.catch(() => undefined);
|
|
172
|
+
if (this.#ownsEndpoint) {
|
|
173
|
+
// Concurrency: remove only our publication, before releasing the listening endpoint.
|
|
174
|
+
// A contender that failed to bind must never erase the successful owner's discovery file.
|
|
175
|
+
const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
|
|
176
|
+
if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
|
|
177
|
+
await rm(this.paths.endpointMetadataPath, { force: true });
|
|
178
|
+
}
|
|
179
|
+
// Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
|
|
180
|
+
// let a replacement bind before the old server finishes releasing that path.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
// Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
|
|
185
|
+
if (this.#server)
|
|
186
|
+
await new Promise(resolve => this.#server?.close(() => resolve()));
|
|
187
|
+
this.#server = null;
|
|
188
|
+
this.#ownsEndpoint = false;
|
|
189
|
+
this.store.close();
|
|
190
|
+
}
|
|
166
191
|
}
|
|
167
192
|
async closeForReleaseReplacement(stopAgents) {
|
|
168
193
|
await this.close(stopAgents);
|
|
@@ -473,6 +498,8 @@ export class SupervisorServer {
|
|
|
473
498
|
return [...this.#instances.keys()];
|
|
474
499
|
}
|
|
475
500
|
#writeEndpointMetadata() {
|
|
501
|
+
if (this.#closing)
|
|
502
|
+
return Promise.resolve();
|
|
476
503
|
const liveInstanceIds = this.#liveInstanceIds();
|
|
477
504
|
const metadata = {
|
|
478
505
|
schema: PRODUCT_IDENTITY.protocol.supervisorSchema,
|
|
@@ -8,7 +8,7 @@ import type { PiShellClipboardContent, PiShellEditorTextRange } from "./shell-sh
|
|
|
8
8
|
* Pi editor port and gives future interactions one registration point.
|
|
9
9
|
*/
|
|
10
10
|
export interface OwnedEditorUxInterceptor {
|
|
11
|
-
handleInput(data: string, next: () => void): void;
|
|
11
|
+
handleInput(data: string, next: (data?: string) => void): void;
|
|
12
12
|
render(width: number, next: () => string[]): string[];
|
|
13
13
|
reset(): void;
|
|
14
14
|
handlePointer?(event: OwnedEditorPointerEvent): boolean;
|
|
@@ -8,15 +8,15 @@ export class OwnedEditorUxInterception {
|
|
|
8
8
|
this.fallback = fallback;
|
|
9
9
|
}
|
|
10
10
|
handleInput(data) {
|
|
11
|
-
const invoke = (index) => {
|
|
11
|
+
const invoke = (index, input) => {
|
|
12
12
|
const interceptor = this.interceptors[index];
|
|
13
13
|
if (interceptor === undefined) {
|
|
14
|
-
this.fallback.handleInput(
|
|
14
|
+
this.fallback.handleInput(input);
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
interceptor.handleInput(
|
|
17
|
+
interceptor.handleInput(input, (replacement = input) => invoke(index + 1, replacement));
|
|
18
18
|
};
|
|
19
|
-
invoke(0);
|
|
19
|
+
invoke(0, data);
|
|
20
20
|
}
|
|
21
21
|
render(width) {
|
|
22
22
|
const invoke = (index) => {
|
|
@@ -65,6 +65,7 @@ class PromptSelectionInterceptor {
|
|
|
65
65
|
#redoStack = [];
|
|
66
66
|
#selectionRevision = 0;
|
|
67
67
|
#pasteGeneration = 0;
|
|
68
|
+
#terminalPaste;
|
|
68
69
|
#wordDirection;
|
|
69
70
|
#geometry;
|
|
70
71
|
constructor(editor, keybindings, options) {
|
|
@@ -74,6 +75,8 @@ class PromptSelectionInterceptor {
|
|
|
74
75
|
installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
|
|
75
76
|
}
|
|
76
77
|
handleInput(data, next) {
|
|
78
|
+
if (this.#handleTerminalPaste(data, next))
|
|
79
|
+
return;
|
|
77
80
|
if (this.keybindings.matches(data, "owned.editor.selectAll")) {
|
|
78
81
|
this.#selectAll();
|
|
79
82
|
return;
|
|
@@ -105,23 +108,6 @@ class PromptSelectionInterceptor {
|
|
|
105
108
|
this.#redo();
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
108
|
-
const terminalPaste = bracketedPasteContent(data);
|
|
109
|
-
if (terminalPaste !== undefined) {
|
|
110
|
-
if (terminalPaste.length === 0) {
|
|
111
|
-
this.pasteClipboard();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const transformed = this.options.transformPastedContent({ kind: "text", text: terminalPaste });
|
|
115
|
-
if (transformed !== terminalPaste) {
|
|
116
|
-
if (this.#orderedSelection() !== undefined)
|
|
117
|
-
this.#replaceSelection(transformed);
|
|
118
|
-
else
|
|
119
|
-
this.editor.insertTextAtCursor(transformed);
|
|
120
|
-
this.#redoStack = [];
|
|
121
|
-
this.#requestRender();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
111
|
if (this.keybindings.matches(data, "tui.editor.undo")) {
|
|
126
112
|
const before = this.#snapshot();
|
|
127
113
|
this.#clearSelection(false);
|
|
@@ -291,7 +277,10 @@ class PromptSelectionInterceptor {
|
|
|
291
277
|
this.#redoStack = [];
|
|
292
278
|
this.#selectionRevision += 1;
|
|
293
279
|
}
|
|
294
|
-
cancelPendingPastes() {
|
|
280
|
+
cancelPendingPastes() {
|
|
281
|
+
this.#pasteGeneration += 1;
|
|
282
|
+
this.#terminalPaste = undefined;
|
|
283
|
+
}
|
|
295
284
|
hasSelection() {
|
|
296
285
|
return this.#activeRange() !== undefined;
|
|
297
286
|
}
|
|
@@ -450,6 +439,46 @@ class PromptSelectionInterceptor {
|
|
|
450
439
|
this.#selectionRevision += 1;
|
|
451
440
|
this.#requestRender();
|
|
452
441
|
}
|
|
442
|
+
#handleTerminalPaste(data, next) {
|
|
443
|
+
const start = data.indexOf("\x1b[200~");
|
|
444
|
+
if (this.#terminalPaste === undefined) {
|
|
445
|
+
if (start < 0)
|
|
446
|
+
return false;
|
|
447
|
+
if (start > 0) {
|
|
448
|
+
const before = data.slice(0, start);
|
|
449
|
+
this.handleInput(before, (replacement = before) => next(replacement));
|
|
450
|
+
}
|
|
451
|
+
this.#terminalPaste = "";
|
|
452
|
+
data = data.slice(start + 6);
|
|
453
|
+
}
|
|
454
|
+
// Compatibility: the terminal's StdinBuffer assembles split opening delimiters;
|
|
455
|
+
// retain the body here too so editor-level chunks cannot allocate a native paste ID.
|
|
456
|
+
this.#terminalPaste += data;
|
|
457
|
+
const end = this.#terminalPaste.indexOf("\x1b[201~");
|
|
458
|
+
if (end < 0)
|
|
459
|
+
return true;
|
|
460
|
+
const text = this.#terminalPaste.slice(0, end);
|
|
461
|
+
const remaining = this.#terminalPaste.slice(end + 6);
|
|
462
|
+
this.#terminalPaste = undefined;
|
|
463
|
+
if (text.length === 0)
|
|
464
|
+
this.pasteClipboard();
|
|
465
|
+
else {
|
|
466
|
+
const transformed = this.options.transformPastedContent({ kind: "text", text });
|
|
467
|
+
if (transformed === text && this.#orderedSelection() === undefined)
|
|
468
|
+
next(`\x1b[200~${text}\x1b[201~`);
|
|
469
|
+
else {
|
|
470
|
+
if (this.#orderedSelection() !== undefined)
|
|
471
|
+
this.#replaceSelection(transformed);
|
|
472
|
+
else
|
|
473
|
+
this.editor.insertTextAtCursor(transformed);
|
|
474
|
+
this.#redoStack = [];
|
|
475
|
+
this.#requestRender();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (remaining.length > 0)
|
|
479
|
+
this.handleInput(remaining, (replacement = remaining) => next(replacement));
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
453
482
|
#pasteFromClipboard() {
|
|
454
483
|
if (this.options.beginClipboardPaste !== undefined) {
|
|
455
484
|
const generation = this.#pasteGeneration;
|
|
@@ -802,11 +831,6 @@ function isEditorSegment(value) {
|
|
|
802
831
|
const input = Reflect.get(value, "input");
|
|
803
832
|
return typeof segment === "string" && typeof index === "number" && typeof input === "string";
|
|
804
833
|
}
|
|
805
|
-
function bracketedPasteContent(data) {
|
|
806
|
-
const start = data.indexOf("\u001b[200~");
|
|
807
|
-
const end = data.indexOf("\u001b[201~", start + 6);
|
|
808
|
-
return start < 0 || end < 0 ? undefined : data.slice(start + 6, end);
|
|
809
|
-
}
|
|
810
834
|
function insertedText(data) {
|
|
811
835
|
if (!data || data.includes("\u001b[200~"))
|
|
812
836
|
return undefined;
|
|
@@ -6,6 +6,10 @@ import { PiSettingsIntegration } from "./settings-integration.js";
|
|
|
6
6
|
import type { PiSettingOwnerHandlers } from "./settings-effects.js";
|
|
7
7
|
import type { PiProjectTrustPreflightPrompt } from "./project-trust-preflight.js";
|
|
8
8
|
import type { AgentSettingOwner } from "../../../contracts/agent-engine/index.js";
|
|
9
|
+
/** Explicit flush failure when required delivery was interrupted rather than completed. */
|
|
10
|
+
export declare class EngineDeliveryError extends Error {
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
9
13
|
export interface PiEngineRuntimeFactoryInput {
|
|
10
14
|
readonly cwd: string;
|
|
11
15
|
readonly agentDir: string;
|
|
@@ -133,6 +137,8 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
|
|
|
133
137
|
setWorkflowInteractionHost(interaction: PiWorkflowInteractionHost): void;
|
|
134
138
|
get sessionId(): string;
|
|
135
139
|
get sessionGeneration(): number;
|
|
140
|
+
/** Actual session replacements, excluding delivery-only invalidation of callbacks. */
|
|
141
|
+
get sessionBindingGeneration(): number;
|
|
136
142
|
get cwd(): string;
|
|
137
143
|
get agentDir(): string;
|
|
138
144
|
resolveTranscriptImage(assetId: string): OwnedUiImageAttachment | null;
|
|
@@ -142,6 +148,20 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
|
|
|
142
148
|
generate(request: OwnedUiPromptSuggestionRequest): Promise<OwnedUiPromptSuggestionResult>;
|
|
143
149
|
start(): Promise<OwnedUiSessionViewModel>;
|
|
144
150
|
onEvent(listener: (event: OwnedUiEvent) => void): () => void;
|
|
151
|
+
/** Developer-only pressure evidence; never mirrored into visible diagnostic/status arrays. */
|
|
152
|
+
deliveryDiagnostics(): {
|
|
153
|
+
overloads: number;
|
|
154
|
+
recovering: boolean;
|
|
155
|
+
pendingCommands: number;
|
|
156
|
+
reservedOutcomes: number;
|
|
157
|
+
invalidatedEvents: number;
|
|
158
|
+
pending: number;
|
|
159
|
+
bytes: number;
|
|
160
|
+
protected: number;
|
|
161
|
+
superseded: number;
|
|
162
|
+
peakNodes: number;
|
|
163
|
+
peakBytes: number;
|
|
164
|
+
};
|
|
145
165
|
flushEvents(): Promise<void>;
|
|
146
166
|
nonVisualResources(): readonly OwnedPiResourceSummary[];
|
|
147
167
|
extensionResources(): readonly OwnedPiExtensionResourceSummary[];
|