@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.
Files changed (62) hide show
  1. package/README.md +22 -11
  2. package/dist/cli/dispatch.js +1 -1
  3. package/dist/features/owned-ui/project-trust-prompt.js +1 -1
  4. package/dist/features/owned-ui/settings-app.js +1 -1
  5. package/dist/features/prompt-history/service.d.ts +31 -1
  6. package/dist/features/prompt-history/service.js +294 -129
  7. package/dist/features/prompt-history/store.d.ts +2 -1
  8. package/dist/features/prompt-history/store.js +17 -4
  9. package/dist/features/prompt-history/worker.js +6 -3
  10. package/dist/foundation/launch-context/index.d.ts +36 -6
  11. package/dist/foundation/launch-context/index.js +64 -11
  12. package/dist/foundation/launch-guardian/main.js +2 -1
  13. package/dist/foundation/release/bootstrap.d.ts +7 -0
  14. package/dist/foundation/release/bootstrap.js +95 -19
  15. package/dist/foundation/release/cohort-state.js +0 -3
  16. package/dist/foundation/release/dependency-certification-retention.d.ts +7 -0
  17. package/dist/foundation/release/dependency-certification-retention.js +88 -0
  18. package/dist/foundation/release/dependency-certification.d.ts +24 -0
  19. package/dist/foundation/release/dependency-certification.js +208 -0
  20. package/dist/foundation/release/dependency-layer.d.ts +3 -4
  21. package/dist/foundation/release/dependency-layer.js +6 -37
  22. package/dist/foundation/release/index.d.ts +1 -0
  23. package/dist/foundation/release/index.js +1 -0
  24. package/dist/foundation/release/release-gc.js +72 -44
  25. package/dist/foundation/release/release-store.d.ts +2 -0
  26. package/dist/foundation/release/release-store.js +4 -3
  27. package/dist/foundation/release/restart-certification.js +16 -5
  28. package/dist/foundation/release/update-launch.d.ts +6 -0
  29. package/dist/foundation/release/update-launch.js +17 -0
  30. package/dist/foundation/release/update.d.ts +2 -0
  31. package/dist/foundation/release/update.js +21 -27
  32. package/dist/foundation/release/warmup.js +16 -5
  33. package/dist/foundation/supervision/main.js +5 -2
  34. package/dist/foundation/supervision/server.js +40 -13
  35. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
  36. package/dist/integrations/pi/components/owned-editor-ux.js +51 -27
  37. package/dist/integrations/pi/engine/adapter.d.ts +20 -0
  38. package/dist/integrations/pi/engine/adapter.js +223 -87
  39. package/dist/integrations/pi/engine/pending-delivery.d.ts +26 -0
  40. package/dist/integrations/pi/engine/pending-delivery.js +149 -0
  41. package/dist/integrations/pi/session-ui/prompt-chips.js +38 -25
  42. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +0 -2
  43. package/dist/integrations/pi/session-ui/prompt-history-controller.js +3 -8
  44. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
  45. package/dist/integrations/pi/session-ui/session-shell-root.js +5 -1
  46. package/dist/integrations/pi/session-ui/session-shell.js +9 -6
  47. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -2
  48. package/dist/integrations/pi/session-ui/session-viewport-controller.js +4 -5
  49. package/dist/integrations/pi/session-ui/text-paste.d.ts +5 -0
  50. package/dist/integrations/pi/session-ui/text-paste.js +16 -0
  51. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +2 -2
  52. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +82 -39
  53. package/dist/native/darwin-arm64/manifest.json +1 -1
  54. package/dist/native/linux-x64/manifest.json +1 -1
  55. package/dist/native/win32-x64/manifest.json +2 -2
  56. package/dist/native/win32-x64/process-guardian.exe +0 -0
  57. package/dist/product-identity.json +1 -1
  58. package/docs/architecture/internal-naming.md +5 -3
  59. package/docs/architecture/toolchain.md +1 -1
  60. package/docs/ci-release-runbook.md +45 -7
  61. package/docs/manual-code-streaming-cleanup.md +88 -0
  62. 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, startSupervisor, waitForProcessExit, waitForVerifiedEndpoint, } from "./bootstrap.js";
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
- const startup = await startSupervisor(candidate, environment);
177
- await waitForVerifiedEndpoint(resolveCohortEndpoint(paths, candidate.releaseId, environment).endpointMetadataPath, candidate, 8_000, startup);
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 = 39;
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
- function renderProgressBar(percent) {
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
- return `${"█".repeat(filled)}${"░".repeat(PROGRESS_BAR_WIDTH - filled)} ${bounded}%`;
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${renderProgressBar(rounded)}`);
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 A1 launcher is available and the transaction can be resumed.")}\n`);
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
- await transactionStore.clearCompleted();
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
- const paths = resolveProductPaths(environment);
618
- // Invariant: rollback re-points the active reference and starts the prior cohort on its own endpoint;
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 { PRIVATE_ENVIRONMENT, assertCurrentLaunchContract } from "../launch-context/index.js";
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), [PRIVATE_ENVIRONMENT.immutableWarmup]: "1" },
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 () => (await cohortState.read()).references.active);
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
- if (await endpointIsLive(this.paths.endpoint))
108
- throw new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`));
109
- await rm(this.paths.endpoint, { force: true });
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 (stopAgents && !await this.#drainInstances("update", this.shutdownDeadlineMs)) {
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
- if (this.#server)
159
- await new Promise(resolve => this.#server?.close(() => resolve()));
160
- this.#server = null;
161
- this.store.close();
162
- await this.#metadataWrites;
163
- await rm(this.paths.endpointMetadataPath, { force: true });
164
- if (platform() !== "win32")
165
- await rm(this.paths.endpoint, { force: true });
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(data);
14
+ this.fallback.handleInput(input);
15
15
  return;
16
16
  }
17
- interceptor.handleInput(data, () => invoke(index + 1));
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() { this.#pasteGeneration += 1; }
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[];