@timurproko/a1 0.1.8-dev.290 → 0.1.8-dev.299

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 +9 -0
  2. package/dist/composition/owned-ui.js +12 -1
  3. package/dist/contracts/owned-ui/index.d.ts +1 -0
  4. package/dist/contracts/owned-ui/index.js +1 -0
  5. package/dist/contracts/owned-ui/prompt-history.d.ts +37 -0
  6. package/dist/contracts/owned-ui/prompt-history.js +23 -0
  7. package/dist/features/prompt-history/index.d.ts +2 -0
  8. package/dist/features/prompt-history/index.js +2 -0
  9. package/dist/features/prompt-history/paths.d.ts +4 -0
  10. package/dist/features/prompt-history/paths.js +16 -0
  11. package/dist/features/prompt-history/service.d.ts +24 -0
  12. package/dist/features/prompt-history/service.js +260 -0
  13. package/dist/features/prompt-history/store.d.ts +15 -0
  14. package/dist/features/prompt-history/store.js +166 -0
  15. package/dist/features/prompt-history/worker.d.ts +9 -0
  16. package/dist/features/prompt-history/worker.js +42 -0
  17. package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
  18. package/dist/integrations/pi/components/editor-interaction.js +1 -0
  19. package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
  20. package/dist/integrations/pi/components/history-editor-loader.js +5 -0
  21. package/dist/integrations/pi/components/index.d.ts +1 -0
  22. package/dist/integrations/pi/components/index.js +1 -0
  23. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
  24. package/dist/integrations/pi/components/owned-editor-ux.js +41 -19
  25. package/dist/integrations/pi/components/shell-editor-autocomplete.js +9 -1
  26. package/dist/integrations/pi/components/shell-shared-facade.d.ts +6 -0
  27. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +13 -9
  28. package/dist/integrations/pi/components/upstream/components/owned-editor.js +137 -133
  29. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
  30. package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
  31. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
  32. package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
  33. package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
  34. package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
  35. package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
  36. package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
  37. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
  38. package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
  39. package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
  40. package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
  41. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +1 -0
  42. package/dist/integrations/pi/session-ui/prompt-chips.js +3 -0
  43. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +30 -0
  44. package/dist/integrations/pi/session-ui/prompt-history-controller.js +147 -0
  45. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +8 -0
  46. package/dist/integrations/pi/session-ui/session-shell-root.js +3 -0
  47. package/dist/integrations/pi/session-ui/session-shell.js +40 -5
  48. package/dist/native/darwin-arm64/manifest.json +1 -1
  49. package/dist/native/linux-x64/manifest.json +1 -1
  50. package/dist/native/win32-x64/manifest.json +2 -2
  51. package/dist/native/win32-x64/process-guardian.exe +0 -0
  52. package/dist/product-identity.d.ts +1 -1
  53. package/dist/product-identity.js +1 -1
  54. package/dist/product-identity.json +1 -0
  55. package/dist/ui/settings/declarations.js +18 -0
  56. package/dist/ui/settings/session.js +13 -4
  57. package/docs/architecture/boundaries.md +1 -0
  58. package/docs/architecture/history-editor-provenance.md +96 -0
  59. package/docs/architecture/project-structure.md +1 -0
  60. package/docs/architecture/resource-and-data-policy.md +22 -1
  61. package/docs/features/prompt-history.md +121 -0
  62. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { PromptHistoryController } from "./prompt-history-controller.js";
1
2
  import { PRODUCT_TEXT } from "../../../product-identity.js";
2
3
  import { boundedCleanup } from "../../../foundation/terminal-cleanup/index.js";
3
4
  import { assertOwnedUiCommand, assertPromptImages, ImageAttachmentError } from "../../../contracts/owned-ui/index.js";
@@ -21,6 +22,7 @@ export class OwnedUiSessionShell {
21
22
  #unsubscribe;
22
23
  #unsubscribePromptSuggestions;
23
24
  #promptSuggestions;
25
+ #promptHistory = null;
24
26
  #extensionBridge;
25
27
  #stopped;
26
28
  #resolveStopped;
@@ -78,6 +80,8 @@ export class OwnedUiSessionShell {
78
80
  replacementSurfaceActive: !this.root.usesDefaultInputSurface(),
79
81
  }),
80
82
  enableDockInputReuse: options.inputPresentation?.viewportReuse !== false,
83
+ persistentHistory: this.#customViewport && options.promptHistory !== undefined,
84
+ ...(options.promptHistory === undefined ? {} : { historyEditor: options.promptHistory.editor }),
81
85
  onSubmit: text => { void this.submit(text).catch(() => this.#reportSubmissionError()); },
82
86
  onPasteRejected: error => this.#reportSubmissionError(error),
83
87
  onInterrupt: () => { void this.interrupt(); },
@@ -98,6 +102,7 @@ export class OwnedUiSessionShell {
98
102
  onInputSurfaceChanged: () => {
99
103
  this.root.clearViewportPointerState();
100
104
  promptSuggestionController?.invalidate();
105
+ this.#promptHistory?.synchronize();
101
106
  },
102
107
  onCopyText: text => {
103
108
  runtime?.writeControl(`\u001b]52;c;${Buffer.from(text, "utf8").toString("base64")}\u0007`);
@@ -309,6 +314,17 @@ export class OwnedUiSessionShell {
309
314
  this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
310
315
  } },
311
316
  });
317
+ if (this.#customViewport && options.promptHistory !== undefined) {
318
+ this.#promptHistory = new PromptHistoryController({
319
+ editor: this.root.editor,
320
+ store: options.promptHistory.store,
321
+ limit: options.promptHistory.limit,
322
+ fallback: this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []),
323
+ active: () => this.root.usesDefaultInputSurface(),
324
+ render: () => this.runtime.requestRender(),
325
+ failure: message => this.root.addExtensionNotification(message, "warning"),
326
+ });
327
+ }
312
328
  this.#extensionBridge = createPiExtensionUiBridge({
313
329
  runtime: {
314
330
  getColumns: () => this.runtime.viewport().columns,
@@ -419,6 +435,7 @@ export class OwnedUiSessionShell {
419
435
  return;
420
436
  this.#started = true;
421
437
  this.runtime.start();
438
+ this.#promptHistory?.start();
422
439
  this.#syncTerminalProgress(this.view());
423
440
  if (this.#customViewport)
424
441
  this.#setPointerReporting(true);
@@ -472,6 +489,16 @@ export class OwnedUiSessionShell {
472
489
  for (const item of this.#waitingImages.values())
473
490
  item.controller.abort();
474
491
  }
492
+ #rememberInput(text, kind) {
493
+ this.root.editor.addToHistory(text);
494
+ if (this.#promptHistory !== null) {
495
+ const reusable = this.root.prepareHistoryText(text);
496
+ if (reusable.length === 0)
497
+ this.#promptHistory.rememberRecovery(text);
498
+ else
499
+ this.#promptHistory.capture(reusable, kind, this.#cwd, this.backend.sessionId);
500
+ }
501
+ }
475
502
  async #submit(text) {
476
503
  this.#promptSuggestions?.invalidate();
477
504
  const displayInput = text.trim();
@@ -486,7 +513,7 @@ export class OwnedUiSessionShell {
486
513
  const excludeFromContext = input.startsWith("!!");
487
514
  const command = input.slice(excludeFromContext ? 2 : 1).trim();
488
515
  if (command) {
489
- this.root.editor.addToHistory(displayInput);
516
+ this.#rememberInput(displayInput, "bash");
490
517
  try {
491
518
  const result = await this.backend.executeBashWorkflow(command, excludeFromContext);
492
519
  const workflow = {
@@ -508,7 +535,7 @@ export class OwnedUiSessionShell {
508
535
  }
509
536
  }
510
537
  if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
511
- this.root.editor.addToHistory(displayInput);
538
+ this.#rememberInput(displayInput, "steer");
512
539
  this.#compactionQueue.push({
513
540
  text: input,
514
541
  draft: displayInput,
@@ -520,7 +547,7 @@ export class OwnedUiSessionShell {
520
547
  return { outcome: "completed", diagnostic: null };
521
548
  }
522
549
  const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
523
- this.root.editor.addToHistory(displayInput);
550
+ this.#rememberInput(displayInput, type);
524
551
  this.root.resumeViewportFollowing();
525
552
  return this.#execute({
526
553
  type,
@@ -633,7 +660,7 @@ export class OwnedUiSessionShell {
633
660
  const prepared = this.root.preparePromptSubmission(displayInput);
634
661
  assertPromptImages(prepared.images);
635
662
  const text = prepared.text.trim();
636
- this.root.editor.addToHistory(displayInput);
663
+ this.#rememberInput(displayInput, "follow-up");
637
664
  if (this.root.editor.getText() === draft)
638
665
  this.root.editor.setText("");
639
666
  this.root.resumeViewportFollowing();
@@ -1157,6 +1184,8 @@ export class OwnedUiSessionShell {
1157
1184
  failures.push(error);
1158
1185
  } };
1159
1186
  let pasteCleanup = Promise.resolve();
1187
+ let historyCleanup = Promise.resolve(true);
1188
+ attempt(() => { historyCleanup = this.#promptHistory?.close() ?? Promise.resolve(true); });
1160
1189
  attempt(() => { pasteCleanup = this.root.disposePendingPastes(); });
1161
1190
  attempt(() => this.root.clearViewportPointerState());
1162
1191
  attempt(() => this.#setPointerReporting(false, true));
@@ -1182,6 +1211,9 @@ export class OwnedUiSessionShell {
1182
1211
  attempt(() => this.#extensionBridge.dispose());
1183
1212
  // Invariant: terminal restoration precedes any potentially stalled backend teardown.
1184
1213
  await this.runtime.dispose().catch(error => failures.push(error));
1214
+ const historySaved = await historyCleanup.catch(() => false);
1215
+ if (!historySaved)
1216
+ this.runtime.writeAfterStop("Prompt history could not finish saving before exit.\n");
1185
1217
  await boundedCleanup(() => pasteCleanup).catch(error => failures.push(error));
1186
1218
  await boundedCleanup(() => this.backend.unbindExtensionUi()).catch(error => failures.push(error));
1187
1219
  if (failures.length > 0)
@@ -1219,6 +1251,7 @@ export class OwnedUiSessionShell {
1219
1251
  this.root.resetPendingPastes();
1220
1252
  this.#promptSuggestions?.invalidate();
1221
1253
  this.#sessionGeneration = this.backend.sessionGeneration;
1254
+ this.#promptHistory?.reset(view.transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
1222
1255
  this.#activeLoginDialog = undefined;
1223
1256
  this.#extensionBridge.reset();
1224
1257
  // Invariant: a replaced session takes its transient viewport and owned-route state with it.
@@ -1377,7 +1410,7 @@ export class OwnedUiSessionShell {
1377
1410
  if (isWorkflowRoute(name))
1378
1411
  return this.runWorkflow({ command: name, argument });
1379
1412
  // Compatibility: unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
1380
- this.root.editor.addToHistory(text);
1413
+ this.#rememberInput(text, "slash");
1381
1414
  this.root.resumeViewportFollowing();
1382
1415
  return this.#execute({
1383
1416
  type: this.view().lifecycle === "busy" ? "steer" : "prompt",
@@ -1523,6 +1556,7 @@ export class OwnedUiSessionShell {
1523
1556
  catch {
1524
1557
  // Security: dispatch might already have reached the provider. Never retry automatically.
1525
1558
  this.root.editor.addToHistory(draft);
1559
+ this.#promptHistory?.rememberRecovery(draft);
1526
1560
  this.#reportSubmissionError(undefined, "Submission failed; delivery is uncertain. Check the conversation before retrying. Press Up to recover the draft.");
1527
1561
  return { outcome: "failed", diagnostic: "submission delivery is uncertain" };
1528
1562
  }
@@ -1540,6 +1574,7 @@ export class OwnedUiSessionShell {
1540
1574
  }
1541
1575
  #recoverSubmission(draft, revision, error) {
1542
1576
  this.root.editor.addToHistory(draft);
1577
+ this.#promptHistory?.rememberRecovery(draft);
1543
1578
  // Concurrency: never overwrite input typed (even typed and cleared) after this submission.
1544
1579
  if (revision === this.#editorRevision && this.root.editor.getText().length === 0)
1545
1580
  this.root.editor.setText(draft);
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-11T07:23:35.242Z",
8
+ "builtAt": "2026-09-11T15:30:41.585Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-11T07:23:38.591Z",
8
+ "builtAt": "2026-09-11T15:30:45.869Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-11T07:23:55.165Z",
8
+ "builtAt": "2026-09-11T15:31:16.823Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "57d07c4be5d43295bb5af2ddafc482f8f79b47c7245c953222690b2e91d302fc",
11
+ "sha256": "78359342ae74e12b31ce05aff4a6b06f25d6e9777b081822c860604d674ec08d",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -3,7 +3,7 @@ declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirect
3
3
  declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
4
4
  declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
5
5
  declare const MANIFEST_KEYS: readonly ["releaseFilename", "packageFilename"];
6
- declare const PROTOCOL_KEYS: readonly ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "releaseCohortSchema", "updateJournalSchema"];
6
+ declare const PROTOCOL_KEYS: readonly ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "promptHistorySchema", "releaseCohortSchema", "updateJournalSchema"];
7
7
  declare const EVIDENCE_KEYS: readonly ["nativeSpikeSchema", "terminalProvenanceSchema", "terminalProofSchema", "stableReleaseSchema", "previewReleaseSchema", "releaseCertificationSchema", "previewPlatformVerdictSchema", "piSourceLedgerSchema", "piComponentParitySchema", "piEventFrameParitySchema", "startupTraceSchema", "dependencyLayerSchema", "dependencyLayerCertificationSchema", "runtimePayloadSchema"];
8
8
  declare const ARTIFACT_KEYS: readonly ["cliEntry", "supervisorEntry", "guardianEntry", "uiEntry", "nativeExecutable", "nativeCrate", "processGuardianExecutable", "releaseTarballStem", "diagnosticStem"];
9
9
  type StringRecord<Keys extends readonly string[]> = {
@@ -40,7 +40,7 @@ const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporary
40
40
  const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
41
41
  const ENDPOINT_KEYS = ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
42
42
  const MANIFEST_KEYS = ["releaseFilename", "packageFilename"];
43
- const PROTOCOL_KEYS = ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "releaseCohortSchema", "updateJournalSchema"];
43
+ const PROTOCOL_KEYS = ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "promptHistorySchema", "releaseCohortSchema", "updateJournalSchema"];
44
44
  const EVIDENCE_KEYS = ["nativeSpikeSchema", "terminalProvenanceSchema", "terminalProofSchema", "stableReleaseSchema", "previewReleaseSchema", "releaseCertificationSchema", "previewPlatformVerdictSchema", "piSourceLedgerSchema", "piComponentParitySchema", "piEventFrameParitySchema", "startupTraceSchema", "dependencyLayerSchema", "dependencyLayerCertificationSchema", "runtimePayloadSchema"];
45
45
  const ARTIFACT_KEYS = ["cliEntry", "supervisorEntry", "guardianEntry", "uiEntry", "nativeExecutable", "nativeCrate", "processGuardianExecutable", "releaseTarballStem", "diagnosticStem"];
46
46
  const ROOT_KEYS = ["schema", "displayName", "commandName", "packageName", "filesystem", "environment", "state", "endpoint", "manifest", "protocol", "evidence", "artifacts"];
@@ -71,6 +71,7 @@
71
71
  "nativeHostSchema": "a1-native-host-v1",
72
72
  "structuredAgentSchema": "a1-structured-agent-v1",
73
73
  "controlStoreSchema": "a1-control-store-v1",
74
+ "promptHistorySchema": "a1-prompt-history-v1",
74
75
  "releaseCohortSchema": "a1-release-cohort-v1",
75
76
  "updateJournalSchema": "a1-update-journal-v1"
76
77
  },
@@ -30,6 +30,24 @@ export const OWNED_UI_SETTING_DECLARATIONS = Object.freeze([
30
30
  defaultValue: "normal",
31
31
  allowedValues: Object.freeze(["normal", "fast", "high"]),
32
32
  }),
33
+ Object.freeze({
34
+ id: "promptHistoryEnabled",
35
+ label: "Persistent history",
36
+ section: Object.freeze({ id: "history", title: "History" }),
37
+ description: "Retain reusable prompts across sessions. Applies on next start; disabling does not erase saved history or stop existing instances.",
38
+ application: "restart",
39
+ defaultValue: true,
40
+ allowedValues: Object.freeze([true, false]),
41
+ }),
42
+ Object.freeze({
43
+ id: "promptHistoryMaxItems",
44
+ label: "History limit",
45
+ section: Object.freeze({ id: "history", title: "History" }),
46
+ description: "Maximum recent unique prompts after next start. Byte limits also apply; increasing the limit cannot restore pruned entries.",
47
+ application: "restart",
48
+ defaultValue: 100,
49
+ allowedValues: Object.freeze([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]),
50
+ }),
33
51
  Object.freeze({
34
52
  id: "promptSuggestions",
35
53
  label: "Prompt suggestions",
@@ -10,12 +10,17 @@ export class OwnedUiSettingsSession {
10
10
  #resolution;
11
11
  #agentSnapshot = null;
12
12
  #pending = new Map();
13
+ #restartEffective = new Map();
13
14
  constructor(options) {
14
15
  this.#store = options.store;
15
16
  this.#agent = options.agent ?? null;
16
17
  this.#agentProvider = options.agentProvider ?? null;
17
18
  this.#hiddenAgentSettingIds = new Set(options.hiddenAgentSettingIds ?? []);
18
19
  this.#resolution = options.store.read();
20
+ for (const setting of this.#resolution.settings) {
21
+ if (setting.declaration.application === "restart")
22
+ this.#restartEffective.set(setting.declaration.id, setting.value);
23
+ }
19
24
  }
20
25
  get resolution() {
21
26
  return this.#resolution;
@@ -27,10 +32,15 @@ export class OwnedUiSettingsSession {
27
32
  this.#notify();
28
33
  }
29
34
  sections() {
30
- return buildOwnedUiSettingsSections({ resolution: this.#resolution, agent: this.#agentSnapshot });
35
+ return buildOwnedUiSettingsSections({ resolution: this.#resolution, agent: this.#agentSnapshot }).map(section => ({
36
+ ...section,
37
+ entries: section.entries.map(entry => entry.backend === "a1" && this.#restartEffective.has(entry.id)
38
+ ? { ...entry, effectiveValue: this.#restartEffective.get(entry.id) }
39
+ : entry),
40
+ }));
31
41
  }
32
42
  value(id) {
33
- return this.#resolution.settings.find(setting => setting.declaration.id === id)?.value ?? null;
43
+ return this.#restartEffective.get(id) ?? this.#resolution.settings.find(setting => setting.declaration.id === id)?.value ?? null;
34
44
  }
35
45
  pendingValue(id) {
36
46
  return this.#pending.get(id) ?? null;
@@ -61,9 +71,8 @@ export class OwnedUiSettingsSession {
61
71
  const previousSetting = previous.settings.find(setting => setting.declaration.id === id);
62
72
  if (previousSetting?.declaration.application === "restart") {
63
73
  this.#pending.set(id, value);
64
- this.#resolution = previous;
65
74
  this.#notify();
66
- return changed("deferred", "next-start", value, previousSetting.value);
75
+ return changed("deferred", "next-start", value, this.#restartEffective.get(id));
67
76
  }
68
77
  this.#pending.delete(id);
69
78
  this.#notify();
@@ -28,6 +28,7 @@ The owned Pi-backed surface is not an arbitrary-CLI terminal multiplexer. A feat
28
28
  - `structured-agent-runtime`: planned typed event/command/snapshot runtime. It must not infer semantics from terminal text, own pseudoterminals, or reconstruct screens.
29
29
  - `native-host-protocol`: bounded typed local boundary for terminal-host identity, topology revisions, lifecycle, and recovery. Terminal bytes, per-event child input, and rendered cells are forbidden across it.
30
30
  - `protocol`: additive control handshake, bounded line framing, authenticated launch-instance commands, typed stop intent, snapshots, and command results.
31
+ - `prompt-history`: narrowly typed user-input retention in separate profile-owned application-data databases, with bounded worker queues and no supervisor/control-store payloads. The editor's owned history state machine remains inside the Pi component boundary; the session UI only coordinates snapshots and classified submissions.
31
32
  - `storage`: SQLite migrations, prior-boot reconciliation, and plural launch-instance persistence. Legacy foreground rows are historical migration input and never authorize current ownership.
32
33
  - `supervisor`: endpoint identity, plural cohort ownership, per-instance reconciliation, and aggregate release shutdown coordination. It owns no terminal surface.
33
34
  - `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-session-ui-integration`: isolate pinned Pi engine and presentation knowledge behind neutral contracts; product features do not import them directly. The session UI render root assembles semantic document and dock rows, while its focused viewport controller owns follow state, pointer routing, selection, and interaction timers. Owned-app route lifecycle belongs to the neutral `ui-apps` owner and is only hosted by the Pi session UI.
@@ -0,0 +1,96 @@
1
+ # History editor source boundary
2
+
3
+ The history-enabled default editor is derived from Pi 0.84.2 at commit
4
+ `914cf1472e715297caa30db4b9535d534a9eb718`, repository
5
+ <https://github.com/earendil-works/pi>. The approved scope is
6
+ `openspec/changes/add-persistent-prompt-history/design.md`, decision 2a.
7
+
8
+ ## Source and import inventory
9
+
10
+ The owning directory is `src/integrations/pi/components/upstream/history/`.
11
+
12
+ | Owned unit | Upstream source under `packages/tui/src/` | Retained behavior |
13
+ |---|---|---|
14
+ | `editor-core.ts` | `components/editor.ts` | Editor state machine, layout, history boundaries, paste backing, autocomplete, undo integration |
15
+ | `kill-ring.ts` | `kill-ring.ts` | Kill/yank accumulation and rotation |
16
+ | `undo-stack.ts` | `undo-stack.ts` | Clone-on-push undo storage |
17
+ | `word-navigation.ts` | `word-navigation.ts` | Word/atomic-segment navigation |
18
+ | `text-helpers.ts` | selected declarations in `utils.ts` | Shared local segmenters, CJK break classification, punctuation/whitespace predicates only |
19
+ | `printable-key.ts` | selected declarations in `keys.ts` | Pure modifyOtherKeys printable helper and modifier constants; delegates Kitty decoding to the public export |
20
+
21
+ All reusable terminal APIs come from `#pi-tui`: keybinding state, `matchesKey`,
22
+ `decodeKittyPrintable`, `CURSOR_MARKER`, width/slicing, `SelectList`, and public
23
+ component/autocomplete types. No terminal runtime, parser stack, terminal
24
+ renderer, package loader, or dependency copy is introduced. The terminal-package
25
+ alias and exported constructors remain unchanged.
26
+
27
+ `node scripts/pi/extract-history-editor-source.mjs` resolves the terminal package
28
+ from pinned Pi's entry and extracts references and hashes into the ignored
29
+ `.artifacts/history-editor-upstream/` directory. It never overwrites owned code.
30
+ The source ledger records upstream and owned destination hashes. Source maps are
31
+ used only by provenance tooling, not by the shipped runtime. Changed source must
32
+ be explicitly reconciled, not automatically adopted in an engine upgrade.
33
+
34
+ ## Owned changes and collaborator boundaries
35
+
36
+ The core uses public imports, a distinct `HistoryEditorCore` class, and strict
37
+ TypeScript declaration adjustments. Persistent history is opt-in: its typed
38
+ snapshot installation and observation methods preserve input and undo state,
39
+ freeze an active browse cycle, and change caret placement only in that mode.
40
+ The editor renders position/overflow in its existing border. The position label
41
+ uses an injected neutral status-text style (`dim`), while the surrounding rules
42
+ retain the active input-border color, as clarified during manual review. Recall temporarily
43
+ separates the draft's live paste backing from recalled literal text, restoring it
44
+ on return; ordinary pinned mode remains source-equivalent.
45
+
46
+ The integration inventory is:
47
+
48
+ - `shell-editor-autocomplete.ts`: choose the default editor through a typed
49
+ component interface, never cast the owned core to the concrete Pi editor.
50
+ - `upstream/components/owned-editor.ts`: retain its pinned-based comparison and
51
+ disabled-history route; share app actions and owned prefix/suggestion behavior
52
+ through typed composition for the history-enabled route.
53
+ - `owned-editor-ux.ts`: selection, atomic segmentation, visual-line geometry,
54
+ provisional paste completion, and undo repair need a typed owned collaborator
55
+ boundary. Existing compatibility access on the pinned route is not permission
56
+ for new private access on the owned core.
57
+ - `shell-extension-ui.ts`: public text, shortcut, autocomplete, and factory
58
+ behavior remain extension-owned. A custom editor is not patched for recall.
59
+ - `session-shell-root.ts` and `session-shell.ts`: deliver semantic snapshot and
60
+ submission events; they must not own a second history index, mutable draft,
61
+ undo stack, or rendered-border parser.
62
+
63
+ `test/integrations/pi/components/history-editor-core.test.ts` independently runs
64
+ real pinned and owned editors against the same input tapes, comparing rows,
65
+ text/expanded text, cursor, autocomplete, and submissions. Separate fixtures
66
+ cover v2 directional placement and numbering, deferred snapshots, draft/undo
67
+ restoration, and literal paste-marker collisions. This is editor-core evidence,
68
+ not acceptance of the complete persistent-history feature.
69
+
70
+ ## License
71
+
72
+ Upstream license at the recorded commit:
73
+
74
+ ```text
75
+ MIT License
76
+
77
+ Copyright (c) 2025 Mario Zechner
78
+
79
+ Permission is hereby granted, free of charge, to any person obtaining a copy
80
+ of this software and associated documentation files (the "Software"), to deal
81
+ in the Software without restriction, including without limitation the rights
82
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
83
+ copies of the Software, and to permit persons to whom the Software is
84
+ furnished to do so, subject to the following conditions:
85
+
86
+ The above copyright notice and this permission notice shall be included in all
87
+ copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
90
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
91
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
92
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
93
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
94
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
95
+ SOFTWARE.
96
+ ```
@@ -13,6 +13,7 @@ src/
13
13
  features/
14
14
  launch/ launch profiles, profile paths, and runtime selection
15
15
  owned-ui/ owned screens, settings application, diagnostics, and runtime lifecycle
16
+ prompt-history/ profile-isolated prompt retention and bounded SQLite worker lifecycle
16
17
  workspace/ multi-agent presentation, reducer state, routing, and persistence orchestration
17
18
  contracts/
18
19
  agent-engine/ dependency-free agent engine, session, package, and capability ports
@@ -31,7 +31,8 @@ Every asynchronous consumer must implement a finite queue or window. Backpressur
31
31
  | Class | Examples | Storage policy |
32
32
  |---|---|---|
33
33
  | Workspace metadata | IDs, names, lifecycle, capability versions, topology revisions, recovery references | May persist in the control store |
34
- | Structured payloads | Messages, tool calls, snapshots, attachments | Bound and process; persist only under a future typed retention policy |
34
+ | Structured payloads | Messages, tool calls, snapshots, attachments | Bound and process; arbitrary payload persistence remains unsupported |
35
+ | User-authored prompt recall | Canonical interactive input and bounded provenance | May persist only through the profile-isolated prompt-history policy below, never in the control store |
35
36
  | Terminal content | PTY bytes, scrollback, selection, rendered cells | Never persist in A1's control store or diagnostics |
36
37
  | Host topology metadata | Window/tab/pane/session IDs, layout shape, dimensions, revision | May persist; not terminal content |
37
38
  | Native proof evidence | Artifact hashes, source revisions, workloads, latency/resource measurements, paint diagnostics | May persist in versioned evidence files without terminal content |
@@ -42,6 +43,26 @@ Every asynchronous consumer must implement a finite queue or window. Backpressur
42
43
 
43
44
  Sanitization must classify before logging or storing. Unknown or untyped values are potentially sensitive by default and must not be persisted. Terminal bytes must never be written to logs as a debugging shortcut.
44
45
 
46
+ ## Typed prompt-history retention
47
+
48
+ The `prompt-history` feature owns only reusable interactive user input, not
49
+ structured-message archives. It stores one independent profile database under
50
+ `<dataDir>/history`, never agent resources or cache/release directories. Text is
51
+ unencrypted and potentially sensitive. Retain up to 100 unique entries (configured
52
+ 10-100), at most 1 MiB per entry and 8 MiB total canonical text. Background queues
53
+ are limited to 32 writes/8 MiB per instance; reads are coalesced, lock retries are
54
+ bounded to one second off-thread, and graceful shutdown drains for at most two
55
+ seconds. SQLite page/journal limits and a 64 MiB maintenance threshold bound
56
+ physical growth separately from text retention.
57
+
58
+ Only typed canonical text, submission identity, recency/timestamp, input kind,
59
+ and bounded cwd/session provenance are allowed. No image bytes, transformed
60
+ agent prompts, environment/credential copies, tool results, terminal streams, or
61
+ private input in diagnostics are permitted. Disabling persistence is next-start
62
+ and does not erase data or stop other live instances. Cache and release cleanup
63
+ must not touch history. See [prompt history](../features/prompt-history.md) for
64
+ retention, failure semantics, profile identity, and stopped-instance removal.
65
+
45
66
  ## Required architectural outcomes
46
67
 
47
68
  - Structured/RPC semantics must not be inferred from ANSI text, terminal timing, or visual content.
@@ -0,0 +1,121 @@
1
+ # Persistent prompt history
2
+
3
+ Bare A1 recalls recent prompts across sessions and projects within the same A1
4
+ profile. It is input recall, not conversation recovery or agent memory. The Pi
5
+ comparison (`a1 pi`) retains its ordinary current-session history.
6
+
7
+ ## Recall
8
+
9
+ Use Up/Down at the editor's normal history boundaries. Up enters older prompts
10
+ with the caret at the end; Down enters newer prompts at the beginning. Moving
11
+ past the newest restores your draft, including its cursor and live pasted text.
12
+ Multiline cursor movement and autocomplete keep their existing priority.
13
+
14
+ The existing editor border shows `History 100/100` for the newest of 100 entries
15
+ and `History 1/100` for the oldest. The label uses the status bar's neutral grey
16
+ (`dim` theme role), independently of the thinking/bash colors on the input bars. Repeated prompts move forward instead of
17
+ creating duplicates. Other instances refresh in the background; the selected
18
+ history and its count stay fixed until you leave that browse cycle. An
19
+ extension-provided custom editor keeps its own behavior; the default editor is
20
+ resynchronized when restored.
21
+
22
+ Ordinary prompts, steering, follow-ups, queued inputs, nonempty bash commands,
23
+ and user-entered skill/template/extension prompt routes are eligible. Opening
24
+ settings or session/workflow screens does not add history. Loading a transcript,
25
+ retrying a dispatch internally, draining a queue, or displaying a suggestion does
26
+ not create another entry. An explicit user submission is retained even if the
27
+ provider later rejects it or the turn is interrupted.
28
+
29
+ Text pastes, URL targets, and file paths remain reusable after restart. History
30
+ keeps the user invocation before template/extension expansion, not the expanded
31
+ request. Image-bearing prompts retain only their text; images are not reattached.
32
+ Image-only prompts do not create saved entries. No v2, Pi, or Claude history is
33
+ imported automatically.
34
+
35
+ ## Storage and privacy
36
+
37
+ | Platform | Default durable location |
38
+ |---|---|
39
+ | Windows | `%LOCALAPPDATA%/a1/history/<profile-id>.sqlite3` |
40
+ | Current Unix policy (including macOS) | `$XDG_DATA_HOME/a1/history/<profile-id>.sqlite3`, or `~/.local/share/a1/history/<profile-id>.sqlite3` |
41
+ | Explicit override | `<A1_DATA_DIR>/history/<profile-id>.sqlite3` |
42
+
43
+ The profile filename is `a1-` followed by a SHA-256 digest of a versioned tuple
44
+ containing the launch kind and normalized effective agent-profile path. It is
45
+ independent of project, session, checkout, and release. Windows ordinary case and
46
+ separator variants normalize together. Different profile roots use different
47
+ files. Moving a profile, unresolved symlink aliases, or selecting a different
48
+ data root can select a different history; A1 never silently merges or imports it.
49
+
50
+ The database is separate from `control.sqlite3`, agent resources, and disposable
51
+ caches. Upgrades, release rollback, cache cleanup, and conversation deletion do
52
+ not clear it. SQLite may keep `-wal` and `-shm` sidecars beside the database.
53
+
54
+ **History is unencrypted potentially sensitive user text.** Owner permissions
55
+ are restricted where supported; Windows uses the account's application-data
56
+ ACLs. These protections do not make it safe to submit secrets. Credential files,
57
+ image data, arbitrary environment values, assistant/tool output, and terminal
58
+ streams are not copied into this store. Prompt content and private provenance
59
+ must not appear in history diagnostics or test evidence.
60
+
61
+ ## Settings and limits
62
+
63
+ The History section in `/settings` provides:
64
+
65
+ - **Persistent history:** on by default; applies on the next start. Off disables
66
+ storage and cross-session recall for subsequent launches, not current-session
67
+ recovery. Existing enabled instances keep running until closed.
68
+ - **History limit:** 10-100 in increments of 10, default 100; applies when a new
69
+ enabled instance starts. All writers then use that profile store's updated
70
+ retention limit. Raising the limit cannot restore pruned entries.
71
+
72
+ The settings use A1's existing settings document, not Pi's settings or a separate
73
+ history configuration file. Disabling persistence does not delete saved data;
74
+ re-enabling makes compatible retained entries available again.
75
+
76
+ Additional bounds protect memory and disk:
77
+
78
+ - At most 1 MiB UTF-8 per persisted prompt, never silently truncated.
79
+ - At most 8 MiB retained canonical text, evicting oldest entries even below the
80
+ configured count.
81
+ - At most 32 queued/in-flight writes and 8 MiB queued text per instance.
82
+ - Provenance: up to 8 KiB cwd and 256 bytes session identity; oversized optional
83
+ provenance is omitted.
84
+ - Short off-thread transactions, a one-second lock retry window, and coalesced
85
+ refresh with at most one read in flight.
86
+ - SQLite page/journal limits and a 64 MiB database/sidecar maintenance threshold;
87
+ text limits exclude transient SQLite overhead.
88
+
89
+ Persistence failure leaves prompt execution and local recall usable. A corrupt,
90
+ wrong-profile, or newer-schema database is preserved, not silently recreated.
91
+ A committed entry survives restart. A hard kill before asynchronous commit can
92
+ lose the newest queued entries. Graceful exit attempts to drain writes within
93
+ two seconds and reports an incomplete flush without preventing exit.
94
+
95
+ ## Remove saved history
96
+
97
+ First close **every instance using the selected profile**. Then remove only that
98
+ profile's `.sqlite3` file and its matching `-wal` and `-shm` sidecars, if present.
99
+ Do not delete an open database or the whole application-data root. Disabling the
100
+ setting alone is not an erase operation. There is no interactive clear/export
101
+ command in this release.
102
+
103
+ ## Manual acceptance
104
+
105
+ Build the checkout, then launch it through `./scripts/dev`. Use an isolated
106
+ `A1_DATA_DIR` for disposable history checks; keep that same directory on each
107
+ restart and in both concurrent terminals. Test:
108
+
109
+ 1. Submit two text prompts and one repeated prompt; start `/new`, then restart.
110
+ The latest unique prompts remain recallable.
111
+ 2. Browse multiline input with a draft present. Check v2 caret placement,
112
+ newest/oldest numbering, and draft restoration.
113
+ 3. Submit from a second instance while the first browses. The first selection
114
+ stays fixed; the update appears on a later safe browse cycle.
115
+ 4. Recall pasted text after restart; image tokens must not promise reattachment.
116
+ 5. Save both History settings, restart, and verify effective values and opt-out.
117
+ 6. Use a second isolated effective profile and verify no sharing. Run
118
+ `./scripts/dev pi` and verify its ordinary history/editor remain unchanged.
119
+
120
+ Source ownership and differential editor evidence are described in
121
+ [history-editor-provenance.md](../architecture/history-editor-provenance.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.290",
3
+ "version": "0.1.8-dev.299",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",