@timurproko/a1 0.2.7-dev.1276 → 0.2.7-dev.1286
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 +5 -1
- package/dist/app/session-shell/session-shell-root.d.ts +4 -68
- package/dist/app/session-shell/session-shell.js +5 -11
- package/dist/app/session-shell/shell-host.js +2 -2
- package/dist/app/session-shell/shell-options.d.ts +65 -0
- package/dist/app/session-shell/shell-options.js +1 -0
- package/dist/app/session-shell/workflow-router.d.ts +3 -1
- package/dist/app/session-shell/workflow-router.js +12 -0
- package/dist/cli/dispatch.js +27 -1
- package/dist/cli/packages.js +18 -2
- package/dist/cli/session-context.js +6 -2
- package/dist/composition/owned-ui.js +20 -8
- package/dist/composition/session-naming.js +1 -1
- package/dist/composition/session-runtime-registry.d.ts +2 -0
- package/dist/composition/session-runtime-registry.js +49 -9
- package/dist/contracts/agent-engine/package-ports.d.ts +10 -14
- package/dist/contracts/agent-engine/package-ports.js +5 -9
- package/dist/contracts/owned-ui/session-workflows.d.ts +6 -0
- package/dist/contracts/presentation/index.d.ts +1 -0
- package/dist/contracts/presentation/progress-palette.generated.d.ts +1 -1
- package/dist/contracts/presentation/progress-palette.generated.js +1 -1
- package/dist/contracts/presentation/progress-spinner.d.ts +7 -0
- package/dist/contracts/presentation/progress-spinner.js +1 -0
- package/dist/features/launch/session-context.d.ts +8 -0
- package/dist/features/launch/session-context.js +19 -10
- package/dist/features/owned-ui/reference-screen-app.js +9 -5
- package/dist/features/owned-ui/session-fork-prompt.d.ts +2 -1
- package/dist/features/owned-ui/session-fork-prompt.js +11 -5
- package/dist/features/owned-ui/settings-app.d.ts +1 -1
- package/dist/features/owned-ui/settings-app.js +14 -7
- package/dist/features/owned-ui/settings-spinner-preview.d.ts +9 -0
- package/dist/features/owned-ui/settings-spinner-preview.js +29 -0
- package/dist/features/session-naming/context.d.ts +17 -0
- package/dist/features/session-naming/context.js +39 -0
- package/dist/features/session-naming/controller.d.ts +1 -5
- package/dist/features/session-naming/controller.js +7 -4
- package/dist/features/session-naming/generate.d.ts +2 -1
- package/dist/features/session-naming/generate.js +14 -10
- package/dist/features/session-naming/index.d.ts +1 -1
- package/dist/foundation/lifecycle/index.d.ts +3 -1
- package/dist/foundation/lifecycle/index.js +3 -1
- package/dist/foundation/lifecycle/session-claim-lock.d.ts +2 -2
- package/dist/foundation/lifecycle/session-claim-lock.js +36 -10
- package/dist/foundation/lifecycle/session-context-retry.d.ts +2 -0
- package/dist/foundation/lifecycle/session-context-retry.js +17 -0
- package/dist/foundation/lifecycle/session-repository-context.d.ts +7 -1
- package/dist/foundation/lifecycle/session-repository-context.js +105 -39
- package/dist/foundation/lifecycle/session-worktree-entry.d.ts +3 -0
- package/dist/foundation/lifecycle/session-worktree-entry.js +87 -0
- package/dist/foundation/release/automatic-update.js +2 -2
- package/dist/foundation/release/bootstrap.js +19 -3
- package/dist/foundation/release/index.d.ts +1 -1
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/release-gc.js +41 -5
- package/dist/foundation/release/release-state.d.ts +48 -4
- package/dist/foundation/release/release-state.js +168 -31
- package/dist/foundation/release/release-use.d.ts +10 -0
- package/dist/foundation/release/release-use.js +86 -0
- package/dist/foundation/release/update-activation.d.ts +3 -1
- package/dist/foundation/release/update-activation.js +23 -6
- package/dist/foundation/release/update-recovery.js +2 -2
- package/dist/foundation/release/update.d.ts +3 -1
- package/dist/foundation/release/update.js +61 -22
- package/dist/foundation/release/warmup.js +8 -9
- package/dist/foundation/session-writer-lock/index.d.ts +2 -2
- package/dist/foundation/session-writer-lock/index.js +82 -12
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/fatal-diagnostic.d.ts +2 -0
- package/dist/foundation/terminal-cleanup/fatal-diagnostic.js +59 -0
- package/dist/foundation/terminal-cleanup/fatal-exit.js +1 -29
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/share-operation-dialog.d.ts +2 -1
- package/dist/integrations/pi/components/share-operation-dialog.js +13 -5
- package/dist/integrations/pi/components/shell-footer-status.d.ts +2 -1
- package/dist/integrations/pi/components/shell-footer-status.js +15 -8
- package/dist/integrations/pi/components/shell-session-info-reference.js +11 -8
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/auth-url.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/auth-url.js +1 -1
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/login-dialog.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/oauth-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/oauth-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +14 -8
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +3 -1
- package/dist/integrations/pi/engine/adapter.js +24 -6
- package/dist/integrations/pi/engine/changelog.js +5 -2
- package/dist/integrations/pi/engine/delivery-retention.d.ts +7 -0
- package/dist/integrations/pi/engine/delivery-retention.js +66 -0
- package/dist/integrations/pi/engine/event-delivery.d.ts +17 -1
- package/dist/integrations/pi/engine/event-delivery.js +25 -3
- package/dist/integrations/pi/engine/extension-ui-binding.d.ts +2 -1
- package/dist/integrations/pi/engine/extension-ui-binding.js +45 -0
- package/dist/integrations/pi/engine/host-ports.d.ts +2 -0
- package/dist/integrations/pi/engine/host.d.ts +3 -0
- package/dist/integrations/pi/engine/host.js +2 -0
- package/dist/integrations/pi/engine/package-integration.js +19 -4
- package/dist/integrations/pi/engine/pending-delivery.d.ts +4 -1
- package/dist/integrations/pi/engine/pending-delivery.js +51 -18
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.d.ts +8 -0
- package/dist/integrations/pi/engine/runtime-integration.js +58 -9
- package/dist/integrations/pi/engine/session-cwd-recovery.d.ts +11 -0
- package/dist/integrations/pi/engine/session-cwd-recovery.js +18 -0
- package/dist/integrations/pi/engine/session-runtime.d.ts +3 -0
- package/dist/integrations/pi/engine/session-runtime.js +2 -0
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -2
- package/dist/integrations/pi/engine/session-selection.js +25 -5
- package/dist/integrations/pi/engine/transcript-image-assets.d.ts +3 -0
- package/dist/integrations/pi/engine/transcript-image-assets.js +65 -14
- package/dist/integrations/pi/engine/view-state.d.ts +14 -0
- package/dist/integrations/pi/engine/view-state.js +17 -0
- package/dist/integrations/pi/engine/workflow-runner.d.ts +2 -0
- package/dist/integrations/pi/engine/workflow-runner.js +47 -26
- package/dist/integrations/pi/engine/worktree-tools.d.ts +4 -0
- package/dist/integrations/pi/engine/worktree-tools.js +26 -0
- package/dist/integrations/pi/session-presenters/components.d.ts +2 -1
- package/dist/integrations/pi/session-presenters/components.js +5 -3
- package/dist/integrations/pi/session-presenters/presenters.d.ts +3 -1
- package/dist/integrations/pi/session-presenters/presenters.js +1 -1
- package/dist/integrations/pi/startup-public.d.ts +1 -1
- package/dist/integrations/pi/startup-public.js +16 -20
- package/dist/integrations/pi/startup-public.manifest.json +29 -29
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/product-identity.json +1 -1
- package/dist/runtime-payload-inventory.json +5027 -5027
- package/dist/ui/components/index.d.ts +1 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/progress-status.d.ts +7 -0
- package/dist/ui/components/progress-status.js +21 -0
- package/dist/ui/components/value-menu.d.ts +1 -1
- package/dist/ui/components/value-menu.js +4 -2
- package/dist/ui/settings/declarations.d.ts +16 -4
- package/dist/ui/settings/declarations.js +13 -4
- package/docs/architecture/internal-naming.md +1 -1
- package/docs/architecture/project-structure.md +7 -11
- package/docs/features/session-exports.md +1 -1
- package/docs/features/session-naming.md +8 -6
- package/docs/features/worktrees.md +38 -0
- package/docs/local-worktree-cleanup.md +27 -4
- package/docs/openspec-archive-automation.md +30 -4
- package/docs/worktree-link-recovery.md +2 -2
- package/package.json +5 -5
|
@@ -19,7 +19,12 @@ import { EngineViewState, projectSessionView } from "./view-state.js";
|
|
|
19
19
|
import { createEnginePorts } from "./ports.js";
|
|
20
20
|
/** Explicit flush failure when required delivery was interrupted rather than completed. */
|
|
21
21
|
export class EngineDeliveryError extends Error {
|
|
22
|
-
|
|
22
|
+
deliveryFailure;
|
|
23
|
+
constructor(failure = null) {
|
|
24
|
+
super("Engine delivery did not complete");
|
|
25
|
+
this.name = "EngineDeliveryError";
|
|
26
|
+
this.deliveryFailure = failure;
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
/** Owns the pinned Pi session lifecycle and serves it to the owned shell as the neutral session backend. */
|
|
25
30
|
export class PiEngineAdapter {
|
|
@@ -43,12 +48,22 @@ export class PiEngineAdapter {
|
|
|
43
48
|
#delivery = new PiEventDelivery({
|
|
44
49
|
sessionId: () => this.#sessionId,
|
|
45
50
|
sessionGeneration: () => this.#engine.generation,
|
|
51
|
+
authoritativeTranscript: () => this.#projection.snapshot(),
|
|
52
|
+
retainedAssetBytes: excluding => this.#projection.assets.pendingBytesExcluding(excluding),
|
|
46
53
|
transcriptBlock: id => this.#projection.block(id),
|
|
47
54
|
retainEvent: event => this.#projection.assets.retainEvent(event),
|
|
48
55
|
isPendingCommand: correlationId => this.#commands.isPending(correlationId),
|
|
49
56
|
cancelForOverload: () => this.#cancelForOverload(),
|
|
50
57
|
reconcileOverload: cancelled => this.#reconcileOverload(cancelled),
|
|
51
58
|
listenerFailed: message => { this.#recordDiagnostic("warning", "event-listener", message, true); },
|
|
59
|
+
canResume: () => !this.#disposed && this.#runningCommands === 0,
|
|
60
|
+
resume: () => {
|
|
61
|
+
this.#admissionStopped = false;
|
|
62
|
+
this.#state.settleOverload(true, false);
|
|
63
|
+
this.#engine.resume();
|
|
64
|
+
this.#emitView();
|
|
65
|
+
},
|
|
66
|
+
incident: failure => { this.#engineHost.deliveryIncident?.(failure); },
|
|
52
67
|
});
|
|
53
68
|
#admissionStopped = false;
|
|
54
69
|
#runningCommands = 0;
|
|
@@ -87,6 +102,7 @@ export class PiEngineAdapter {
|
|
|
87
102
|
});
|
|
88
103
|
#extensions = new PiExtensionUiBinding({
|
|
89
104
|
session: () => this.#engine.session,
|
|
105
|
+
runtime: () => this.#engine.runtime,
|
|
90
106
|
diagnostic: (severity, code, message, recoverable) => { this.#addDiagnostic(severity, code, message, recoverable); },
|
|
91
107
|
emitView: () => { this.#emitView(); },
|
|
92
108
|
});
|
|
@@ -113,6 +129,7 @@ export class PiEngineAdapter {
|
|
|
113
129
|
signal: this.#engineHost.signal,
|
|
114
130
|
markStartupPhase: phase => this.#engineHost.markStartupPhase(phase),
|
|
115
131
|
...(options.repositoryContextReader === undefined ? {} : { repositoryContextReader: options.repositoryContextReader }),
|
|
132
|
+
...(options.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: options.worktreeCwdReader }),
|
|
116
133
|
host: this.#workflowHost,
|
|
117
134
|
}, {
|
|
118
135
|
disposed: () => this.#disposed,
|
|
@@ -148,7 +165,7 @@ export class PiEngineAdapter {
|
|
|
148
165
|
admissionStopped: () => this.#delivery.overloaded || this.#admissionStopped || this.#disposed,
|
|
149
166
|
pendingCommandCount: () => this.#commands.pendingCount,
|
|
150
167
|
beginRunning: () => { this.#runningCommands++; },
|
|
151
|
-
endRunning: () => { this.#runningCommands--; },
|
|
168
|
+
endRunning: () => { this.#runningCommands--; this.#delivery.recovery.resume(); },
|
|
152
169
|
activeModel: () => this.#state.activeModel,
|
|
153
170
|
setActiveModel: model => { this.#state.activeModel = model; },
|
|
154
171
|
thinkingLevel: () => this.#state.thinkingLevel,
|
|
@@ -167,7 +184,7 @@ export class PiEngineAdapter {
|
|
|
167
184
|
pendingWorkflowCount: () => this.#workflows.pendingCount,
|
|
168
185
|
sessionGeneration: () => this.#engine.generation,
|
|
169
186
|
beginRunning: () => { this.#runningCommands++; },
|
|
170
|
-
endRunning: () => { this.#runningCommands--; },
|
|
187
|
+
endRunning: () => { this.#runningCommands--; this.#delivery.recovery.resume(); },
|
|
171
188
|
perform: command => performEngineCommand(command, this.#commandContext()),
|
|
172
189
|
emit: value => { this.#emitEvent(value); },
|
|
173
190
|
emitView: () => { this.#emitView(); },
|
|
@@ -250,7 +267,7 @@ export class PiEngineAdapter {
|
|
|
250
267
|
await this.#delivery.settle();
|
|
251
268
|
if (failed || this.#delivery.failed || this.#disposed && this.#state.lifecycle !== "stopped") {
|
|
252
269
|
this.#delivery.clearFailure();
|
|
253
|
-
throw new EngineDeliveryError();
|
|
270
|
+
throw new EngineDeliveryError(this.#delivery.failure);
|
|
254
271
|
}
|
|
255
272
|
}
|
|
256
273
|
async executeWorkflow(request) {
|
|
@@ -438,8 +455,8 @@ export class PiEngineAdapter {
|
|
|
438
455
|
}
|
|
439
456
|
// Concurrency: cancellation runs synchronously so the overload reservation already guards reentrant outcomes.
|
|
440
457
|
#cancelForOverload() {
|
|
441
|
-
this.#commands.cancelPending();
|
|
442
|
-
this.#workflows.
|
|
458
|
+
this.#commands.cancelPending(["set-model", "set-thinking-level", "resize-surface"]);
|
|
459
|
+
this.#workflows.cancelForDelivery();
|
|
443
460
|
const session = this.#engine.session;
|
|
444
461
|
return Promise.resolve().then(() => session?.abort()).then(() => undefined);
|
|
445
462
|
}
|
|
@@ -447,6 +464,7 @@ export class PiEngineAdapter {
|
|
|
447
464
|
const canResume = cancelled === true && this.#runningCommands === 0;
|
|
448
465
|
this.#admissionStopped = !canResume;
|
|
449
466
|
this.#engine.suspend();
|
|
467
|
+
this.#delivery.recovery.defer(canResume ? undefined : this.#engine.generation);
|
|
450
468
|
this.#events.abandonRun();
|
|
451
469
|
this.#state.settleOverload(canResume, this.#disposed);
|
|
452
470
|
// Invariant: overload publishes one authoritative view; settle tool phases before finalizing other live blocks.
|
|
@@ -20,14 +20,17 @@ export async function readPinnedCommandChangelog(sinceVersion, path = new URL(".
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
function formatPinnedCommandChangelog(content, sinceVersion) {
|
|
23
|
-
// Provenance: behavior follows Pi
|
|
23
|
+
// Provenance: behavior follows pinned Pi's utils/changelog.ts, including leading-version comparison.
|
|
24
24
|
// Runtime reads only the owned data resource; maintenance tooling verifies its pinned provenance.
|
|
25
25
|
let releases = content.split(/(?=^## )/m).flatMap((section) => {
|
|
26
26
|
const version = /^##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/.exec(section);
|
|
27
27
|
return version ? [{ version: version.slice(1).map(Number), markdown: section.trim() }] : [];
|
|
28
28
|
});
|
|
29
29
|
if (sinceVersion !== undefined) {
|
|
30
|
-
const
|
|
30
|
+
const matched = sinceVersion.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
31
|
+
if (matched === null)
|
|
32
|
+
return "";
|
|
33
|
+
const previous = matched.slice(1).map(Number);
|
|
31
34
|
releases = releases.filter(release => {
|
|
32
35
|
for (let index = 0; index < 3; index++) {
|
|
33
36
|
const difference = release.version[index] - (previous[index] ?? 0);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Accounts shared immutable graphs once; only delivery-exclusive storage consumes backlog capacity. */
|
|
2
|
+
export class DeliveryRetention {
|
|
3
|
+
#objects = new WeakMap();
|
|
4
|
+
#current;
|
|
5
|
+
#baseline;
|
|
6
|
+
#bytes = 0;
|
|
7
|
+
get bytes() { return this.#bytes; }
|
|
8
|
+
baseline(value) {
|
|
9
|
+
if (value === this.#current)
|
|
10
|
+
return;
|
|
11
|
+
const next = this.#allocation(value);
|
|
12
|
+
// Invariant: retain first so common blocks never become transient backlog during a revision change.
|
|
13
|
+
this.#change(next, "authoritative", 1);
|
|
14
|
+
if (this.#baseline)
|
|
15
|
+
this.#change(this.#baseline, "authoritative", -1);
|
|
16
|
+
this.#current = value;
|
|
17
|
+
this.#baseline = next;
|
|
18
|
+
}
|
|
19
|
+
retain(value) {
|
|
20
|
+
const allocation = this.#allocation(value);
|
|
21
|
+
this.#change(allocation, "pending", 1);
|
|
22
|
+
let released = false;
|
|
23
|
+
return () => {
|
|
24
|
+
if (released)
|
|
25
|
+
return;
|
|
26
|
+
released = true;
|
|
27
|
+
this.#change(allocation, "pending", -1);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
#allocation(value) {
|
|
31
|
+
if (value === null || typeof value !== "object") {
|
|
32
|
+
return { bytes: scalarBytes(value), children: [], pending: 0, authoritative: 0 };
|
|
33
|
+
}
|
|
34
|
+
const cached = this.#objects.get(value);
|
|
35
|
+
if (cached)
|
|
36
|
+
return cached;
|
|
37
|
+
let bytes = 32;
|
|
38
|
+
const children = [];
|
|
39
|
+
for (const [key, item] of Object.entries(value)) {
|
|
40
|
+
bytes += 16 + key.length * 2;
|
|
41
|
+
if (item !== null && typeof item === "object")
|
|
42
|
+
children.push(this.#allocation(item));
|
|
43
|
+
else
|
|
44
|
+
bytes += scalarBytes(item);
|
|
45
|
+
}
|
|
46
|
+
const result = { bytes, children, pending: 0, authoritative: 0 };
|
|
47
|
+
this.#objects.set(value, result);
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
#change(allocation, owner, delta) {
|
|
51
|
+
const before = allocation[owner];
|
|
52
|
+
const charged = allocation.pending > 0 && allocation.authoritative === 0;
|
|
53
|
+
allocation[owner] += delta;
|
|
54
|
+
const charge = allocation.pending > 0 && allocation.authoritative === 0;
|
|
55
|
+
if (charged !== charge)
|
|
56
|
+
this.#bytes += charge ? allocation.bytes : -allocation.bytes;
|
|
57
|
+
// Invariant: a shared parent's second owner does not retain every descendant again.
|
|
58
|
+
if (before === 0 || allocation[owner] === 0) {
|
|
59
|
+
for (const child of allocation.children)
|
|
60
|
+
this.#change(child, owner, delta);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function scalarBytes(value) {
|
|
65
|
+
return typeof value === "string" ? 16 + value.length * 2 : 8;
|
|
66
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OwnedUiEvent, OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
2
|
+
import { OverloadRecovery } from "./view-state.js";
|
|
2
3
|
/** An event the adapter emits before delivery stamps it with the session id and sequence. */
|
|
3
4
|
export type PiEmittedEvent = Omit<Extract<OwnedUiEvent, {
|
|
4
5
|
type: "session-lifecycle";
|
|
@@ -32,9 +33,19 @@ interface ReservedCommandOutcome {
|
|
|
32
33
|
type: "command-outcome";
|
|
33
34
|
}>["diagnostic"];
|
|
34
35
|
}
|
|
36
|
+
export interface DeliveryFailure {
|
|
37
|
+
readonly reason: "event-count" | "retained-bytes" | "listener";
|
|
38
|
+
readonly event: OwnedUiEvent["type"] | "snapshot";
|
|
39
|
+
readonly generation: number;
|
|
40
|
+
readonly pending: number;
|
|
41
|
+
readonly bytes: number;
|
|
42
|
+
}
|
|
35
43
|
export interface PiEventDeliveryPorts {
|
|
36
44
|
sessionId(): string;
|
|
37
45
|
sessionGeneration(): number;
|
|
46
|
+
/** Immutable history already owned by the engine, not additional pending payload. */
|
|
47
|
+
authoritativeTranscript?(): readonly OwnedUiTranscriptBlock[];
|
|
48
|
+
retainedAssetBytes?(excluding?: OwnedUiEvent): number;
|
|
38
49
|
/** The current block for a lazily materialized transcript event. */
|
|
39
50
|
transcriptBlock(id: string): OwnedUiTranscriptBlock | undefined;
|
|
40
51
|
/** Retain the image assets an event references until the returned release runs. */
|
|
@@ -47,6 +58,9 @@ export interface PiEventDeliveryPorts {
|
|
|
47
58
|
reconcileOverload(cancelled: boolean): Promise<void>;
|
|
48
59
|
/** A listener threw; the adapter records the warning. */
|
|
49
60
|
listenerFailed(message: string): void;
|
|
61
|
+
canResume?(): boolean;
|
|
62
|
+
resume?(): void;
|
|
63
|
+
incident?(failure: DeliveryFailure): void;
|
|
50
64
|
}
|
|
51
65
|
/**
|
|
52
66
|
* Bounded delivery of owned UI events: stamps sequence numbers, keeps subscribers, queues events
|
|
@@ -56,6 +70,8 @@ export interface PiEventDeliveryPorts {
|
|
|
56
70
|
*/
|
|
57
71
|
export declare class PiEventDelivery {
|
|
58
72
|
#private;
|
|
73
|
+
readonly recovery: OverloadRecovery;
|
|
74
|
+
get failure(): DeliveryFailure | null;
|
|
59
75
|
constructor(ports: PiEventDeliveryPorts);
|
|
60
76
|
/** The sequence number of the most recently stamped event. */
|
|
61
77
|
get sequence(): number;
|
|
@@ -90,7 +106,7 @@ export declare class PiEventDelivery {
|
|
|
90
106
|
/** Drop queued events from earlier session generations. */
|
|
91
107
|
discardObsolete(generation: number): void;
|
|
92
108
|
/** Enter the bounded overload transition; a no-op while one is already in progress. */
|
|
93
|
-
beginOverload(): void;
|
|
109
|
+
beginOverload(event?: DeliveryFailure["event"]): void;
|
|
94
110
|
/** Deliver out of band, bypassing the queue; used for reconciliation after an overload. */
|
|
95
111
|
deliverNow(event: OwnedUiEvent): void;
|
|
96
112
|
/** The command outcomes reserved during an overload, in arrival order; clears the reservation. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OverloadRecovery } from "./view-state.js";
|
|
1
2
|
import { PendingEngineDelivery } from "./pending-delivery.js";
|
|
2
3
|
/**
|
|
3
4
|
* Engine events delivered before the queue hands the event loop a turn. Small enough
|
|
@@ -26,9 +27,15 @@ export class PiEventDelivery {
|
|
|
26
27
|
#overloads = 0;
|
|
27
28
|
#deliveryFailed = false;
|
|
28
29
|
#invalidatedEvents = 0;
|
|
30
|
+
#failure = null;
|
|
31
|
+
#reported = 0;
|
|
32
|
+
recovery;
|
|
33
|
+
get failure() { return this.#failure; }
|
|
29
34
|
constructor(ports) {
|
|
30
35
|
this.#ports = ports;
|
|
31
|
-
this
|
|
36
|
+
this.recovery = new OverloadRecovery({ generation: () => ports.sessionGeneration(), busy: () => this.overloaded,
|
|
37
|
+
canResume: () => ports.canResume?.() ?? false, resume: () => ports.resume?.() });
|
|
38
|
+
this.#queue = new PendingEngineDelivery(event => ports.retainEvent(event), ports.authoritativeTranscript, ports.retainedAssetBytes);
|
|
32
39
|
}
|
|
33
40
|
/** The sequence number of the most recently stamped event. */
|
|
34
41
|
get sequence() {
|
|
@@ -77,7 +84,7 @@ export class PiEventDelivery {
|
|
|
77
84
|
}
|
|
78
85
|
if (!this.#queue.push(event, this.#ports.sessionGeneration(), event.type === "transcript-block"
|
|
79
86
|
? this.#blockReconciliation(event.block.id, event.sequence) : undefined)) {
|
|
80
|
-
this.beginOverload();
|
|
87
|
+
this.beginOverload(event.type);
|
|
81
88
|
if (event.type === "command-outcome" && event.outcome !== "accepted" && this.#ports.isPendingCommand(event.correlationId)
|
|
82
89
|
&& !this.#reservedOutcomes.has(event.correlationId)) {
|
|
83
90
|
this.#reservedOutcomes.set(event.correlationId, { outcome: event.outcome, diagnostic: event.diagnostic });
|
|
@@ -98,19 +105,22 @@ export class PiEventDelivery {
|
|
|
98
105
|
this.#invalidatedEvents += this.#queue.discardObsolete(generation);
|
|
99
106
|
}
|
|
100
107
|
/** Enter the bounded overload transition; a no-op while one is already in progress. */
|
|
101
|
-
beginOverload() {
|
|
108
|
+
beginOverload(event = "snapshot") {
|
|
102
109
|
if (this.#overload !== undefined)
|
|
103
110
|
return;
|
|
111
|
+
this.#recordFailure(this.#queue.rejection ?? "retained-bytes", event);
|
|
104
112
|
this.#overloads = Math.min(Number.MAX_SAFE_INTEGER, this.#overloads + 1);
|
|
105
113
|
this.#deliveryFailed = true;
|
|
106
114
|
// Concurrency: reserve before cancellation; outcomes produced reentrantly must not enter the saturated queue.
|
|
107
115
|
this.#overload = Promise.resolve(false);
|
|
116
|
+
this.recovery.begin();
|
|
108
117
|
const cancellation = this.#ports.cancelForOverload();
|
|
109
118
|
this.#overload = new Promise(resolve => {
|
|
110
119
|
const timer = setTimeout(() => resolve(false), OVERLOAD_CANCELLATION_TIMEOUT_MS);
|
|
111
120
|
cancellation.then(() => {
|
|
112
121
|
clearTimeout(timer);
|
|
113
122
|
resolve(true);
|
|
123
|
+
this.recovery.settled();
|
|
114
124
|
}, () => { clearTimeout(timer); resolve(false); });
|
|
115
125
|
});
|
|
116
126
|
}
|
|
@@ -124,10 +134,21 @@ export class PiEventDelivery {
|
|
|
124
134
|
}
|
|
125
135
|
catch (error) {
|
|
126
136
|
this.#deliveryFailed = true;
|
|
137
|
+
this.#recordFailure("listener", event.type);
|
|
127
138
|
this.#ports.listenerFailed(error instanceof Error ? error.message : String(error));
|
|
128
139
|
}
|
|
129
140
|
}
|
|
130
141
|
}
|
|
142
|
+
#recordFailure(reason, event) {
|
|
143
|
+
const { pending, bytes } = this.#queue.diagnostics();
|
|
144
|
+
this.#failure = { reason, event, generation: this.#ports.sessionGeneration(), pending, bytes };
|
|
145
|
+
if (this.#reported++ < 10) {
|
|
146
|
+
try {
|
|
147
|
+
this.#ports.incident?.(this.#failure);
|
|
148
|
+
}
|
|
149
|
+
catch { /* Invariant: diagnostics never own recovery. */ }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
131
152
|
/** The command outcomes reserved during an overload, in arrival order; clears the reservation. */
|
|
132
153
|
takeReservedOutcomes() {
|
|
133
154
|
const reserved = [...this.#reservedOutcomes];
|
|
@@ -172,6 +193,7 @@ export class PiEventDelivery {
|
|
|
172
193
|
// Invariant: an outcome reserved while reconciliation replayed the earlier ones is dropped with the overload.
|
|
173
194
|
this.#reservedOutcomes.clear();
|
|
174
195
|
this.#overload = undefined;
|
|
196
|
+
this.recovery.resume();
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
finally {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { AgentSession } from "../startup-public.js";
|
|
1
|
+
import type { AgentSession, AgentSessionRuntime } from "../startup-public.js";
|
|
2
2
|
import { type OwnedUiDiagnostics, type OwnedUiVisualExtensionSupport } from "../../../contracts/owned-ui/index.js";
|
|
3
3
|
export type OwnedPiVisualExtensionSupport = OwnedUiVisualExtensionSupport;
|
|
4
4
|
interface PiExtensionUiBindingPorts {
|
|
5
5
|
session(): AgentSession | undefined;
|
|
6
|
+
runtime(): AgentSessionRuntime | undefined;
|
|
6
7
|
diagnostic(severity: OwnedUiDiagnostics["severity"], code: string, message: string, recoverable: boolean): void;
|
|
7
8
|
emitView(): void;
|
|
8
9
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
2
2
|
import { isRecord } from "./message-values.js";
|
|
3
|
+
import { missingSessionCwdRecovery } from "./session-cwd-recovery.js";
|
|
4
|
+
import { switchToRecoveredFork } from "./runtime-integration.js";
|
|
3
5
|
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiExtensionUiPort, } from "../../../contracts/owned-ui/index.js";
|
|
4
6
|
/**
|
|
5
7
|
* The owned extension UI bridge's attachment to pinned Pi sessions: the bridge the shell hands
|
|
@@ -54,8 +56,51 @@ export class PiExtensionUiBinding {
|
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
58
|
try {
|
|
59
|
+
const runtime = this.#ports.runtime();
|
|
57
60
|
await session.bindExtensions({
|
|
58
61
|
uiContext: ui,
|
|
62
|
+
...(runtime === undefined ? {} : {
|
|
63
|
+
commandContextActions: {
|
|
64
|
+
waitForIdle: () => session.waitForIdle(),
|
|
65
|
+
newSession: options => runtime.newSession(options),
|
|
66
|
+
fork: (entryId, options) => runtime.fork(entryId, options),
|
|
67
|
+
navigateTree: async (targetId, options) => {
|
|
68
|
+
const result = await session.navigateTree(targetId, options);
|
|
69
|
+
return { cancelled: result.cancelled === true };
|
|
70
|
+
},
|
|
71
|
+
reload: async () => { await session.reload(); await this.rebind(); },
|
|
72
|
+
switchSession: async (path, options) => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await runtime.switchSession(path, options);
|
|
75
|
+
if (!result.cancelled)
|
|
76
|
+
ui.notify("Resumed session", "info");
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const recovery = missingSessionCwdRecovery(error);
|
|
81
|
+
if (recovery === undefined)
|
|
82
|
+
throw error;
|
|
83
|
+
const selected = await ui.select(recovery.message, recovery.options.map(option => option.label));
|
|
84
|
+
const choice = recovery.options.find(option => option.label === selected)?.id;
|
|
85
|
+
const cwd = recovery.cwdRecovery.fallbackCwd;
|
|
86
|
+
if (choice === "fork") {
|
|
87
|
+
const result = await switchToRecoveredFork(runtime, path, cwd, options);
|
|
88
|
+
if (!result.cancelled)
|
|
89
|
+
ui.notify(result.message, "info");
|
|
90
|
+
return { cancelled: result.cancelled };
|
|
91
|
+
}
|
|
92
|
+
if (choice === "continue") {
|
|
93
|
+
const result = await runtime.switchSession(path, { ...options, cwdOverride: cwd });
|
|
94
|
+
if (!result.cancelled)
|
|
95
|
+
ui.notify(`Resumed session in ${cwd}`, "info");
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
ui.notify("Resume cancelled", "info");
|
|
99
|
+
return { cancelled: true };
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
59
104
|
mode: "tui",
|
|
60
105
|
shutdownHandler: () => this.#extensionShutdown?.(),
|
|
61
106
|
onError: error => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DeliveryFailure } from "./event-delivery.js";
|
|
1
2
|
import { type StartupPhase } from "../../../foundation/startup/startup-runtime.js";
|
|
2
3
|
/**
|
|
3
4
|
* What one engine session may ask of the process-level host that created it. The host owns
|
|
@@ -6,6 +7,7 @@ import { type StartupPhase } from "../../../foundation/startup/startup-runtime.j
|
|
|
6
7
|
export interface PiEngineHostSessionPorts {
|
|
7
8
|
/** Aborted when the host is disposed; host-level timers and probes stop with it. */
|
|
8
9
|
readonly signal: AbortSignal;
|
|
10
|
+
deliveryIncident?(failure: DeliveryFailure): void;
|
|
9
11
|
/** Record a startup trace phase; the host records each phase once per process. */
|
|
10
12
|
markStartupPhase(phase: StartupPhase): Promise<void>;
|
|
11
13
|
/** A session's runtime loaded the profile's HTTP idle timeout; the host installs the dispatcher once. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DeliveryFailure } from "./event-delivery.js";
|
|
1
2
|
import { PiEngineAdapter } from "./adapter.js";
|
|
2
3
|
import { type StartupPhase } from "../../../foundation/startup/startup-runtime.js";
|
|
3
4
|
import type { PiEnginePackageUpdateProbe, PiEngineRuntimeFactory, PiRepositoryContextReader } from "./session-runtime.js";
|
|
@@ -21,6 +22,7 @@ export interface PiEngineHostTheme {
|
|
|
21
22
|
}
|
|
22
23
|
export interface PiEngineHostOptions {
|
|
23
24
|
readonly productMode: "bare" | "comparison";
|
|
25
|
+
readonly deliveryIncident?: (failure: DeliveryFailure) => void;
|
|
24
26
|
readonly theme: PiEngineHostTheme;
|
|
25
27
|
/** Themes installed on this machine, listed by the component adapter that owns the theme unit. */
|
|
26
28
|
readonly availableThemes?: () => readonly string[];
|
|
@@ -41,6 +43,7 @@ export interface PiEngineSessionRequest extends OwnedUiSessionRequest {
|
|
|
41
43
|
readonly sessionSelection?: PiSessionSelection;
|
|
42
44
|
readonly sessionForkPrompt?: PiSessionForkPrompt;
|
|
43
45
|
readonly repositoryContextReader?: PiRepositoryContextReader;
|
|
46
|
+
readonly worktreeCwdReader?: import("./worktree-tools.js").PiWorktreeCwdReader;
|
|
44
47
|
}
|
|
45
48
|
/** The dispatcher policy in force for the process and the session that put it there. */
|
|
46
49
|
export interface PiEngineHttpPolicy {
|
|
@@ -26,6 +26,7 @@ class ProcessPiEngineHost {
|
|
|
26
26
|
this.#markStartupPhase = options.markStartupPhase ?? (phase => markStartupPhase(process.env, phase));
|
|
27
27
|
this.#sessionPorts = {
|
|
28
28
|
signal: this.#abort.signal,
|
|
29
|
+
deliveryIncident: failure => options.deliveryIncident?.(failure),
|
|
29
30
|
markStartupPhase: phase => this.#tracePhase(phase),
|
|
30
31
|
httpPolicyLoaded: (timeoutMs, sessionId) => { this.#applyHttpPolicy(timeoutMs, sessionId, "startup"); },
|
|
31
32
|
httpPolicyChanged: (timeoutMs, sessionId) => { this.#applyHttpPolicy(timeoutMs, sessionId, "setting"); },
|
|
@@ -68,6 +69,7 @@ class ProcessPiEngineHost {
|
|
|
68
69
|
...(request.sessionSelection === undefined ? {} : { sessionSelection: request.sessionSelection }),
|
|
69
70
|
...(request.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: request.sessionForkPrompt }),
|
|
70
71
|
...(request.repositoryContextReader === undefined ? {} : { repositoryContextReader: request.repositoryContextReader }),
|
|
72
|
+
...(request.worktreeCwdReader === undefined ? {} : { worktreeCwdReader: request.worktreeCwdReader }),
|
|
71
73
|
});
|
|
72
74
|
this.#sessions.set(sessionId, adapter);
|
|
73
75
|
try {
|
|
@@ -17,6 +17,8 @@ export function createPiPackagesPort(input) {
|
|
|
17
17
|
const { profileRoot, cwd } = input;
|
|
18
18
|
// Compatibility: model-only refresh bypasses package settings and their diagnostics, as Pi does.
|
|
19
19
|
let packageManager;
|
|
20
|
+
let updateChanges = [];
|
|
21
|
+
let updateWarningCount = 0;
|
|
20
22
|
const manager = () => {
|
|
21
23
|
if (packageManager)
|
|
22
24
|
return packageManager;
|
|
@@ -29,9 +31,16 @@ export function createPiPackagesPort(input) {
|
|
|
29
31
|
}
|
|
30
32
|
packageManager = new DefaultPackageManager({ cwd, agentDir: profileRoot, settingsManager });
|
|
31
33
|
packageManager.setProgressCallback(event => {
|
|
32
|
-
if (event.type
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
if (event.type === "result" && event.action === "update" && event.message) {
|
|
35
|
+
updateChanges.push(event.message);
|
|
36
|
+
}
|
|
37
|
+
else if (event.type === "start") {
|
|
38
|
+
input.onProgress?.({ operation: operationFor(event.action), message: String(event.message) });
|
|
39
|
+
}
|
|
40
|
+
else if (event.type === "progress" && event.message) {
|
|
41
|
+
updateWarningCount++;
|
|
42
|
+
input.onProgress?.({ operation: operationFor(event.action), message: event.message, severity: "warning" });
|
|
43
|
+
}
|
|
35
44
|
});
|
|
36
45
|
return packageManager;
|
|
37
46
|
};
|
|
@@ -65,8 +74,14 @@ export function createPiPackagesPort(input) {
|
|
|
65
74
|
async update(source) {
|
|
66
75
|
return await attempt("update", source ?? null, async () => {
|
|
67
76
|
// Compatibility: Pi owns source identity and its contextual no-match suggestions.
|
|
77
|
+
updateChanges = [];
|
|
78
|
+
updateWarningCount = 0;
|
|
68
79
|
await manager().update(source);
|
|
69
|
-
|
|
80
|
+
// Compatibility: Pi's offline predicate is not part of its package-root public API.
|
|
81
|
+
const offline = ["1", "true", "yes"].includes(process.env.PI_OFFLINE?.toLowerCase() ?? "");
|
|
82
|
+
return agentPackageOutcome("update", "completed", null, source ?? null, configured(), {
|
|
83
|
+
offline, warningCount: updateWarningCount, changes: updateChanges,
|
|
84
|
+
});
|
|
70
85
|
});
|
|
71
86
|
},
|
|
72
87
|
async refreshModels() {
|
|
@@ -2,8 +2,11 @@ import type { OwnedUiEvent } from "../../../contracts/owned-ui/index.js";
|
|
|
2
2
|
/** Intrusive FIFO + segment index: replacement unlinks in O(1), never changes source ordering. */
|
|
3
3
|
export declare class PendingEngineDelivery {
|
|
4
4
|
#private;
|
|
5
|
+
rejection: "event-count" | "retained-bytes" | null;
|
|
5
6
|
private readonly retain;
|
|
6
|
-
|
|
7
|
+
private readonly current;
|
|
8
|
+
private readonly externalBytes;
|
|
9
|
+
constructor(retain?: (event: OwnedUiEvent) => () => void, current?: () => unknown, externalBytes?: (excluding?: OwnedUiEvent) => number);
|
|
7
10
|
get size(): number;
|
|
8
11
|
diagnostics(): {
|
|
9
12
|
pending: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DeliveryRetention } from "./delivery-retention.js";
|
|
1
2
|
const MAX_PENDING_EVENTS = 1024;
|
|
2
3
|
const MAX_PENDING_EVENT_BYTES = 8 * 1024 * 1024;
|
|
3
4
|
/** Actual shell semantics: only live complete block state has no independent side effect. */
|
|
@@ -29,21 +30,32 @@ export class PendingEngineDelivery {
|
|
|
29
30
|
#head;
|
|
30
31
|
#tail;
|
|
31
32
|
#size = 0;
|
|
32
|
-
#
|
|
33
|
+
#retention = new DeliveryRetention();
|
|
33
34
|
#protected = 0;
|
|
34
35
|
#generation = -1;
|
|
35
36
|
#superseded = 0;
|
|
36
37
|
#peakNodes = 0;
|
|
37
38
|
#peakBytes = 0;
|
|
39
|
+
rejection = null;
|
|
38
40
|
retain;
|
|
39
|
-
|
|
41
|
+
current;
|
|
42
|
+
externalBytes;
|
|
43
|
+
constructor(retain, current, externalBytes) {
|
|
44
|
+
this.retain = retain;
|
|
45
|
+
this.current = current;
|
|
46
|
+
this.externalBytes = externalBytes;
|
|
47
|
+
}
|
|
40
48
|
get size() { return this.#size; }
|
|
49
|
+
get #bytes() { return this.#retention.bytes + this.#size * 128 + (this.externalBytes?.() ?? 0); }
|
|
41
50
|
diagnostics() {
|
|
42
51
|
return { pending: this.#size, bytes: this.#bytes, protected: this.#protected,
|
|
43
52
|
superseded: this.#superseded, peakNodes: this.#peakNodes, peakBytes: this.#peakBytes };
|
|
44
53
|
}
|
|
45
54
|
/** False means not admitted. The caller must enter its reserved recovery protocol, never evict. */
|
|
46
55
|
push(event, generation, reconcile) {
|
|
56
|
+
this.rejection = null;
|
|
57
|
+
if (this.current)
|
|
58
|
+
this.#retention.baseline(this.current());
|
|
47
59
|
const key = replaceableEventKey(event);
|
|
48
60
|
if (generation !== this.#generation || key === null) {
|
|
49
61
|
if (!this.seal())
|
|
@@ -51,14 +63,23 @@ export class PendingEngineDelivery {
|
|
|
51
63
|
this.#generation = generation;
|
|
52
64
|
}
|
|
53
65
|
const prior = key === null ? undefined : this.#replaceable.get(key);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
// Compatibility: production owns exact immutable revisions through the ledger. Legacy standalone
|
|
67
|
+
// queues retain the compact-marker contract when no authoritative owner is supplied.
|
|
68
|
+
const marker = this.current === undefined && key !== null && retainedEventBytes(event) > 64 * 1024 && reconcile !== undefined;
|
|
69
|
+
const charge = marker ? undefined : this.#retention.retain(event);
|
|
70
|
+
const release = marker ? undefined : this.retain?.(event);
|
|
71
|
+
prior?.charge?.();
|
|
72
|
+
const bytes = this.#retention.bytes + this.#size * 128 + (this.externalBytes?.(prior?.event) ?? 0);
|
|
58
73
|
if (this.#size - (prior === undefined ? 0 : 1) >= MAX_PENDING_EVENTS
|
|
59
|
-
||
|
|
74
|
+
|| bytes + (prior === undefined ? 128 : 0) > MAX_PENDING_EVENT_BYTES) {
|
|
75
|
+
this.rejection = this.#size - (prior === undefined ? 0 : 1) >= MAX_PENDING_EVENTS ? "event-count" : "retained-bytes";
|
|
76
|
+
charge?.();
|
|
77
|
+
release?.();
|
|
78
|
+
if (prior?.event)
|
|
79
|
+
prior.charge = this.#retention.retain(prior.event);
|
|
60
80
|
return false;
|
|
61
|
-
|
|
81
|
+
}
|
|
82
|
+
const node = { key, generation, charge, ...(marker ? { resolve: reconcile } : { event, release }) };
|
|
62
83
|
if (prior !== undefined) {
|
|
63
84
|
this.#unlink(prior);
|
|
64
85
|
this.#superseded = Math.min(Number.MAX_SAFE_INTEGER, this.#superseded + 1);
|
|
@@ -70,7 +91,6 @@ export class PendingEngineDelivery {
|
|
|
70
91
|
this.#head = node;
|
|
71
92
|
this.#tail = node;
|
|
72
93
|
this.#size++;
|
|
73
|
-
this.#bytes += bytes;
|
|
74
94
|
if (key === null)
|
|
75
95
|
this.#protected++;
|
|
76
96
|
else
|
|
@@ -101,9 +121,17 @@ export class PendingEngineDelivery {
|
|
|
101
121
|
return undefined;
|
|
102
122
|
const event = node.event ?? node.resolve();
|
|
103
123
|
const release = node.release ?? this.retain?.(event);
|
|
124
|
+
const charge = node.charge;
|
|
104
125
|
this.#unlink(node, false);
|
|
105
|
-
// Invariant:
|
|
106
|
-
|
|
126
|
+
// Invariant: keep the exact revision and assets charged through reentrant listener production.
|
|
127
|
+
let released = false;
|
|
128
|
+
return { event, generation: node.generation, release: () => {
|
|
129
|
+
if (released)
|
|
130
|
+
return;
|
|
131
|
+
released = true;
|
|
132
|
+
charge?.();
|
|
133
|
+
release?.();
|
|
134
|
+
} };
|
|
107
135
|
}
|
|
108
136
|
// Invariant: a barrier freezes lazy authoritative markers before a later segment can change them.
|
|
109
137
|
seal() {
|
|
@@ -111,22 +139,28 @@ export class PendingEngineDelivery {
|
|
|
111
139
|
if (node.resolve === undefined)
|
|
112
140
|
continue;
|
|
113
141
|
const event = node.resolve();
|
|
114
|
-
const
|
|
115
|
-
|
|
142
|
+
const charge = this.#retention.retain(event);
|
|
143
|
+
const release = this.retain?.(event);
|
|
144
|
+
if (this.#bytes > MAX_PENDING_EVENT_BYTES) {
|
|
145
|
+
this.rejection = "retained-bytes";
|
|
146
|
+
charge();
|
|
147
|
+
release?.();
|
|
116
148
|
return false;
|
|
117
|
-
|
|
118
|
-
node.
|
|
149
|
+
}
|
|
150
|
+
node.charge = charge;
|
|
119
151
|
node.event = event;
|
|
120
152
|
node.resolve = undefined;
|
|
121
|
-
node.release =
|
|
153
|
+
node.release = release;
|
|
122
154
|
this.#peakBytes = Math.max(this.#peakBytes, this.#bytes);
|
|
123
155
|
}
|
|
124
156
|
this.#replaceable.clear();
|
|
125
157
|
return true;
|
|
126
158
|
}
|
|
127
159
|
#unlink(node, release = true) {
|
|
128
|
-
if (release)
|
|
160
|
+
if (release) {
|
|
161
|
+
node.charge?.();
|
|
129
162
|
node.release?.();
|
|
163
|
+
}
|
|
130
164
|
if (node.previous !== undefined)
|
|
131
165
|
node.previous.next = node.next;
|
|
132
166
|
else
|
|
@@ -136,7 +170,6 @@ export class PendingEngineDelivery {
|
|
|
136
170
|
else
|
|
137
171
|
this.#tail = node.previous;
|
|
138
172
|
this.#size--;
|
|
139
|
-
this.#bytes -= node.bytes;
|
|
140
173
|
if (node.key === null)
|
|
141
174
|
this.#protected--;
|
|
142
175
|
else if (this.#replaceable.get(node.key) === node)
|