@timurproko/a1 0.1.8-dev.322 → 0.1.8-dev.335
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -11
- package/dist/cli/dispatch.js +1 -1
- package/dist/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/service.d.ts +31 -1
- package/dist/features/prompt-history/service.js +294 -129
- package/dist/features/prompt-history/store.d.ts +2 -1
- package/dist/features/prompt-history/store.js +17 -4
- package/dist/features/prompt-history/worker.js +6 -3
- package/dist/foundation/launch-context/index.d.ts +36 -6
- package/dist/foundation/launch-context/index.js +64 -11
- package/dist/foundation/launch-guardian/main.js +2 -1
- package/dist/foundation/release/bootstrap.d.ts +7 -0
- package/dist/foundation/release/bootstrap.js +95 -19
- package/dist/foundation/release/cohort-state.js +0 -3
- package/dist/foundation/release/dependency-certification-retention.d.ts +7 -0
- package/dist/foundation/release/dependency-certification-retention.js +88 -0
- package/dist/foundation/release/dependency-certification.d.ts +24 -0
- package/dist/foundation/release/dependency-certification.js +208 -0
- package/dist/foundation/release/dependency-layer.d.ts +3 -4
- package/dist/foundation/release/dependency-layer.js +6 -37
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/release-gc.js +72 -44
- package/dist/foundation/release/release-store.d.ts +2 -0
- package/dist/foundation/release/release-store.js +4 -3
- package/dist/foundation/release/restart-certification.js +16 -5
- package/dist/foundation/release/update-launch.d.ts +6 -0
- package/dist/foundation/release/update-launch.js +17 -0
- package/dist/foundation/release/update.d.ts +2 -0
- package/dist/foundation/release/update.js +21 -27
- package/dist/foundation/release/warmup.js +16 -5
- package/dist/foundation/supervision/main.js +5 -2
- package/dist/foundation/supervision/server.js +40 -13
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +51 -27
- package/dist/integrations/pi/engine/adapter.d.ts +20 -0
- package/dist/integrations/pi/engine/adapter.js +223 -87
- package/dist/integrations/pi/engine/pending-delivery.d.ts +26 -0
- package/dist/integrations/pi/engine/pending-delivery.js +149 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +38 -25
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +0 -2
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +3 -8
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell.js +9 -6
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -2
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +4 -5
- package/dist/integrations/pi/session-ui/text-paste.d.ts +5 -0
- package/dist/integrations/pi/session-ui/text-paste.js +16 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +2 -2
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +82 -39
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.json +1 -1
- package/docs/architecture/internal-naming.md +5 -3
- package/docs/architecture/toolchain.md +1 -1
- package/docs/ci-release-runbook.md +45 -7
- package/docs/manual-code-streaming-cleanup.md +88 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,18 +160,29 @@ a1 update --develop 0.1.8-dev.107 # install that exact full preview versio
|
|
|
160
160
|
### Stable
|
|
161
161
|
|
|
162
162
|
```sh
|
|
163
|
-
npm run release -- patch # 0.1.1 -> 0.1.
|
|
164
|
-
npm run release -- minor # 0.1.
|
|
165
|
-
npm run release -- major # 0.1.
|
|
166
|
-
npm run release -- 0.4.0 # an exact version
|
|
163
|
+
npm run release -- patch # 0.1.8-dev -> 0.1.8; already-stable 0.1.8 -> 0.1.9
|
|
164
|
+
npm run release -- minor # 0.1.8-dev -> 0.2.0
|
|
165
|
+
npm run release -- major # 0.1.8-dev -> 1.0.0
|
|
166
|
+
npm run release -- 0.4.0 # an exact stable version
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
Run from the repository root on a clean `develop` matching `origin/develop`.
|
|
170
|
+
A target is required; bare `npm run release` displays usage and releases nothing.
|
|
171
|
+
`patch` promotes the current prerelease rather than skipping its stable version.
|
|
172
|
+
|
|
173
|
+
The command prepares a version-only PR in an isolated detached worktree, prints
|
|
174
|
+
its URL, and waits for you to validate and **merge it manually**. Neither this PR
|
|
175
|
+
nor the next-development PR is auto-merged, and green CI alone does not advance
|
|
176
|
+
the release. After the stable PR merges, the command dispatches publication for
|
|
177
|
+
that exact authoritative commit and waits for success. CI validates the packed
|
|
178
|
+
release on Windows, Linux, and macOS, publishes to npm `latest` with provenance,
|
|
179
|
+
then writes the `v<version>` tag and GitHub Release and fast-forwards `master`.
|
|
180
|
+
|
|
181
|
+
Only after confirmed publication of `0.1.8` does the command prepare the separate
|
|
182
|
+
`0.1.9-dev` PR. It reports development reopened only after you manually merge that
|
|
183
|
+
PR too. Work added to your checkout during either wait is preserved, not reset.
|
|
184
|
+
If publication fails or is uncertain, no reopening PR is prepared. If publication
|
|
185
|
+
succeeded but reopening failed, inspect the reported phase and PR; do not republish
|
|
186
|
+
the immutable stable version.
|
|
176
187
|
|
|
177
188
|
`docs/ci-release-runbook.md` has the full picture.
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -212,7 +212,7 @@ function pinnedPiUpdateError() {
|
|
|
212
212
|
message: [
|
|
213
213
|
PRODUCT_TEXT.diagnostic("pins its certified Pi runtime and cannot update it independently."),
|
|
214
214
|
"Use:",
|
|
215
|
-
` ${PRODUCT_TEXT.commandName} update Update
|
|
215
|
+
` ${PRODUCT_TEXT.commandName} update Update a1`,
|
|
216
216
|
` ${PRODUCT_TEXT.commandName} pi update --extensions Update Pi-compatible packages`,
|
|
217
217
|
` ${PRODUCT_TEXT.commandName} pi update --models Refresh model catalogs`,
|
|
218
218
|
].join("\n"),
|
|
@@ -37,7 +37,7 @@ export function createConsoleProjectTrustPrompt(options = {}) {
|
|
|
37
37
|
`${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
|
|
38
38
|
cwd,
|
|
39
39
|
"",
|
|
40
|
-
"This allows
|
|
40
|
+
"This allows a1 to load project settings and resources, install missing project packages, and execute project extensions.",
|
|
41
41
|
"",
|
|
42
42
|
optionRow("Trust", selected === 0),
|
|
43
43
|
optionRow("Do not trust", selected === 1),
|
|
@@ -628,7 +628,7 @@ export class SettingsApp {
|
|
|
628
628
|
const open = this.#structured;
|
|
629
629
|
if (open !== null)
|
|
630
630
|
return this.#dialogLines(open, width, theme);
|
|
631
|
-
const hint = this.#interruptArmed ? "press ctrl+c again to exit
|
|
631
|
+
const hint = this.#interruptArmed ? "press ctrl+c again to exit a1" : SETTINGS_SHORTCUTS.hint(SCOPE);
|
|
632
632
|
const status = renderStatusLine({ hint, report: this.#notice }, width, theme);
|
|
633
633
|
const input = this.#filter;
|
|
634
634
|
if (input === null)
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Worker } from "node:worker_threads";
|
|
2
2
|
import { type PromptHistoryFailure, type PromptHistoryPort, type PromptHistoryResult, type PromptHistorySnapshot, type PromptHistorySubmission } from "../../contracts/owned-ui/index.js";
|
|
3
|
-
|
|
3
|
+
export type HistoryRecoveryState = "starting" | "ready" | "recovering" | "blocked-storage" | "closing" | "closed";
|
|
4
|
+
export interface HistoryClock {
|
|
5
|
+
now(): number;
|
|
6
|
+
setTimeout(callback: () => void, milliseconds: number): ReturnType<typeof setTimeout>;
|
|
7
|
+
clearTimeout(timer: ReturnType<typeof setTimeout> | undefined): void;
|
|
8
|
+
}
|
|
9
|
+
/** One generation owns storage work; only proven non-commits may be retried. No default output sink. */
|
|
4
10
|
export declare class PromptHistoryService implements PromptHistoryPort {
|
|
5
11
|
#private;
|
|
6
12
|
readonly options: {
|
|
@@ -8,13 +14,37 @@ export declare class PromptHistoryService implements PromptHistoryPort {
|
|
|
8
14
|
profileRoot: string;
|
|
9
15
|
limit: number;
|
|
10
16
|
createWorker?: () => Worker;
|
|
17
|
+
clock?: HistoryClock;
|
|
18
|
+
random?: () => number;
|
|
11
19
|
};
|
|
12
20
|
constructor(options: {
|
|
13
21
|
dataDir: string;
|
|
14
22
|
profileRoot: string;
|
|
15
23
|
limit: number;
|
|
16
24
|
createWorker?: () => Worker;
|
|
25
|
+
clock?: HistoryClock;
|
|
26
|
+
random?: () => number;
|
|
17
27
|
});
|
|
28
|
+
/** Explicit developer inspection: bounded classified counters, never exception or prompt payloads. */
|
|
29
|
+
diagnostics(): {
|
|
30
|
+
state: HistoryRecoveryState;
|
|
31
|
+
generation: number;
|
|
32
|
+
attempts: number;
|
|
33
|
+
recoveries: number;
|
|
34
|
+
pending: number;
|
|
35
|
+
bytes: number;
|
|
36
|
+
active: boolean;
|
|
37
|
+
timers: number;
|
|
38
|
+
failures: {
|
|
39
|
+
schema?: number;
|
|
40
|
+
shutdown?: number;
|
|
41
|
+
unavailable?: number;
|
|
42
|
+
busy?: number;
|
|
43
|
+
capacity?: number;
|
|
44
|
+
oversized?: number;
|
|
45
|
+
corrupt?: number;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
18
48
|
start(): void;
|
|
19
49
|
record(submission: PromptHistorySubmission): Promise<PromptHistoryResult>;
|
|
20
50
|
refresh(): void;
|
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
import { assertPromptHistorySubmission, PROMPT_HISTORY_MAX_ENTRY_BYTES, PROMPT_HISTORY_MAX_PENDING, PROMPT_HISTORY_MAX_TEXT_BYTES } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
import { resolvePromptHistoryPath } from "./paths.js";
|
|
3
|
-
|
|
3
|
+
const clock = { now: () => Date.now(), setTimeout: (fn, ms) => setTimeout(fn, ms), clearTimeout: timer => clearTimeout(timer) };
|
|
4
|
+
const RETENTION_MS = 30_000;
|
|
5
|
+
const LIVENESS_MS = 10_000;
|
|
6
|
+
/** One generation owns storage work; only proven non-commits may be retried. No default output sink. */
|
|
4
7
|
export class PromptHistoryService {
|
|
5
8
|
options;
|
|
6
9
|
#snapshots = new Set();
|
|
7
10
|
#failures = new Set();
|
|
8
|
-
#
|
|
11
|
+
#counts = {};
|
|
9
12
|
#queue = [];
|
|
13
|
+
#clock;
|
|
14
|
+
#state = "starting";
|
|
10
15
|
#worker;
|
|
16
|
+
#stopping;
|
|
11
17
|
#active;
|
|
12
18
|
#timer;
|
|
19
|
+
#expiry;
|
|
13
20
|
#poll;
|
|
21
|
+
#closeTimer;
|
|
14
22
|
#sequence = 0;
|
|
23
|
+
#generation = 0;
|
|
15
24
|
#bytes = 0;
|
|
16
|
-
#writes = 0;
|
|
17
25
|
#started = false;
|
|
18
26
|
#ready = false;
|
|
19
|
-
#failed = false;
|
|
20
|
-
#closing = false;
|
|
21
|
-
#reading = false;
|
|
22
27
|
#refreshPending = false;
|
|
23
28
|
#lastRevision = -1;
|
|
29
|
+
#backoff = 100;
|
|
30
|
+
#recoveries = 0;
|
|
24
31
|
#closePromise;
|
|
32
|
+
#resolveClose;
|
|
25
33
|
constructor(options) {
|
|
26
34
|
this.options = options;
|
|
35
|
+
this.#clock = options.clock ?? clock;
|
|
36
|
+
}
|
|
37
|
+
/** Explicit developer inspection: bounded classified counters, never exception or prompt payloads. */
|
|
38
|
+
diagnostics() {
|
|
39
|
+
return { state: this.#state, generation: this.#generation, attempts: this.#sequence, recoveries: this.#recoveries,
|
|
40
|
+
pending: this.#queue.length, bytes: this.#bytes, active: this.#active !== undefined,
|
|
41
|
+
timers: [this.#timer, this.#expiry, this.#poll, this.#closeTimer].filter(value => value !== undefined).length,
|
|
42
|
+
failures: { ...this.#counts } };
|
|
27
43
|
}
|
|
28
44
|
start() {
|
|
29
|
-
if (this.#started || this.#
|
|
45
|
+
if (this.#started || this.#isClosed())
|
|
30
46
|
return;
|
|
31
47
|
this.#started = true;
|
|
32
|
-
|
|
48
|
+
void this.#open();
|
|
33
49
|
}
|
|
34
50
|
record(submission) {
|
|
35
|
-
if (this.#
|
|
51
|
+
if (this.#isClosed() || this.#state === "blocked-storage")
|
|
36
52
|
return Promise.resolve("skipped");
|
|
37
53
|
try {
|
|
38
54
|
assertPromptHistorySubmission(submission);
|
|
@@ -46,7 +62,7 @@ export class PromptHistoryService {
|
|
|
46
62
|
this.#report("oversized");
|
|
47
63
|
return Promise.resolve("skipped");
|
|
48
64
|
}
|
|
49
|
-
if (this.#
|
|
65
|
+
if (this.#queue.length >= PROMPT_HISTORY_MAX_PENDING || this.#bytes + bytes > PROMPT_HISTORY_MAX_TEXT_BYTES) {
|
|
50
66
|
this.#report("capacity");
|
|
51
67
|
return Promise.resolve("skipped");
|
|
52
68
|
}
|
|
@@ -56,53 +72,18 @@ export class PromptHistoryService {
|
|
|
56
72
|
...(submission.sessionId !== undefined && Buffer.byteLength(submission.sessionId) <= 256 ? { sessionId: submission.sessionId } : {}),
|
|
57
73
|
};
|
|
58
74
|
this.start();
|
|
59
|
-
this.#writes++;
|
|
60
75
|
this.#bytes += bytes;
|
|
61
76
|
return new Promise(resolve => {
|
|
62
|
-
this.#queue.push({
|
|
63
|
-
|
|
64
|
-
this.#bytes -= bytes;
|
|
65
|
-
resolve(ok ? "committed" : "skipped");
|
|
66
|
-
if (ok)
|
|
67
|
-
this.refresh();
|
|
68
|
-
} });
|
|
77
|
+
this.#queue.push({ submission: candidate, expires: this.#clock.now() + RETENTION_MS, bytes, done: resolve });
|
|
78
|
+
this.#armExpiry();
|
|
69
79
|
this.#drain();
|
|
70
80
|
});
|
|
71
81
|
}
|
|
72
82
|
refresh() {
|
|
73
|
-
if (this.#
|
|
83
|
+
if (this.#isClosed() || this.#state === "blocked-storage")
|
|
74
84
|
return;
|
|
75
85
|
this.start();
|
|
76
|
-
|
|
77
|
-
this.#refreshPending = true;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.#reading = true;
|
|
81
|
-
const observers = [...this.#snapshots];
|
|
82
|
-
this.#queue.push({ request: { id: ++this.#sequence, kind: "read" }, done: (value, ok) => {
|
|
83
|
-
this.#reading = false;
|
|
84
|
-
if (ok) {
|
|
85
|
-
if (!isSnapshot(value)) {
|
|
86
|
-
this.#fail("corrupt");
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (value.revision !== this.#lastRevision && observers.some(listener => this.#snapshots.has(listener))) {
|
|
90
|
-
this.#lastRevision = value.revision;
|
|
91
|
-
for (const listener of observers) {
|
|
92
|
-
if (!this.#snapshots.has(listener))
|
|
93
|
-
continue;
|
|
94
|
-
try {
|
|
95
|
-
listener(value);
|
|
96
|
-
}
|
|
97
|
-
catch { /* Security: observer errors must not escape the worker event handler. */ }
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (this.#refreshPending) {
|
|
102
|
-
this.#refreshPending = false;
|
|
103
|
-
this.refresh();
|
|
104
|
-
}
|
|
105
|
-
} });
|
|
86
|
+
this.#refreshPending = true;
|
|
106
87
|
this.#drain();
|
|
107
88
|
}
|
|
108
89
|
onSnapshot(listener) {
|
|
@@ -110,6 +91,7 @@ export class PromptHistoryService {
|
|
|
110
91
|
this.#lastRevision = -1;
|
|
111
92
|
return () => this.#snapshots.delete(listener);
|
|
112
93
|
}
|
|
94
|
+
// Security: this classified observer is developer-only, never wired to shell notifications.
|
|
113
95
|
onFailure(listener) {
|
|
114
96
|
this.#failures.add(listener);
|
|
115
97
|
return () => this.#failures.delete(listener);
|
|
@@ -117,116 +99,299 @@ export class PromptHistoryService {
|
|
|
117
99
|
close() {
|
|
118
100
|
if (this.#closePromise !== undefined)
|
|
119
101
|
return this.#closePromise;
|
|
120
|
-
this.#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
102
|
+
this.#state = "closing";
|
|
103
|
+
this.#clock.clearTimeout(this.#poll);
|
|
104
|
+
this.#poll = undefined;
|
|
105
|
+
this.#clock.clearTimeout(this.#expiry);
|
|
106
|
+
this.#expiry = undefined;
|
|
107
|
+
this.#refreshPending = false;
|
|
108
|
+
this.#closePromise = new Promise(resolve => { this.#resolveClose = resolve; });
|
|
109
|
+
this.#closeTimer = this.#clock.setTimeout(() => {
|
|
110
|
+
this.#report("shutdown");
|
|
111
|
+
this.#finishClose();
|
|
112
|
+
}, 2000);
|
|
113
|
+
if (!this.#started || !this.#ready && this.#worker === undefined)
|
|
114
|
+
this.#finishClose();
|
|
115
|
+
else if (this.#active === undefined && this.#ready) {
|
|
116
|
+
this.#clock.clearTimeout(this.#timer);
|
|
117
|
+
this.#timer = undefined;
|
|
135
118
|
this.#drain();
|
|
136
|
-
}
|
|
119
|
+
}
|
|
137
120
|
return this.#closePromise;
|
|
138
121
|
}
|
|
122
|
+
#isClosed() { return this.#state === "closing" || this.#state === "closed"; }
|
|
139
123
|
async #open() {
|
|
140
|
-
if (this.#
|
|
124
|
+
if (this.#isClosed() || this.#state === "blocked-storage" || this.#worker !== undefined || this.#stopping !== undefined)
|
|
141
125
|
return;
|
|
142
|
-
const
|
|
143
|
-
|
|
126
|
+
const generation = ++this.#generation;
|
|
127
|
+
this.#state = "starting";
|
|
128
|
+
try {
|
|
129
|
+
const { Worker } = await import("node:worker_threads");
|
|
130
|
+
if (this.#isClosed() || generation !== this.#generation)
|
|
131
|
+
return;
|
|
132
|
+
const location = resolvePromptHistoryPath(this.options.dataDir, this.options.profileRoot);
|
|
133
|
+
const source = import.meta.url.endsWith(".ts");
|
|
134
|
+
const entry = new URL(source ? "./worker.ts" : "./worker.js", import.meta.url);
|
|
135
|
+
const options = { workerData: { ...location, limit: this.options.limit }, stdout: true, stderr: true };
|
|
136
|
+
const worker = this.options.createWorker?.() ?? (source
|
|
137
|
+
? new Worker(`import('tsx/esm/api').then(({ tsImport }) => tsImport(${JSON.stringify(entry.href)}, ${JSON.stringify(import.meta.url)}))`, { ...options, eval: true })
|
|
138
|
+
: new Worker(entry, options));
|
|
139
|
+
this.#worker = worker;
|
|
140
|
+
worker.stdout?.resume();
|
|
141
|
+
worker.stderr?.resume();
|
|
142
|
+
this.#timer = this.#clock.setTimeout(() => this.#lost("unavailable"), LIVENESS_MS);
|
|
143
|
+
worker.on("message", (message) => {
|
|
144
|
+
if (generation !== this.#generation || this.#state === "closed")
|
|
145
|
+
return;
|
|
146
|
+
this.#response(message);
|
|
147
|
+
});
|
|
148
|
+
worker.on("error", () => { if (generation === this.#generation)
|
|
149
|
+
this.#lost("unavailable"); });
|
|
150
|
+
worker.on("exit", () => { if (generation === this.#generation)
|
|
151
|
+
this.#lost("unavailable"); });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
if (generation === this.#generation)
|
|
155
|
+
this.#lost("unavailable");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
#response(value) {
|
|
159
|
+
if (value === null || typeof value !== "object") {
|
|
160
|
+
this.#lost("unavailable");
|
|
144
161
|
return;
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.#
|
|
153
|
-
|
|
154
|
-
this.#
|
|
155
|
-
this.#
|
|
156
|
-
|
|
162
|
+
}
|
|
163
|
+
const message = value;
|
|
164
|
+
if (!Number.isSafeInteger(message.id) || typeof message.ok !== "boolean") {
|
|
165
|
+
this.#lost("unavailable");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Concurrency: late/duplicate attempt replies cannot clear another attempt's liveness timer.
|
|
169
|
+
if (message.id !== (this.#ready ? this.#active?.id : 0))
|
|
170
|
+
return;
|
|
171
|
+
this.#clock.clearTimeout(this.#timer);
|
|
172
|
+
this.#timer = undefined;
|
|
173
|
+
const attempt = this.#active;
|
|
174
|
+
this.#active = undefined;
|
|
175
|
+
if (!message.ok) {
|
|
176
|
+
const code = failureCode(message.code);
|
|
177
|
+
this.#report(code);
|
|
178
|
+
if (code === "schema" || code === "corrupt") {
|
|
179
|
+
this.#state = this.#isClosed() ? "closing" : "blocked-storage";
|
|
180
|
+
this.#settleAll();
|
|
181
|
+
void this.#stopWorker();
|
|
182
|
+
if (this.#isClosed())
|
|
183
|
+
this.#finishClose();
|
|
157
184
|
return;
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
this.#
|
|
185
|
+
}
|
|
186
|
+
if (attempt?.work !== undefined && message.certainty !== "uncommitted") {
|
|
187
|
+
this.#settle(attempt.work, "skipped");
|
|
188
|
+
this.#lost(code);
|
|
161
189
|
return;
|
|
162
190
|
}
|
|
163
|
-
if (
|
|
164
|
-
this.#
|
|
191
|
+
if (attempt?.work !== undefined && code === "oversized")
|
|
192
|
+
this.#settle(attempt.work, "skipped");
|
|
193
|
+
if (this.#isClosed()) {
|
|
194
|
+
this.#report("shutdown");
|
|
195
|
+
this.#finishClose();
|
|
165
196
|
return;
|
|
166
197
|
}
|
|
167
|
-
if (
|
|
168
|
-
this.#
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
if (!this.#ready)
|
|
199
|
+
void this.#stopWorker().then(() => this.#recover());
|
|
200
|
+
else
|
|
201
|
+
this.#recover();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!this.#ready) {
|
|
205
|
+
this.#ready = true;
|
|
206
|
+
if (!this.#isClosed()) {
|
|
207
|
+
this.#state = "ready";
|
|
208
|
+
this.#refreshPending = true;
|
|
209
|
+
this.#schedulePoll();
|
|
174
210
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
211
|
+
}
|
|
212
|
+
else if (attempt?.work !== undefined) {
|
|
213
|
+
this.#settle(attempt.work, "committed");
|
|
214
|
+
this.#refreshPending = !this.#isClosed();
|
|
215
|
+
}
|
|
216
|
+
else if (attempt?.observers !== undefined) {
|
|
217
|
+
if (!isSnapshot(message.value)) {
|
|
218
|
+
this.#lost("corrupt");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const snapshot = message.value;
|
|
222
|
+
if (snapshot.revision !== this.#lastRevision && attempt.observers.some(listener => this.#snapshots.has(listener))) {
|
|
223
|
+
this.#lastRevision = snapshot.revision;
|
|
224
|
+
for (const listener of attempt.observers) {
|
|
225
|
+
if (!this.#snapshots.has(listener) || this.#isClosed())
|
|
226
|
+
continue;
|
|
227
|
+
try {
|
|
228
|
+
listener(snapshot);
|
|
229
|
+
}
|
|
230
|
+
catch { /* Security: observer exceptions are not storage failures. */ }
|
|
180
231
|
}
|
|
181
|
-
this.#active = undefined;
|
|
182
|
-
item.done(message.value, true);
|
|
183
232
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
233
|
+
}
|
|
234
|
+
else if (this.#isClosed()) {
|
|
235
|
+
this.#finishClose();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.#backoff = 100;
|
|
239
|
+
if (!this.#isClosed())
|
|
240
|
+
this.#state = "ready";
|
|
241
|
+
this.#drain();
|
|
189
242
|
}
|
|
190
243
|
#drain() {
|
|
191
|
-
if (!this.#ready || this.#
|
|
244
|
+
if (!this.#ready || this.#active !== undefined || this.#timer !== undefined || this.#state === "blocked-storage" || this.#state === "closed")
|
|
192
245
|
return;
|
|
193
|
-
const
|
|
194
|
-
|
|
246
|
+
const work = this.#queue[0];
|
|
247
|
+
const id = ++this.#sequence;
|
|
248
|
+
let request;
|
|
249
|
+
if (work !== undefined) {
|
|
250
|
+
this.#active = { id, work };
|
|
251
|
+
request = { id, kind: "record", submission: work.submission };
|
|
252
|
+
}
|
|
253
|
+
else if (this.#isClosed()) {
|
|
254
|
+
this.#active = { id };
|
|
255
|
+
request = { id, kind: "close" };
|
|
256
|
+
}
|
|
257
|
+
else if (this.#refreshPending) {
|
|
258
|
+
this.#refreshPending = false;
|
|
259
|
+
this.#active = { id, observers: [...this.#snapshots] };
|
|
260
|
+
request = { id, kind: "read" };
|
|
261
|
+
}
|
|
262
|
+
else
|
|
195
263
|
return;
|
|
196
|
-
this.#
|
|
197
|
-
this.#timer = setTimeout(() => this.#fail("busy"), 1500);
|
|
264
|
+
this.#timer = this.#clock.setTimeout(() => this.#lost("unavailable"), LIVENESS_MS);
|
|
198
265
|
try {
|
|
199
|
-
this.#worker.postMessage(
|
|
266
|
+
this.#worker.postMessage(request);
|
|
200
267
|
}
|
|
201
268
|
catch {
|
|
202
|
-
this.#
|
|
269
|
+
this.#lost("unavailable");
|
|
203
270
|
}
|
|
204
271
|
}
|
|
205
|
-
#
|
|
206
|
-
|
|
272
|
+
#settle(work, result) {
|
|
273
|
+
const index = this.#queue.indexOf(work);
|
|
274
|
+
if (index < 0)
|
|
207
275
|
return;
|
|
208
|
-
this.#
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
276
|
+
this.#queue.splice(index, 1);
|
|
277
|
+
this.#bytes -= work.bytes;
|
|
278
|
+
work.done(result);
|
|
279
|
+
this.#armExpiry();
|
|
280
|
+
}
|
|
281
|
+
#settleAll() { for (const work of [...this.#queue])
|
|
282
|
+
this.#settle(work, "skipped"); }
|
|
283
|
+
#armExpiry() {
|
|
284
|
+
this.#clock.clearTimeout(this.#expiry);
|
|
285
|
+
this.#expiry = undefined;
|
|
286
|
+
if (this.#isClosed() || this.#queue.length === 0)
|
|
287
|
+
return;
|
|
288
|
+
this.#expiry = this.#clock.setTimeout(() => {
|
|
289
|
+
this.#expiry = undefined;
|
|
290
|
+
for (const work of [...this.#queue]) {
|
|
291
|
+
if (work.expires > this.#clock.now())
|
|
292
|
+
break;
|
|
293
|
+
if (this.#active?.work === work)
|
|
294
|
+
this.#lost("unavailable");
|
|
295
|
+
this.#settle(work, "skipped");
|
|
212
296
|
}
|
|
213
|
-
|
|
214
|
-
}
|
|
297
|
+
this.#armExpiry();
|
|
298
|
+
}, Math.max(0, this.#queue[0].expires - this.#clock.now()));
|
|
299
|
+
}
|
|
300
|
+
#schedulePoll() {
|
|
301
|
+
this.#clock.clearTimeout(this.#poll);
|
|
302
|
+
this.#poll = this.#clock.setTimeout(() => {
|
|
303
|
+
this.#poll = undefined;
|
|
304
|
+
if (this.#isClosed() || this.#state === "blocked-storage")
|
|
305
|
+
return;
|
|
306
|
+
this.refresh();
|
|
307
|
+
this.#schedulePoll();
|
|
308
|
+
}, 1000);
|
|
309
|
+
this.#poll.unref?.();
|
|
310
|
+
}
|
|
311
|
+
#recover() {
|
|
312
|
+
if (this.#isClosed() || this.#state === "blocked-storage")
|
|
313
|
+
return;
|
|
314
|
+
this.#state = "recovering";
|
|
315
|
+
this.#recoveries = Math.min(Number.MAX_SAFE_INTEGER, this.#recoveries + 1);
|
|
316
|
+
this.#clock.clearTimeout(this.#timer);
|
|
317
|
+
const jitter = Math.max(0, Math.min(1, (this.options.random ?? Math.random)()));
|
|
318
|
+
const delay = Math.min(5000, this.#backoff * (1 + jitter * 0.2));
|
|
319
|
+
this.#backoff = Math.min(5000, this.#backoff * 2);
|
|
320
|
+
this.#timer = this.#clock.setTimeout(() => {
|
|
321
|
+
this.#timer = undefined;
|
|
322
|
+
if (this.#isClosed())
|
|
323
|
+
return;
|
|
324
|
+
if (this.#ready) {
|
|
325
|
+
this.#state = "ready";
|
|
326
|
+
this.#refreshPending = true;
|
|
327
|
+
this.#drain();
|
|
328
|
+
}
|
|
329
|
+
else
|
|
330
|
+
void this.#open();
|
|
331
|
+
}, delay);
|
|
215
332
|
}
|
|
216
|
-
#
|
|
217
|
-
if (this.#
|
|
333
|
+
#lost(code) {
|
|
334
|
+
if (this.#state === "closed")
|
|
218
335
|
return;
|
|
219
|
-
this.#failed = true;
|
|
220
|
-
clearTimeout(this.#timer);
|
|
221
|
-
clearInterval(this.#poll);
|
|
222
336
|
this.#report(code);
|
|
223
337
|
const active = this.#active;
|
|
224
338
|
this.#active = undefined;
|
|
225
|
-
active?.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
339
|
+
if (active?.work !== undefined)
|
|
340
|
+
this.#settle(active.work, "skipped");
|
|
341
|
+
if (code === "schema" || code === "corrupt") {
|
|
342
|
+
this.#state = this.#isClosed() ? "closing" : "blocked-storage";
|
|
343
|
+
this.#settleAll();
|
|
344
|
+
}
|
|
345
|
+
if (this.#isClosed()) {
|
|
346
|
+
this.#report("shutdown");
|
|
347
|
+
this.#finishClose();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
void this.#stopWorker().then(() => this.#recover());
|
|
351
|
+
}
|
|
352
|
+
#stopWorker() {
|
|
353
|
+
if (this.#stopping !== undefined)
|
|
354
|
+
return this.#stopping;
|
|
355
|
+
this.#clock.clearTimeout(this.#timer);
|
|
356
|
+
this.#timer = undefined;
|
|
357
|
+
this.#clock.clearTimeout(this.#poll);
|
|
358
|
+
this.#poll = undefined;
|
|
359
|
+
this.#ready = false;
|
|
360
|
+
++this.#generation;
|
|
361
|
+
const worker = this.#worker;
|
|
362
|
+
this.#worker = undefined;
|
|
363
|
+
if (worker === undefined)
|
|
364
|
+
return Promise.resolve();
|
|
365
|
+
worker.unref();
|
|
366
|
+
// Invariant: a rejected terminate is not proof of death. Wait for exit before opening a replacement.
|
|
367
|
+
const exited = new Promise(resolve => worker.once("exit", () => resolve()));
|
|
368
|
+
this.#stopping = worker.terminate().then(() => { }, () => exited).then(() => { this.#stopping = undefined; });
|
|
369
|
+
return this.#stopping;
|
|
370
|
+
}
|
|
371
|
+
#finishClose() {
|
|
372
|
+
if (this.#queue.length > 0)
|
|
373
|
+
this.#report("shutdown");
|
|
374
|
+
this.#state = "closed";
|
|
375
|
+
this.#active = undefined;
|
|
376
|
+
this.#settleAll();
|
|
377
|
+
for (const timer of [this.#timer, this.#poll, this.#expiry, this.#closeTimer])
|
|
378
|
+
this.#clock.clearTimeout(timer);
|
|
379
|
+
this.#timer = this.#poll = this.#expiry = this.#closeTimer = undefined;
|
|
380
|
+
void this.#stopWorker();
|
|
381
|
+
this.#resolveClose?.();
|
|
382
|
+
this.#resolveClose = undefined;
|
|
383
|
+
}
|
|
384
|
+
#report(code) {
|
|
385
|
+
const count = this.#counts[code] ?? 0;
|
|
386
|
+
this.#counts[code] = Math.min(Number.MAX_SAFE_INTEGER, count + 1);
|
|
387
|
+
if (count > 0)
|
|
388
|
+
return;
|
|
389
|
+
for (const listener of this.#failures) {
|
|
390
|
+
try {
|
|
391
|
+
listener(code);
|
|
392
|
+
}
|
|
393
|
+
catch { /* Security: never forward arbitrary observer errors. */ }
|
|
394
|
+
}
|
|
230
395
|
}
|
|
231
396
|
}
|
|
232
397
|
function failureCode(value) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type PromptHistoryFailure, type PromptHistorySnapshot, type PromptHistorySubmission } from "../../contracts/owned-ui/index.js";
|
|
2
2
|
export declare class HistoryStorageError extends Error {
|
|
3
3
|
readonly code: PromptHistoryFailure;
|
|
4
|
-
|
|
4
|
+
readonly certainty: "uncommitted" | "unknown";
|
|
5
|
+
constructor(code: PromptHistoryFailure, certainty?: "uncommitted" | "unknown");
|
|
5
6
|
}
|
|
6
7
|
export declare class PromptHistoryStore {
|
|
7
8
|
#private;
|