@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.
- package/README.md +9 -0
- package/dist/composition/owned-ui.js +12 -1
- package/dist/contracts/owned-ui/index.d.ts +1 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-history.d.ts +37 -0
- package/dist/contracts/owned-ui/prompt-history.js +23 -0
- package/dist/features/prompt-history/index.d.ts +2 -0
- package/dist/features/prompt-history/index.js +2 -0
- package/dist/features/prompt-history/paths.d.ts +4 -0
- package/dist/features/prompt-history/paths.js +16 -0
- package/dist/features/prompt-history/service.d.ts +24 -0
- package/dist/features/prompt-history/service.js +260 -0
- package/dist/features/prompt-history/store.d.ts +15 -0
- package/dist/features/prompt-history/store.js +166 -0
- package/dist/features/prompt-history/worker.d.ts +9 -0
- package/dist/features/prompt-history/worker.js +42 -0
- package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
- package/dist/integrations/pi/components/editor-interaction.js +1 -0
- package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
- package/dist/integrations/pi/components/history-editor-loader.js +5 -0
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +41 -19
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +9 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +6 -0
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +13 -9
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +137 -133
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
- package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
- package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
- package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
- package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +3 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +30 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +147 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +8 -0
- package/dist/integrations/pi/session-ui/session-shell-root.js +3 -0
- package/dist/integrations/pi/session-ui/session-shell.js +40 -5
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/settings/declarations.js +18 -0
- package/dist/ui/settings/session.js +13 -4
- package/docs/architecture/boundaries.md +1 -0
- package/docs/architecture/history-editor-provenance.md +96 -0
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/resource-and-data-policy.md +22 -1
- package/docs/features/prompt-history.md +121 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
8
|
+
"builtAt": "2026-09-11T15:31:16.823Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "78359342ae74e12b31ce05aff4a6b06f25d6e9777b081822c860604d674ec08d",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -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[]> = {
|
package/dist/product-identity.js
CHANGED
|
@@ -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,
|
|
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;
|
|
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).
|