@jmoyers/harness 0.1.11 → 0.1.20
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 +31 -39
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/packages/harness-ui/src/modal-manager.ts +222 -0
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3872
- package/scripts/control-plane-daemon.ts +11 -0
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3019
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +348 -8
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +864 -70
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/workspace.ts +68 -5
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +13 -131
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +77 -12
- package/src/mux/live-mux/modal-overlays.ts +168 -34
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +10 -101
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +73 -46
- package/src/services/runtime-conversation-starter.ts +53 -45
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -72
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +360 -56
- package/src/store/event-store.ts +366 -8
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -195
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -137
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -279
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -269
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/modals/manager.ts +0 -218
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { NormalizedEventEnvelope } from '../events/normalized-events.ts';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_EVENT_RETENTION_MS = 72 * 60 * 60 * 1000;
|
|
5
|
+
const DEFAULT_TELEMETRY_RETENTION_MS = 72 * 60 * 60 * 1000;
|
|
6
|
+
const DEFAULT_MAINTENANCE_INTERVAL_MS = 5000;
|
|
7
|
+
const DEFAULT_PRUNE_BATCH_SIZE = 500;
|
|
8
|
+
const DEFAULT_COMPACT_FREELIST_PAGES = 256;
|
|
9
|
+
const DEFAULT_COPY_FORWARD_BATCH_SIZE = 250;
|
|
10
|
+
const DEFAULT_COPY_FORWARD_FINALIZE_TAIL_ROWS = 500;
|
|
11
|
+
const DEFAULT_TELEMETRY_PAYLOAD_MAX_BYTES = 16 * 1024;
|
|
12
|
+
const DEFAULT_TEXT_DELTA_PAYLOAD_MAX_BYTES = 32 * 1024;
|
|
13
|
+
const DEFAULT_TEXT_DELTA_COALESCE_WINDOW_MS = 1200;
|
|
14
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
15
|
+
|
|
16
|
+
export interface StorageLifecyclePolicy {
|
|
17
|
+
readonly eventRetentionMs: number;
|
|
18
|
+
readonly telemetryRetentionMs: number;
|
|
19
|
+
readonly maintenanceIntervalMs: number;
|
|
20
|
+
readonly pruneBatchSize: number;
|
|
21
|
+
readonly compactFreelistPages: number;
|
|
22
|
+
readonly copyForwardBatchSize: number;
|
|
23
|
+
readonly copyForwardFinalizeTailRows: number;
|
|
24
|
+
readonly telemetryPayloadMaxBytes: number;
|
|
25
|
+
readonly textDeltaPayloadMaxBytes: number;
|
|
26
|
+
readonly textDeltaCoalesceWindowMs: number;
|
|
27
|
+
readonly busyTimeoutMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_STORAGE_LIFECYCLE_POLICY: StorageLifecyclePolicy = {
|
|
31
|
+
eventRetentionMs: DEFAULT_EVENT_RETENTION_MS,
|
|
32
|
+
telemetryRetentionMs: DEFAULT_TELEMETRY_RETENTION_MS,
|
|
33
|
+
maintenanceIntervalMs: DEFAULT_MAINTENANCE_INTERVAL_MS,
|
|
34
|
+
pruneBatchSize: DEFAULT_PRUNE_BATCH_SIZE,
|
|
35
|
+
compactFreelistPages: DEFAULT_COMPACT_FREELIST_PAGES,
|
|
36
|
+
copyForwardBatchSize: DEFAULT_COPY_FORWARD_BATCH_SIZE,
|
|
37
|
+
copyForwardFinalizeTailRows: DEFAULT_COPY_FORWARD_FINALIZE_TAIL_ROWS,
|
|
38
|
+
telemetryPayloadMaxBytes: DEFAULT_TELEMETRY_PAYLOAD_MAX_BYTES,
|
|
39
|
+
textDeltaPayloadMaxBytes: DEFAULT_TEXT_DELTA_PAYLOAD_MAX_BYTES,
|
|
40
|
+
textDeltaCoalesceWindowMs: DEFAULT_TEXT_DELTA_COALESCE_WINDOW_MS,
|
|
41
|
+
busyTimeoutMs: DEFAULT_BUSY_TIMEOUT_MS,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface StorageLifecycleCompactionStepResult {
|
|
45
|
+
readonly state: 'idle' | 'copying' | 'finalized';
|
|
46
|
+
readonly copiedRows: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type WalCheckpointMode = 'PASSIVE' | 'TRUNCATE';
|
|
50
|
+
|
|
51
|
+
export interface StorageLifecycleEventStore {
|
|
52
|
+
pruneEventsOlderThan(cutoffTs: string, limit: number): number;
|
|
53
|
+
checkpointWal(mode?: WalCheckpointMode): void;
|
|
54
|
+
compactFreelistPages(maxPages: number): void;
|
|
55
|
+
runOnlineCopyForwardCompactionStep?(
|
|
56
|
+
batchSize: number,
|
|
57
|
+
finalizeTailRows: number,
|
|
58
|
+
): StorageLifecycleCompactionStepResult;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface StorageLifecycleTelemetryStore {
|
|
62
|
+
pruneTelemetryOlderThan(cutoffIngestedAt: string, limit: number): number;
|
|
63
|
+
checkpointWal(mode?: WalCheckpointMode): void;
|
|
64
|
+
compactFreelistPages(maxPages: number): void;
|
|
65
|
+
runOnlineCopyForwardCompactionStep?(
|
|
66
|
+
batchSize: number,
|
|
67
|
+
finalizeTailRows: number,
|
|
68
|
+
): StorageLifecycleCompactionStepResult;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface StorageLifecycleCoreOptions {
|
|
72
|
+
readonly eventStore?: StorageLifecycleEventStore | null;
|
|
73
|
+
readonly telemetryStore?: StorageLifecycleTelemetryStore | null;
|
|
74
|
+
readonly policy?: Partial<StorageLifecyclePolicy>;
|
|
75
|
+
readonly nowMs?: () => number;
|
|
76
|
+
readonly writeStderr?: (text: string) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface StorageLifecycleMaintenanceResult {
|
|
80
|
+
readonly ran: boolean;
|
|
81
|
+
readonly eventsPruned: number;
|
|
82
|
+
readonly telemetryPruned: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizePositiveInt(value: number | undefined, fallback: number): number {
|
|
86
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
return Math.floor(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizePolicy(
|
|
93
|
+
policy: Partial<StorageLifecyclePolicy> | undefined,
|
|
94
|
+
): StorageLifecyclePolicy {
|
|
95
|
+
return {
|
|
96
|
+
eventRetentionMs: normalizePositiveInt(
|
|
97
|
+
policy?.eventRetentionMs,
|
|
98
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.eventRetentionMs,
|
|
99
|
+
),
|
|
100
|
+
telemetryRetentionMs: normalizePositiveInt(
|
|
101
|
+
policy?.telemetryRetentionMs,
|
|
102
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.telemetryRetentionMs,
|
|
103
|
+
),
|
|
104
|
+
maintenanceIntervalMs: normalizePositiveInt(
|
|
105
|
+
policy?.maintenanceIntervalMs,
|
|
106
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.maintenanceIntervalMs,
|
|
107
|
+
),
|
|
108
|
+
pruneBatchSize: normalizePositiveInt(
|
|
109
|
+
policy?.pruneBatchSize,
|
|
110
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.pruneBatchSize,
|
|
111
|
+
),
|
|
112
|
+
compactFreelistPages: normalizePositiveInt(
|
|
113
|
+
policy?.compactFreelistPages,
|
|
114
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.compactFreelistPages,
|
|
115
|
+
),
|
|
116
|
+
copyForwardBatchSize: normalizePositiveInt(
|
|
117
|
+
policy?.copyForwardBatchSize,
|
|
118
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.copyForwardBatchSize,
|
|
119
|
+
),
|
|
120
|
+
copyForwardFinalizeTailRows: normalizePositiveInt(
|
|
121
|
+
policy?.copyForwardFinalizeTailRows,
|
|
122
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.copyForwardFinalizeTailRows,
|
|
123
|
+
),
|
|
124
|
+
telemetryPayloadMaxBytes: normalizePositiveInt(
|
|
125
|
+
policy?.telemetryPayloadMaxBytes,
|
|
126
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.telemetryPayloadMaxBytes,
|
|
127
|
+
),
|
|
128
|
+
textDeltaPayloadMaxBytes: normalizePositiveInt(
|
|
129
|
+
policy?.textDeltaPayloadMaxBytes,
|
|
130
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.textDeltaPayloadMaxBytes,
|
|
131
|
+
),
|
|
132
|
+
textDeltaCoalesceWindowMs: normalizePositiveInt(
|
|
133
|
+
policy?.textDeltaCoalesceWindowMs,
|
|
134
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.textDeltaCoalesceWindowMs,
|
|
135
|
+
),
|
|
136
|
+
busyTimeoutMs: normalizePositiveInt(
|
|
137
|
+
policy?.busyTimeoutMs,
|
|
138
|
+
DEFAULT_STORAGE_LIFECYCLE_POLICY.busyTimeoutMs,
|
|
139
|
+
),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizePolicyWithFallback(
|
|
144
|
+
policy: Partial<StorageLifecyclePolicy> | undefined,
|
|
145
|
+
fallback: StorageLifecyclePolicy,
|
|
146
|
+
): StorageLifecyclePolicy {
|
|
147
|
+
return {
|
|
148
|
+
eventRetentionMs: normalizePositiveInt(policy?.eventRetentionMs, fallback.eventRetentionMs),
|
|
149
|
+
telemetryRetentionMs: normalizePositiveInt(
|
|
150
|
+
policy?.telemetryRetentionMs,
|
|
151
|
+
fallback.telemetryRetentionMs,
|
|
152
|
+
),
|
|
153
|
+
maintenanceIntervalMs: normalizePositiveInt(
|
|
154
|
+
policy?.maintenanceIntervalMs,
|
|
155
|
+
fallback.maintenanceIntervalMs,
|
|
156
|
+
),
|
|
157
|
+
pruneBatchSize: normalizePositiveInt(policy?.pruneBatchSize, fallback.pruneBatchSize),
|
|
158
|
+
compactFreelistPages: normalizePositiveInt(
|
|
159
|
+
policy?.compactFreelistPages,
|
|
160
|
+
fallback.compactFreelistPages,
|
|
161
|
+
),
|
|
162
|
+
copyForwardBatchSize: normalizePositiveInt(
|
|
163
|
+
policy?.copyForwardBatchSize,
|
|
164
|
+
fallback.copyForwardBatchSize,
|
|
165
|
+
),
|
|
166
|
+
copyForwardFinalizeTailRows: normalizePositiveInt(
|
|
167
|
+
policy?.copyForwardFinalizeTailRows,
|
|
168
|
+
fallback.copyForwardFinalizeTailRows,
|
|
169
|
+
),
|
|
170
|
+
telemetryPayloadMaxBytes: normalizePositiveInt(
|
|
171
|
+
policy?.telemetryPayloadMaxBytes,
|
|
172
|
+
fallback.telemetryPayloadMaxBytes,
|
|
173
|
+
),
|
|
174
|
+
textDeltaPayloadMaxBytes: normalizePositiveInt(
|
|
175
|
+
policy?.textDeltaPayloadMaxBytes,
|
|
176
|
+
fallback.textDeltaPayloadMaxBytes,
|
|
177
|
+
),
|
|
178
|
+
textDeltaCoalesceWindowMs: normalizePositiveInt(
|
|
179
|
+
policy?.textDeltaCoalesceWindowMs,
|
|
180
|
+
fallback.textDeltaCoalesceWindowMs,
|
|
181
|
+
),
|
|
182
|
+
busyTimeoutMs: normalizePositiveInt(policy?.busyTimeoutMs, fallback.busyTimeoutMs),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseIsoMs(value: string): number | null {
|
|
187
|
+
const parsed = Date.parse(value);
|
|
188
|
+
if (!Number.isFinite(parsed)) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return parsed;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function safeJsonStringify(value: unknown): string {
|
|
195
|
+
try {
|
|
196
|
+
return JSON.stringify(value);
|
|
197
|
+
} catch {
|
|
198
|
+
return '"[unserializable]"';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function asTextDeltaEvent(event: NormalizedEventEnvelope):
|
|
203
|
+
| (NormalizedEventEnvelope & {
|
|
204
|
+
readonly source: 'provider';
|
|
205
|
+
readonly type: 'provider-text-delta';
|
|
206
|
+
readonly payload: {
|
|
207
|
+
readonly kind: 'text-delta';
|
|
208
|
+
readonly threadId: string;
|
|
209
|
+
readonly turnId: string;
|
|
210
|
+
readonly delta: string;
|
|
211
|
+
};
|
|
212
|
+
})
|
|
213
|
+
| null {
|
|
214
|
+
if (event.source !== 'provider' || event.type !== 'provider-text-delta') {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
if (event.payload.kind !== 'text-delta') {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (
|
|
221
|
+
typeof event.payload.threadId !== 'string' ||
|
|
222
|
+
typeof event.payload.turnId !== 'string' ||
|
|
223
|
+
typeof event.payload.delta !== 'string'
|
|
224
|
+
) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return event as NormalizedEventEnvelope & {
|
|
228
|
+
readonly source: 'provider';
|
|
229
|
+
readonly type: 'provider-text-delta';
|
|
230
|
+
readonly payload: {
|
|
231
|
+
readonly kind: 'text-delta';
|
|
232
|
+
readonly threadId: string;
|
|
233
|
+
readonly turnId: string;
|
|
234
|
+
readonly delta: string;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function sameEventScope(left: NormalizedEventEnvelope, right: NormalizedEventEnvelope): boolean {
|
|
240
|
+
return (
|
|
241
|
+
left.scope.tenantId === right.scope.tenantId &&
|
|
242
|
+
left.scope.userId === right.scope.userId &&
|
|
243
|
+
left.scope.workspaceId === right.scope.workspaceId &&
|
|
244
|
+
left.scope.worktreeId === right.scope.worktreeId &&
|
|
245
|
+
left.scope.conversationId === right.scope.conversationId &&
|
|
246
|
+
(left.scope.turnId ?? null) === (right.scope.turnId ?? null)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export class StorageLifecycleCore {
|
|
251
|
+
private readonly eventStore: StorageLifecycleEventStore | null;
|
|
252
|
+
private readonly telemetryStore: StorageLifecycleTelemetryStore | null;
|
|
253
|
+
private policyValues: StorageLifecyclePolicy;
|
|
254
|
+
private readonly nowMs: () => number;
|
|
255
|
+
private readonly writeStderr: (text: string) => void;
|
|
256
|
+
private nextMaintenanceAtMs = 0;
|
|
257
|
+
|
|
258
|
+
constructor(options: StorageLifecycleCoreOptions = {}) {
|
|
259
|
+
this.eventStore = options.eventStore ?? null;
|
|
260
|
+
this.telemetryStore = options.telemetryStore ?? null;
|
|
261
|
+
this.policyValues = normalizePolicy(options.policy);
|
|
262
|
+
this.nowMs = options.nowMs ?? Date.now;
|
|
263
|
+
this.writeStderr = options.writeStderr ?? ((text) => process.stderr.write(text));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
policy(): StorageLifecyclePolicy {
|
|
267
|
+
return this.policyValues;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
updatePolicy(policy: Partial<StorageLifecyclePolicy>): {
|
|
271
|
+
readonly previous: StorageLifecyclePolicy;
|
|
272
|
+
readonly current: StorageLifecyclePolicy;
|
|
273
|
+
readonly maintenanceIntervalChanged: boolean;
|
|
274
|
+
} {
|
|
275
|
+
const previous = this.policyValues;
|
|
276
|
+
const next = normalizePolicyWithFallback(policy, previous);
|
|
277
|
+
this.policyValues = next;
|
|
278
|
+
if (next.maintenanceIntervalMs < previous.maintenanceIntervalMs) {
|
|
279
|
+
this.nextMaintenanceAtMs = Math.min(
|
|
280
|
+
this.nextMaintenanceAtMs,
|
|
281
|
+
this.nowMs() + next.maintenanceIntervalMs,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
previous,
|
|
286
|
+
current: next,
|
|
287
|
+
maintenanceIntervalChanged: previous.maintenanceIntervalMs !== next.maintenanceIntervalMs,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
prepareEventBatch(
|
|
292
|
+
events: readonly NormalizedEventEnvelope[],
|
|
293
|
+
): readonly NormalizedEventEnvelope[] {
|
|
294
|
+
if (events.length < 2) {
|
|
295
|
+
return events;
|
|
296
|
+
}
|
|
297
|
+
const merged: NormalizedEventEnvelope[] = [];
|
|
298
|
+
for (const event of events) {
|
|
299
|
+
const previous = merged.length === 0 ? null : merged[merged.length - 1]!;
|
|
300
|
+
if (previous === null) {
|
|
301
|
+
merged.push(event);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const left = asTextDeltaEvent(previous);
|
|
306
|
+
const right = asTextDeltaEvent(event);
|
|
307
|
+
if (left === null || right === null) {
|
|
308
|
+
merged.push(event);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (!sameEventScope(left, right)) {
|
|
312
|
+
merged.push(event);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (
|
|
316
|
+
left.payload.threadId !== right.payload.threadId ||
|
|
317
|
+
left.payload.turnId !== right.payload.turnId
|
|
318
|
+
) {
|
|
319
|
+
merged.push(event);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const leftMs = parseIsoMs(left.ts);
|
|
324
|
+
const rightMs = parseIsoMs(right.ts);
|
|
325
|
+
if (leftMs === null || rightMs === null) {
|
|
326
|
+
merged.push(event);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (rightMs < leftMs || rightMs - leftMs > this.policyValues.textDeltaCoalesceWindowMs) {
|
|
330
|
+
merged.push(event);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const mergedDelta = `${left.payload.delta}${right.payload.delta}`;
|
|
335
|
+
const mergedBytes = Buffer.byteLength(mergedDelta, 'utf8');
|
|
336
|
+
if (mergedBytes > this.policyValues.textDeltaPayloadMaxBytes) {
|
|
337
|
+
merged.push(event);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const nextEvent: NormalizedEventEnvelope = {
|
|
342
|
+
...left,
|
|
343
|
+
ts: right.ts,
|
|
344
|
+
payload: {
|
|
345
|
+
...left.payload,
|
|
346
|
+
delta: mergedDelta,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
merged[merged.length - 1] = nextEvent;
|
|
350
|
+
}
|
|
351
|
+
return merged;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
prepareTelemetryPayload(payload: Record<string, unknown>): Record<string, unknown> {
|
|
355
|
+
const serialized = safeJsonStringify(payload);
|
|
356
|
+
const serializedBytes = Buffer.byteLength(serialized, 'utf8');
|
|
357
|
+
if (serializedBytes <= this.policyValues.telemetryPayloadMaxBytes) {
|
|
358
|
+
return payload;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const metadata = {
|
|
362
|
+
truncated: true,
|
|
363
|
+
originalBytes: serializedBytes,
|
|
364
|
+
maxBytes: this.policyValues.telemetryPayloadMaxBytes,
|
|
365
|
+
sha256: createHash('sha256').update(serialized).digest('hex'),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
let previewChars = Math.min(serialized.length, 4096);
|
|
369
|
+
while (previewChars > 0) {
|
|
370
|
+
const candidate: Record<string, unknown> = {
|
|
371
|
+
storageLifecycle: metadata,
|
|
372
|
+
previewJson: serialized.slice(0, previewChars),
|
|
373
|
+
};
|
|
374
|
+
const candidateBytes = Buffer.byteLength(safeJsonStringify(candidate), 'utf8');
|
|
375
|
+
if (candidateBytes <= this.policyValues.telemetryPayloadMaxBytes) {
|
|
376
|
+
return candidate;
|
|
377
|
+
}
|
|
378
|
+
previewChars = Math.floor(previewChars / 2);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
storageLifecycle: metadata,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
runMaintenanceTick(): StorageLifecycleMaintenanceResult {
|
|
387
|
+
const nowMs = this.nowMs();
|
|
388
|
+
if (nowMs < this.nextMaintenanceAtMs) {
|
|
389
|
+
return {
|
|
390
|
+
ran: false,
|
|
391
|
+
eventsPruned: 0,
|
|
392
|
+
telemetryPruned: 0,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
this.nextMaintenanceAtMs = nowMs + this.policyValues.maintenanceIntervalMs;
|
|
396
|
+
|
|
397
|
+
let eventsPruned = 0;
|
|
398
|
+
let telemetryPruned = 0;
|
|
399
|
+
|
|
400
|
+
if (this.eventStore !== null) {
|
|
401
|
+
try {
|
|
402
|
+
const cutoff = new Date(nowMs - this.policyValues.eventRetentionMs).toISOString();
|
|
403
|
+
eventsPruned = this.eventStore.pruneEventsOlderThan(
|
|
404
|
+
cutoff,
|
|
405
|
+
this.policyValues.pruneBatchSize,
|
|
406
|
+
);
|
|
407
|
+
if (eventsPruned > 0) {
|
|
408
|
+
this.eventStore.checkpointWal('PASSIVE');
|
|
409
|
+
}
|
|
410
|
+
} catch (error: unknown) {
|
|
411
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
412
|
+
this.writeStderr(`[storage-lifecycle] event maintenance failed: ${message}\n`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (this.telemetryStore !== null) {
|
|
417
|
+
try {
|
|
418
|
+
const cutoff = new Date(nowMs - this.policyValues.telemetryRetentionMs).toISOString();
|
|
419
|
+
telemetryPruned = this.telemetryStore.pruneTelemetryOlderThan(
|
|
420
|
+
cutoff,
|
|
421
|
+
this.policyValues.pruneBatchSize,
|
|
422
|
+
);
|
|
423
|
+
if (telemetryPruned > 0) {
|
|
424
|
+
this.telemetryStore.checkpointWal('PASSIVE');
|
|
425
|
+
}
|
|
426
|
+
} catch (error: unknown) {
|
|
427
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
428
|
+
this.writeStderr(`[storage-lifecycle] telemetry maintenance failed: ${message}\n`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
ran: true,
|
|
434
|
+
eventsPruned,
|
|
435
|
+
telemetryPruned,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|