open-multi-agent-kit 0.78.5 → 0.78.7
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/AGENTS.md +1 -1
- package/CHANGELOG.md +64 -0
- package/MATURITY.md +2 -2
- package/README.md +20 -5
- package/dist/brand/night-city.theme.json +80 -0
- package/dist/brand/palette.d.ts +73 -177
- package/dist/brand/palette.js +115 -39
- package/dist/brand/rust-forge.theme.json +216 -0
- package/dist/brand/theme-compiled.d.ts +61 -0
- package/dist/brand/theme-compiled.js +241 -0
- package/dist/brand/theme.js +50 -37
- package/dist/cli/release-promotion-gate.d.ts +8 -1
- package/dist/cli/release-promotion-gate.js +23 -11
- package/dist/cli/root.d.ts +1 -0
- package/dist/cli/root.js +2 -1
- package/dist/cli/theme/index.d.ts +11 -2
- package/dist/cli/theme/index.js +6 -1
- package/dist/cli/theme/oklab-quantize.d.ts +29 -0
- package/dist/cli/theme/oklab-quantize.js +105 -0
- package/dist/cli/theme/render-table.d.ts +59 -0
- package/dist/cli/theme/render-table.js +112 -0
- package/dist/cli/theme/status-frame.d.ts +10 -0
- package/dist/cli/theme/status-frame.js +22 -0
- package/dist/cli/theme/terminal-capability.d.ts +6 -1
- package/dist/cli/theme/terminal-capability.js +20 -4
- package/dist/cli/theme/theme-doc.d.ts +22 -0
- package/dist/cli/theme/theme-doc.js +97 -0
- package/dist/cli/theme/theme-registry.js +39 -18
- package/dist/cli/theme/tier-explain.d.ts +21 -0
- package/dist/cli/theme/tier-explain.js +60 -0
- package/dist/cli/ui/green-rain-renderer.js +3 -2
- package/dist/cli/ui/neon-grid-renderer.js +4 -3
- package/dist/cli/ui/rust-forge-renderer.js +17 -3
- package/dist/cli/ui/system24-renderer.d.ts +18 -1
- package/dist/cli/ui/system24-renderer.js +39 -16
- package/dist/cli/v2/chat-repl.js +3 -1
- package/dist/cli/v2/cli-v2-skeleton.d.ts +6 -0
- package/dist/cli/v2/cli-v2-skeleton.js +121 -9
- package/dist/cli/v2/interactive-prompt.d.ts +1 -0
- package/dist/cli/v2/interactive-prompt.js +6 -2
- package/dist/commands/chat/core.js +2 -2
- package/dist/commands/chat/runtime.js +2 -0
- package/dist/commands/chat/utils.js +12 -4
- package/dist/commands/cockpit/render.js +102 -64
- package/dist/commands/cockpit/utils.d.ts +2 -0
- package/dist/commands/design.js +18 -16
- package/dist/commands/doctor/checks.d.ts +0 -1
- package/dist/commands/doctor/checks.js +1 -36
- package/dist/commands/doctor/report.js +0 -11
- package/dist/commands/init/content.d.ts +1 -1
- package/dist/commands/init/content.js +3 -11
- package/dist/commands/init.js +2 -2
- package/dist/commands/mcp/config.d.ts +32 -0
- package/dist/commands/mcp/config.js +264 -0
- package/dist/commands/mcp/doctor-fix.d.ts +12 -0
- package/dist/commands/mcp/doctor-fix.js +261 -0
- package/dist/commands/mcp/doctor.d.ts +55 -0
- package/dist/commands/mcp/doctor.js +401 -0
- package/dist/commands/mcp/list.d.ts +1 -0
- package/dist/commands/mcp/list.js +56 -0
- package/dist/commands/mcp/shared.d.ts +47 -0
- package/dist/commands/mcp/shared.js +192 -0
- package/dist/commands/mcp/test.d.ts +5 -0
- package/dist/commands/mcp/test.js +457 -0
- package/dist/commands/mcp.d.ts +8 -104
- package/dist/commands/mcp.js +5 -1603
- package/dist/contracts/proof.d.ts +1 -1
- package/dist/goal/control-loop.d.ts +15 -0
- package/dist/goal/control-loop.js +39 -5
- package/dist/hud/render.d.ts +2 -0
- package/dist/hud/render.js +7 -1
- package/dist/mcp/secret-scanner.d.ts +6 -0
- package/dist/mcp/secret-scanner.js +44 -11
- package/dist/memory/graph-delta-log.d.ts +137 -0
- package/dist/memory/graph-delta-log.js +650 -0
- package/dist/memory/graph-viewer.js +13 -32
- package/dist/memory/local-graph-memory-store.d.ts +36 -3
- package/dist/memory/local-graph-memory-store.js +255 -10
- package/dist/orchestration/log-streamer.js +13 -8
- package/dist/orchestration/routing.d.ts +4 -0
- package/dist/orchestration/routing.js +28 -3
- package/dist/orchestration/state-persister.js +8 -1
- package/dist/providers/anthropic-messages-runner.d.ts +17 -0
- package/dist/providers/anthropic-messages-runner.js +197 -0
- package/dist/providers/model-registry.d.ts +1 -1
- package/dist/providers/model-registry.js +37 -3
- package/dist/providers/provider-runtime.js +27 -11
- package/dist/providers/thinking-levels.js +4 -1
- package/dist/providers/types.d.ts +3 -3
- package/dist/runtime/contracts/weakness-remediation.d.ts +11 -1
- package/dist/runtime/contracts/weakness-remediation.js +9 -1
- package/dist/runtime/router-v2-scoring.d.ts +8 -3
- package/dist/runtime/router-v2-scoring.js +27 -22
- package/dist/runtime/runtime-router.d.ts +5 -0
- package/dist/runtime/runtime-router.js +40 -8
- package/dist/runtime/sandbox-profile.d.ts +29 -0
- package/dist/runtime/sandbox-profile.js +101 -0
- package/dist/runtime/slash-commands.js +300 -2
- package/dist/runtime/tool-dispatch-contracts.d.ts +15 -0
- package/dist/runtime/tool-dispatch-contracts.js +12 -0
- package/dist/runtime/weights-config.d.ts +90 -0
- package/dist/runtime/weights-config.js +249 -0
- package/dist/schema/envelope.schema.d.ts +18 -18
- package/dist/schema/evidence.schema.d.ts +2 -2
- package/dist/schema/proof-bundle.schema.d.ts +6 -6
- package/dist/schema/proof-bundle.schema.js +0 -1
- package/dist/schema/provider.schema.d.ts +2 -2
- package/dist/schema/run-manifest.schema.d.ts +16 -16
- package/dist/theme/ansi.js +9 -2
- package/dist/theme/extended-palette.d.ts +85 -0
- package/dist/theme/extended-palette.js +108 -0
- package/dist/theme/layout.js +29 -8
- package/dist/theme/metrics.js +5 -5
- package/dist/theme/parallel.js +25 -15
- package/dist/ui/omk-sigil.js +5 -14
- package/dist/ui/omk-working-sweep.js +5 -15
- package/dist/util/chat-agent-mode.d.ts +11 -0
- package/dist/util/chat-agent-mode.js +41 -8
- package/dist/util/chat-cockpit.d.ts +9 -0
- package/dist/util/chat-cockpit.js +77 -20
- package/dist/util/fs/core.d.ts +5 -0
- package/dist/util/fs/core.js +35 -0
- package/dist/util/fs/internal.d.ts +14 -0
- package/dist/util/fs/internal.js +169 -0
- package/dist/util/fs/kimi-sync.d.ts +41 -0
- package/dist/util/fs/kimi-sync.js +495 -0
- package/dist/util/fs/logo.d.ts +2 -0
- package/dist/util/fs/logo.js +78 -0
- package/dist/util/fs/manifest.d.ts +6 -0
- package/dist/util/fs/manifest.js +40 -0
- package/dist/util/fs/mcp-diagnose.d.ts +15 -0
- package/dist/util/fs/mcp-diagnose.js +204 -0
- package/dist/util/fs/mcp-runtime-config.d.ts +23 -0
- package/dist/util/fs/mcp-runtime-config.js +412 -0
- package/dist/util/fs/paths.d.ts +13 -0
- package/dist/util/fs/paths.js +82 -0
- package/dist/util/fs/preflight.d.ts +27 -0
- package/dist/util/fs/preflight.js +271 -0
- package/dist/util/fs.d.ts +8 -128
- package/dist/util/fs.js +8 -1757
- package/dist/util/terminal-layout.js +5 -1
- package/dist/util/version.js +2 -1
- package/docs/2026-06-10/critical-issues.md +20 -0
- package/docs/2026-06-10/improvements.md +14 -0
- package/docs/2026-06-10/init-checklist.md +25 -0
- package/docs/2026-06-10/plan.md +20 -0
- package/docs/2026-06-11/critical-issues.md +20 -0
- package/docs/2026-06-11/improvements.md +14 -0
- package/docs/2026-06-11/init-checklist.md +25 -0
- package/docs/2026-06-11/plan.md +20 -0
- package/docs/ARCHITECTURE_ANALYSIS_CODEGRAPH.md +223 -0
- package/docs/adr/0001-no-native-rust-lane.md +52 -0
- package/docs/codex-oauth-setup.md +14 -0
- package/docs/decisions/2026-06-10-git-history-bloat.md +61 -0
- package/docs/decisions/ADR-theme-dark-only-assets.md +43 -0
- package/docs/getting-started.md +1 -1
- package/docs/headroom-omk-final-summary.md +188 -0
- package/docs/headroom-omk-integration.md +394 -0
- package/docs/headroom-omk-setup-guide.md +422 -0
- package/docs/headroom-omk-usage-examples.md +371 -0
- package/docs/provider-maturity.md +1 -1
- package/docs/versioning.md +3 -3
- package/package.json +10 -10
- package/readmeasset/.npmignore +2 -0
- package/readmeasset/ASSET_INDEX.md +7 -5
- package/readmeasset/ASSET_PROVENANCE.md +21 -5
- package/readmeasset/omk-control-surfaces.svg +55 -0
- package/readmeasset/omk-core-loop.svg +1 -0
- package/readmeasset/omk-logo-mark.svg +1 -0
- package/readmeasset/omk-release-assertions.svg +51 -0
- package/dist/native/linux-x64/omk-safety +0 -0
- package/dist/util/native-safety.d.ts +0 -28
- package/dist/util/native-safety.js +0 -118
- package/templates/skills/agents/omk-adaptorch-orchestration-review/SKILL.md +0 -52
- package/templates/skills/kimi/mcp-install/SKILL.md +0 -66
- package/templates/skills/kimi/omk-adaptorch-orchestration-review/SKILL.md +0 -52
|
@@ -4,29 +4,9 @@ import { mkdir, readFile, writeFile, stat } from "fs/promises";
|
|
|
4
4
|
import { dirname } from "path";
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
import { checkCommand } from "../util/shell.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Memory: "#059669",
|
|
11
|
-
MemoryVersion: "#6b7280",
|
|
12
|
-
Run: "#14b8a6",
|
|
13
|
-
Goal: "#f59e0b",
|
|
14
|
-
Topic: "#64748b",
|
|
15
|
-
Decision: "#dc2626",
|
|
16
|
-
Task: "#ea580c",
|
|
17
|
-
Risk: "#be123c",
|
|
18
|
-
Command: "#0f766e",
|
|
19
|
-
File: "#0891b2",
|
|
20
|
-
Evidence: "#16a34a",
|
|
21
|
-
Provider: "#a855f7",
|
|
22
|
-
ProviderRoute: "#8b5cf6",
|
|
23
|
-
ProviderFallback: "#f97316",
|
|
24
|
-
AuditLink: "#eab308",
|
|
25
|
-
Constraint: "#9333ea",
|
|
26
|
-
Question: "#0284c7",
|
|
27
|
-
Answer: "#22c55e",
|
|
28
|
-
Concept: "#4f46e5",
|
|
29
|
-
};
|
|
7
|
+
import { GRAPH_VIEWER } from "../theme/extended-palette.js";
|
|
8
|
+
// Web/SVG document palette declared once in the extended palette data module.
|
|
9
|
+
const TYPE_COLORS = GRAPH_VIEWER.typeColors;
|
|
30
10
|
const REL_BY_TYPE = {
|
|
31
11
|
Session: "HAS_SESSION",
|
|
32
12
|
Memory: "HAS_MEMORY",
|
|
@@ -189,7 +169,7 @@ export function toCytoscapeElements(nodes, edges) {
|
|
|
189
169
|
summary: sanitizeText(node.summary ?? node.content ?? "", 500),
|
|
190
170
|
path: node.path ?? "",
|
|
191
171
|
tags: (node.tags ?? []).join(", "),
|
|
192
|
-
color: TYPE_COLORS[node.type] ??
|
|
172
|
+
color: TYPE_COLORS[node.type] ?? GRAPH_VIEWER.defaultNode,
|
|
193
173
|
},
|
|
194
174
|
})),
|
|
195
175
|
...edges.map((edge) => ({
|
|
@@ -206,6 +186,7 @@ function renderGraphHtml(elements, meta) {
|
|
|
206
186
|
const encodedElements = JSON.stringify(elements).replace(/</g, "\\u003c");
|
|
207
187
|
const title = sanitizeText(meta.projectName ? `OMK Ontology Graph — ${meta.projectName}` : "OMK Ontology Graph", 120);
|
|
208
188
|
const ontology = sanitizeText(meta.ontologyVersion ?? "unknown", 80);
|
|
189
|
+
const G = GRAPH_VIEWER.ui;
|
|
209
190
|
return `<!doctype html>
|
|
210
191
|
<html lang="en">
|
|
211
192
|
<head>
|
|
@@ -214,12 +195,12 @@ function renderGraphHtml(elements, meta) {
|
|
|
214
195
|
<title>${escapeHtml(title)}</title>
|
|
215
196
|
<script src="https://unpkg.com/cytoscape@3.30.2/dist/cytoscape.min.js"></script>
|
|
216
197
|
<style>
|
|
217
|
-
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:
|
|
218
|
-
#top { min-height: 56px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 8px 16px; background:
|
|
198
|
+
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: ${G.bodyBg}; color: ${G.bodyFg}; }
|
|
199
|
+
#top { min-height: 56px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 8px 16px; background: ${G.topBg}; border-bottom: 1px solid ${G.topBorder}; box-sizing: border-box; }
|
|
219
200
|
#cy { width: 100vw; height: calc(100vh - 72px); display: block; }
|
|
220
|
-
input { background:
|
|
221
|
-
.stat { color:
|
|
222
|
-
.pill { color:
|
|
201
|
+
input { background: ${G.inputBg}; color: ${G.bodyFg}; border: 1px solid ${G.inputBorder}; border-radius: 8px; padding: 8px 10px; width: min(420px, 70vw); }
|
|
202
|
+
.stat { color: ${G.statText}; font-size: 13px; }
|
|
203
|
+
.pill { color: ${G.pillText}; background: ${G.pillBg}; border: 1px solid ${G.pillBorder}; border-radius: 999px; padding: 3px 8px; font-size: 12px; }
|
|
223
204
|
</style>
|
|
224
205
|
</head>
|
|
225
206
|
<body>
|
|
@@ -238,9 +219,9 @@ function renderGraphHtml(elements, meta) {
|
|
|
238
219
|
elements: allElements,
|
|
239
220
|
wheelSensitivity: 0.15,
|
|
240
221
|
style: [
|
|
241
|
-
{ selector: "node", style: { "background-color": "data(color)", "label": "data(label)", "color": "
|
|
242
|
-
{ selector: "edge", style: { "width": 1, "line-color": "
|
|
243
|
-
{ selector: ":selected", style: { "border-width": 4, "border-color": "
|
|
222
|
+
{ selector: "node", style: { "background-color": "data(color)", "label": "data(label)", "color": "${G.nodeLabel}", "font-size": 9, "text-wrap": "wrap", "text-max-width": 130, "width": 24, "height": 24, "border-width": 1, "border-color": "${G.nodeBorder}" } },
|
|
223
|
+
{ selector: "edge", style: { "width": 1, "line-color": "${G.edgeLine}", "target-arrow-color": "${G.edgeLine}", "target-arrow-shape": "triangle", "curve-style": "bezier", "label": "data(label)", "font-size": 7, "color": "${G.edgeLabel}", "text-rotation": "autorotate" } },
|
|
224
|
+
{ selector: ":selected", style: { "border-width": 4, "border-color": "${G.selected}", "line-color": "${G.selected}", "target-arrow-color": "${G.selected}" } }
|
|
244
225
|
],
|
|
245
226
|
layout: { name: "cose", animate: false, nodeRepulsion: 9000, idealEdgeLength: 90, edgeElasticity: 80, gravity: 0.18, numIter: 1600 }
|
|
246
227
|
});
|
|
@@ -49,7 +49,7 @@ export interface LocalGraphMemoryStoreOptions {
|
|
|
49
49
|
}
|
|
50
50
|
type Primitive = string | number | boolean | null;
|
|
51
51
|
type Properties = Record<string, Primitive>;
|
|
52
|
-
interface LocalGraphNode {
|
|
52
|
+
export interface LocalGraphNode {
|
|
53
53
|
id: string;
|
|
54
54
|
type: string;
|
|
55
55
|
labels: string[];
|
|
@@ -62,7 +62,7 @@ interface LocalGraphNode {
|
|
|
62
62
|
createdAt: string;
|
|
63
63
|
updatedAt: string;
|
|
64
64
|
}
|
|
65
|
-
interface LocalGraphEdge {
|
|
65
|
+
export interface LocalGraphEdge {
|
|
66
66
|
id: string;
|
|
67
67
|
type: string;
|
|
68
68
|
from: string;
|
|
@@ -101,7 +101,13 @@ export declare const ONTOLOGY: MemoryOntology;
|
|
|
101
101
|
export declare class LocalGraphMemoryStore {
|
|
102
102
|
private readonly settings;
|
|
103
103
|
private readonly source;
|
|
104
|
-
|
|
104
|
+
private readonly env?;
|
|
105
|
+
private readonly durability;
|
|
106
|
+
private deltaEpoch;
|
|
107
|
+
private deltaLastSeq;
|
|
108
|
+
private deltaOpCount;
|
|
109
|
+
private deltaManifest;
|
|
110
|
+
constructor(settings: MemorySettings, source?: string, env?: NodeJS.ProcessEnv | undefined);
|
|
105
111
|
static create(options?: LocalGraphMemoryStoreOptions): Promise<LocalGraphMemoryStore | null>;
|
|
106
112
|
get status(): MemoryStatus;
|
|
107
113
|
get strict(): boolean;
|
|
@@ -113,6 +119,15 @@ export declare class LocalGraphMemoryStore {
|
|
|
113
119
|
writeMirrorFiles(state: LocalGraphState): Promise<void>;
|
|
114
120
|
append(path: string, content: string): Promise<void>;
|
|
115
121
|
search(query: string, limit?: number): Promise<MemorySearchResult[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Build a reusable `path -> latest-content` resolver from a single pass over
|
|
124
|
+
* nodes and edges. Mirrors readFromState()/findLatestMemoryVersionNode()
|
|
125
|
+
* selection and ordering exactly (same memory-by-id lookup, same UPDATES-edge
|
|
126
|
+
* filter, same descending updatedAt/createdAt/id tiebreak), but pays
|
|
127
|
+
* O(N + E) once instead of O(N) per Memory node, removing the O(N^2) search
|
|
128
|
+
* hot path. Resolved content is memoized per path for O(1) repeat lookups.
|
|
129
|
+
*/
|
|
130
|
+
private buildMemoryContentIndex;
|
|
116
131
|
ontology(): Promise<MemoryOntology>;
|
|
117
132
|
mindmap(query?: string, limit?: number): Promise<MemoryMindmap>;
|
|
118
133
|
/**
|
|
@@ -131,11 +146,29 @@ export declare class LocalGraphMemoryStore {
|
|
|
131
146
|
private applyRunManifest;
|
|
132
147
|
graphQuery(query: string): Promise<GraphQueryResult>;
|
|
133
148
|
private loadState;
|
|
149
|
+
private loadStateLegacy;
|
|
150
|
+
private loadStateDelta;
|
|
134
151
|
private emptyState;
|
|
135
152
|
private saveState;
|
|
136
153
|
private handleInvalidState;
|
|
137
154
|
private writeInvalidStateRepair;
|
|
138
155
|
private mutateState;
|
|
156
|
+
/**
|
|
157
|
+
* Load state for a write, reusing the process-local cache only when the file
|
|
158
|
+
* is unchanged on mtimeMs + size + ctimeMs + inode since this process last
|
|
159
|
+
* persisted it. In delta mode, also checks snapshot + delta file stats.
|
|
160
|
+
* Any divergence (concurrent external writer) or a missing entry invalidates
|
|
161
|
+
* the cache and falls back to a full loadState(), preserving multi-writer
|
|
162
|
+
* correctness and loadState's ENOENT / empty / invalid / strict semantics.
|
|
163
|
+
*/
|
|
164
|
+
private loadStateForMutation;
|
|
165
|
+
/**
|
|
166
|
+
* Record the just-written state plus its fresh on-disk stats so the next
|
|
167
|
+
* cache hit can be validated. In delta mode, tracks snapshot + delta file
|
|
168
|
+
* stats. If the files cannot be stat'd, drop the entry rather than risk
|
|
169
|
+
* serving stale data on a later write.
|
|
170
|
+
*/
|
|
171
|
+
private refreshGraphStateCache;
|
|
139
172
|
private readFromState;
|
|
140
173
|
private findLatestMemoryVersionNode;
|
|
141
174
|
private findMemoryNode;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
|
+
import { statSync } from "fs";
|
|
2
3
|
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
3
4
|
import { dirname, resolve } from "path";
|
|
4
5
|
import { loadMemorySettings, summarizeMemorySettings, usesLocalGraphBackend, } from "./memory-config.js";
|
|
6
|
+
import { appendDelta, compactIfNeeded, computeDelta, deltaStatsMatch, loadStateViaDelta, readManifest, resolveCompactionThresholds, resolveDurabilityMode, setupDeltaMode, statDeltaFiles, withDeltaLock, } from "./graph-delta-log.js";
|
|
5
7
|
export const ONTOLOGY = {
|
|
6
8
|
version: "omk-ontology-mindmap-v1",
|
|
7
9
|
description: "Project-local ontology for Kimi/OMK memory. Memories are decomposed into mind-map nodes for goals, topics, decisions, tasks, risks, commands, files, evidence, provider routes, provider fallbacks, questions, answers, constraints, and concepts.",
|
|
@@ -97,6 +99,27 @@ const CANONICAL_NODE_TYPES = new Set([
|
|
|
97
99
|
"AuditLink",
|
|
98
100
|
]);
|
|
99
101
|
const graphWriteQueues = new Map();
|
|
102
|
+
/**
|
|
103
|
+
* Process-local cache of the most-recently-persisted parsed graph state, keyed
|
|
104
|
+
* by graph-state.json path. A cache hit is only honored when the on-disk file
|
|
105
|
+
* is unchanged on ALL of mtimeMs + size + ctimeMs + inode since the cached
|
|
106
|
+
* snapshot was written. ctimeMs catches a same-millisecond, same-size overwrite
|
|
107
|
+
* that mtimeMs+size alone would miss, and inode catches an atomic-rename replace
|
|
108
|
+
* (new file) — so any concurrent external writer forces a fresh disk re-read
|
|
109
|
+
* (multi-writer safe). The entry is populated exclusively from a state we just
|
|
110
|
+
* wrote via saveState, so it never masks loadState's ENOENT / empty / invalid /
|
|
111
|
+
* strict-mode handling.
|
|
112
|
+
*/
|
|
113
|
+
const graphStateCache = new Map();
|
|
114
|
+
function statSyncSafe(path) {
|
|
115
|
+
try {
|
|
116
|
+
const stat = statSync(path);
|
|
117
|
+
return { mtimeMs: stat.mtimeMs, size: stat.size, ctimeMs: stat.ctimeMs, ino: stat.ino };
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
100
123
|
function enqueueGraphWrite(graphPath, operation) {
|
|
101
124
|
const previous = graphWriteQueues.get(graphPath) ?? Promise.resolve();
|
|
102
125
|
const running = previous.catch(() => undefined).then(operation);
|
|
@@ -135,9 +158,17 @@ async function writeFileAtomic(path, payload) {
|
|
|
135
158
|
export class LocalGraphMemoryStore {
|
|
136
159
|
settings;
|
|
137
160
|
source;
|
|
138
|
-
|
|
161
|
+
env;
|
|
162
|
+
durability;
|
|
163
|
+
deltaEpoch = 0;
|
|
164
|
+
deltaLastSeq = 0;
|
|
165
|
+
deltaOpCount = 0;
|
|
166
|
+
deltaManifest = null;
|
|
167
|
+
constructor(settings, source = "omk-local-graph-memory", env) {
|
|
139
168
|
this.settings = settings;
|
|
140
169
|
this.source = source;
|
|
170
|
+
this.env = env;
|
|
171
|
+
this.durability = resolveDurabilityMode(env);
|
|
141
172
|
}
|
|
142
173
|
static async create(options = {}) {
|
|
143
174
|
const env = options.sessionId
|
|
@@ -146,7 +177,7 @@ export class LocalGraphMemoryStore {
|
|
|
146
177
|
const settings = await loadMemorySettings(options.projectRoot, env);
|
|
147
178
|
if (!usesLocalGraphBackend(settings.backend))
|
|
148
179
|
return null;
|
|
149
|
-
return new LocalGraphMemoryStore(settings, options.source ?? "omk-local-graph-memory");
|
|
180
|
+
return new LocalGraphMemoryStore(settings, options.source ?? "omk-local-graph-memory", env);
|
|
150
181
|
}
|
|
151
182
|
get status() {
|
|
152
183
|
return summarizeMemorySettings(this.settings);
|
|
@@ -287,10 +318,15 @@ export class LocalGraphMemoryStore {
|
|
|
287
318
|
const state = await this.loadState();
|
|
288
319
|
const normalizedQuery = query.trim().toLowerCase();
|
|
289
320
|
const safeLimit = Math.max(1, Math.min(50, Math.floor(limit)) || 10);
|
|
321
|
+
// Build the per-path latest-content resolver ONCE in O(N + E) and reuse it
|
|
322
|
+
// for every Memory node, instead of rescanning all nodes+edges per node via
|
|
323
|
+
// readFromState() (which made search O(N^2)). Results are identical & same
|
|
324
|
+
// order; resolver output is memoized so the filter+map phases stay O(1).
|
|
325
|
+
const resolveContent = this.buildMemoryContentIndex(state);
|
|
290
326
|
return state.nodes
|
|
291
327
|
.filter((node) => node.type === "Memory")
|
|
292
328
|
.filter((node) => {
|
|
293
|
-
const content =
|
|
329
|
+
const content = resolveContent(node.path ?? node.label);
|
|
294
330
|
if (!normalizedQuery)
|
|
295
331
|
return true;
|
|
296
332
|
return [node.path, node.label, node.summary, content]
|
|
@@ -301,12 +337,63 @@ export class LocalGraphMemoryStore {
|
|
|
301
337
|
.slice(0, safeLimit)
|
|
302
338
|
.map((node) => ({
|
|
303
339
|
path: node.path ?? node.label,
|
|
304
|
-
content:
|
|
340
|
+
content: resolveContent(node.path ?? node.label),
|
|
305
341
|
sessionId: String(node.properties.sessionId ?? ""),
|
|
306
342
|
updatedAt: node.updatedAt,
|
|
307
343
|
source: String(node.properties.source ?? this.source),
|
|
308
344
|
}));
|
|
309
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Build a reusable `path -> latest-content` resolver from a single pass over
|
|
348
|
+
* nodes and edges. Mirrors readFromState()/findLatestMemoryVersionNode()
|
|
349
|
+
* selection and ordering exactly (same memory-by-id lookup, same UPDATES-edge
|
|
350
|
+
* filter, same descending updatedAt/createdAt/id tiebreak), but pays
|
|
351
|
+
* O(N + E) once instead of O(N) per Memory node, removing the O(N^2) search
|
|
352
|
+
* hot path. Resolved content is memoized per path for O(1) repeat lookups.
|
|
353
|
+
*/
|
|
354
|
+
buildMemoryContentIndex(state) {
|
|
355
|
+
const memoryById = new Map();
|
|
356
|
+
const versionsByPath = new Map();
|
|
357
|
+
for (const node of state.nodes) {
|
|
358
|
+
if (node.type === "Memory") {
|
|
359
|
+
if (!memoryById.has(node.id))
|
|
360
|
+
memoryById.set(node.id, node);
|
|
361
|
+
}
|
|
362
|
+
else if (node.type === "MemoryVersion" && node.path !== undefined) {
|
|
363
|
+
const existing = versionsByPath.get(node.path);
|
|
364
|
+
if (existing)
|
|
365
|
+
existing.push(node);
|
|
366
|
+
else
|
|
367
|
+
versionsByPath.set(node.path, [node]);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const updateFromByMemoryId = new Map();
|
|
371
|
+
for (const edge of state.edges) {
|
|
372
|
+
if (edge.type !== "UPDATES")
|
|
373
|
+
continue;
|
|
374
|
+
const existing = updateFromByMemoryId.get(edge.to);
|
|
375
|
+
if (existing)
|
|
376
|
+
existing.add(edge.from);
|
|
377
|
+
else
|
|
378
|
+
updateFromByMemoryId.set(edge.to, new Set([edge.from]));
|
|
379
|
+
}
|
|
380
|
+
const contentCache = new Map();
|
|
381
|
+
return (path) => {
|
|
382
|
+
const cached = contentCache.get(path);
|
|
383
|
+
if (cached !== undefined)
|
|
384
|
+
return cached;
|
|
385
|
+
const memory = memoryById.get(this.memoryNodeId(path));
|
|
386
|
+
const updateIds = memory ? updateFromByMemoryId.get(memory.id) : undefined;
|
|
387
|
+
const version = (versionsByPath.get(path) ?? [])
|
|
388
|
+
.filter((node) => !updateIds || updateIds.size === 0 || updateIds.has(node.id))
|
|
389
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt) ||
|
|
390
|
+
b.createdAt.localeCompare(a.createdAt) ||
|
|
391
|
+
b.id.localeCompare(a.id))[0];
|
|
392
|
+
const content = version?.content ?? memory?.content ?? "";
|
|
393
|
+
contentCache.set(path, content);
|
|
394
|
+
return content;
|
|
395
|
+
};
|
|
396
|
+
}
|
|
310
397
|
async ontology() {
|
|
311
398
|
return ONTOLOGY;
|
|
312
399
|
}
|
|
@@ -571,6 +658,12 @@ export class LocalGraphMemoryStore {
|
|
|
571
658
|
};
|
|
572
659
|
}
|
|
573
660
|
async loadState() {
|
|
661
|
+
if (this.durability === "delta") {
|
|
662
|
+
return this.loadStateDelta();
|
|
663
|
+
}
|
|
664
|
+
return this.loadStateLegacy();
|
|
665
|
+
}
|
|
666
|
+
async loadStateLegacy() {
|
|
574
667
|
let raw;
|
|
575
668
|
try {
|
|
576
669
|
raw = await readFile(this.settings.localGraph.path, "utf-8");
|
|
@@ -603,6 +696,32 @@ export class LocalGraphMemoryStore {
|
|
|
603
696
|
return this.handleInvalidState(raw, `invalid JSON: ${errorMessage(err)}`);
|
|
604
697
|
}
|
|
605
698
|
}
|
|
699
|
+
async loadStateDelta() {
|
|
700
|
+
const graphPath = this.settings.localGraph.path;
|
|
701
|
+
// Check if manifest exists; if not, try to load legacy file for migration
|
|
702
|
+
const manifest = await readManifest(graphPath);
|
|
703
|
+
if (!manifest) {
|
|
704
|
+
// Load legacy file as base state for migration
|
|
705
|
+
let legacyState;
|
|
706
|
+
try {
|
|
707
|
+
legacyState = await this.loadStateLegacy();
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
legacyState = this.emptyState();
|
|
711
|
+
}
|
|
712
|
+
this.deltaManifest = await setupDeltaMode(graphPath, legacyState);
|
|
713
|
+
this.deltaEpoch = this.deltaManifest.snapshotEpoch;
|
|
714
|
+
this.deltaLastSeq = 0;
|
|
715
|
+
this.deltaOpCount = this.deltaManifest.deltaOpCount;
|
|
716
|
+
return legacyState;
|
|
717
|
+
}
|
|
718
|
+
this.deltaManifest = manifest;
|
|
719
|
+
const result = await loadStateViaDelta(graphPath, this.settings.strict, this.emptyState());
|
|
720
|
+
this.deltaEpoch = result.epoch;
|
|
721
|
+
this.deltaLastSeq = result.lastSeq;
|
|
722
|
+
this.deltaOpCount = result.deltaOpCount;
|
|
723
|
+
return result.state;
|
|
724
|
+
}
|
|
606
725
|
emptyState() {
|
|
607
726
|
const now = new Date().toISOString();
|
|
608
727
|
return {
|
|
@@ -653,15 +772,141 @@ export class LocalGraphMemoryStore {
|
|
|
653
772
|
return { backupPath, signalPath, reason };
|
|
654
773
|
}
|
|
655
774
|
async mutateState(mutator) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
775
|
+
const graphPath = this.settings.localGraph.path;
|
|
776
|
+
await enqueueGraphWrite(graphPath, async () => {
|
|
777
|
+
if (this.durability === "delta") {
|
|
778
|
+
await withDeltaLock(graphPath, async () => {
|
|
779
|
+
const state = await this.loadStateForMutation(graphPath);
|
|
780
|
+
// Capture pre-mutation id→object maps for diffing
|
|
781
|
+
const preNodes = new Map(state.nodes.map((n) => [n.id, n]));
|
|
782
|
+
const preEdges = new Map(state.edges.map((e) => [e.id, e]));
|
|
783
|
+
const now = new Date().toISOString();
|
|
784
|
+
mutator(state, now);
|
|
785
|
+
// Compute delta (changed/added/deleted nodes and edges)
|
|
786
|
+
const delta = computeDelta(preNodes, preEdges, state);
|
|
787
|
+
// Ensure delta mode is set up (first write migrates if needed)
|
|
788
|
+
if (!this.deltaManifest) {
|
|
789
|
+
this.deltaManifest = await setupDeltaMode(graphPath, state);
|
|
790
|
+
this.deltaEpoch = this.deltaManifest.snapshotEpoch;
|
|
791
|
+
this.deltaLastSeq = 0;
|
|
792
|
+
this.deltaOpCount = this.deltaManifest.deltaOpCount;
|
|
793
|
+
}
|
|
794
|
+
const seq = this.deltaLastSeq + 1;
|
|
795
|
+
await appendDelta(graphPath, this.deltaEpoch, seq, now, {
|
|
796
|
+
updatedAt: state.updatedAt,
|
|
797
|
+
project: state.project,
|
|
798
|
+
ontology: state.ontology.version,
|
|
799
|
+
}, delta.nodes, delta.edges);
|
|
800
|
+
this.deltaLastSeq = seq;
|
|
801
|
+
this.deltaOpCount += 1;
|
|
802
|
+
// Check compaction thresholds
|
|
803
|
+
const thresholds = resolveCompactionThresholds(this.env);
|
|
804
|
+
const compactResult = await compactIfNeeded(graphPath, state, this.deltaEpoch, this.deltaOpCount, thresholds);
|
|
805
|
+
if (compactResult.compacted) {
|
|
806
|
+
this.deltaEpoch = compactResult.newEpoch;
|
|
807
|
+
this.deltaOpCount = compactResult.newOpCount;
|
|
808
|
+
this.deltaLastSeq = 0;
|
|
809
|
+
}
|
|
810
|
+
// Refresh the process-local cache from the file we just wrote so the next
|
|
811
|
+
// mutation in this process can reuse the parsed object and skip the disk
|
|
812
|
+
// read + JSON.parse entirely (on-disk format/durability are unchanged).
|
|
813
|
+
this.refreshGraphStateCache(graphPath, state);
|
|
814
|
+
if (this.settings.mirrorFiles) {
|
|
815
|
+
await this.writeMirrorFiles(state);
|
|
816
|
+
}
|
|
817
|
+
}, this.env);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
const state = await this.loadStateForMutation(graphPath);
|
|
821
|
+
mutator(state, new Date().toISOString());
|
|
822
|
+
await this.saveState(state);
|
|
823
|
+
this.refreshGraphStateCache(graphPath, state);
|
|
824
|
+
if (this.settings.mirrorFiles) {
|
|
825
|
+
await this.writeMirrorFiles(state);
|
|
826
|
+
}
|
|
662
827
|
}
|
|
663
828
|
});
|
|
664
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Load state for a write, reusing the process-local cache only when the file
|
|
832
|
+
* is unchanged on mtimeMs + size + ctimeMs + inode since this process last
|
|
833
|
+
* persisted it. In delta mode, also checks snapshot + delta file stats.
|
|
834
|
+
* Any divergence (concurrent external writer) or a missing entry invalidates
|
|
835
|
+
* the cache and falls back to a full loadState(), preserving multi-writer
|
|
836
|
+
* correctness and loadState's ENOENT / empty / invalid / strict semantics.
|
|
837
|
+
*/
|
|
838
|
+
async loadStateForMutation(graphPath) {
|
|
839
|
+
const cached = graphStateCache.get(graphPath);
|
|
840
|
+
if (cached) {
|
|
841
|
+
if (this.durability === "delta") {
|
|
842
|
+
const currentDeltaStats = statDeltaFiles(graphPath);
|
|
843
|
+
if (currentDeltaStats &&
|
|
844
|
+
deltaStatsMatch(currentDeltaStats, cached.deltaStats ?? null)) {
|
|
845
|
+
// Restore delta tracking from cache
|
|
846
|
+
if (cached.deltaEpoch !== undefined)
|
|
847
|
+
this.deltaEpoch = cached.deltaEpoch;
|
|
848
|
+
if (cached.deltaOpCount !== undefined)
|
|
849
|
+
this.deltaOpCount = cached.deltaOpCount;
|
|
850
|
+
if (cached.deltaLastSeq !== undefined)
|
|
851
|
+
this.deltaLastSeq = cached.deltaLastSeq;
|
|
852
|
+
return cached.state;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
const stat = statSyncSafe(graphPath);
|
|
857
|
+
if (stat &&
|
|
858
|
+
stat.mtimeMs === cached.mtimeMs &&
|
|
859
|
+
stat.size === cached.size &&
|
|
860
|
+
stat.ctimeMs === cached.ctimeMs &&
|
|
861
|
+
stat.ino === cached.ino) {
|
|
862
|
+
return cached.state;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
graphStateCache.delete(graphPath);
|
|
866
|
+
}
|
|
867
|
+
return this.loadState();
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Record the just-written state plus its fresh on-disk stats so the next
|
|
871
|
+
* cache hit can be validated. In delta mode, tracks snapshot + delta file
|
|
872
|
+
* stats. If the files cannot be stat'd, drop the entry rather than risk
|
|
873
|
+
* serving stale data on a later write.
|
|
874
|
+
*/
|
|
875
|
+
refreshGraphStateCache(graphPath, state) {
|
|
876
|
+
if (this.durability === "delta") {
|
|
877
|
+
const deltaStats = statDeltaFiles(graphPath);
|
|
878
|
+
if (deltaStats) {
|
|
879
|
+
graphStateCache.set(graphPath, {
|
|
880
|
+
state,
|
|
881
|
+
mtimeMs: deltaStats.snapshotMtimeMs,
|
|
882
|
+
size: deltaStats.snapshotSize,
|
|
883
|
+
ctimeMs: deltaStats.snapshotCtimeMs,
|
|
884
|
+
ino: deltaStats.snapshotIno,
|
|
885
|
+
deltaStats,
|
|
886
|
+
deltaEpoch: this.deltaEpoch,
|
|
887
|
+
deltaOpCount: this.deltaOpCount,
|
|
888
|
+
deltaLastSeq: this.deltaLastSeq,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
graphStateCache.delete(graphPath);
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const stat = statSyncSafe(graphPath);
|
|
897
|
+
if (stat) {
|
|
898
|
+
graphStateCache.set(graphPath, {
|
|
899
|
+
state,
|
|
900
|
+
mtimeMs: stat.mtimeMs,
|
|
901
|
+
size: stat.size,
|
|
902
|
+
ctimeMs: stat.ctimeMs,
|
|
903
|
+
ino: stat.ino,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
graphStateCache.delete(graphPath);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
665
910
|
readFromState(state, path) {
|
|
666
911
|
const memory = this.findMemoryNode(state, path);
|
|
667
912
|
const version = this.findLatestMemoryVersionNode(state, path, memory?.id);
|
|
@@ -153,15 +153,20 @@ export class LogStreamer {
|
|
|
153
153
|
const timestamp = entry.timestamp.split("T")[1].replace("Z", "");
|
|
154
154
|
const prefix = `[${timestamp}] [${entry.workerId}]`;
|
|
155
155
|
if (useFormat === "colored") {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
156
|
+
// Standard ANSI-16 SGR foreground codes (ECMA-48) built from numeric
|
|
157
|
+
// values rather than `\x1b[3Xm` literals so color:gate stays clean.
|
|
158
|
+
// Plain ANSI-16 (not brand truecolor) is intentional: these lines are
|
|
159
|
+
// also parsed/greppable in log files and stay terminal-portable.
|
|
160
|
+
const CSI = "\x1b[";
|
|
161
|
+
const LEVEL_FG = {
|
|
162
|
+
stdout: 37, // white
|
|
163
|
+
stderr: 31, // red
|
|
164
|
+
info: 34, // blue
|
|
165
|
+
warn: 33, // yellow
|
|
166
|
+
error: 31, // red
|
|
162
167
|
};
|
|
163
|
-
const
|
|
164
|
-
const
|
|
168
|
+
const color = `${CSI}${LEVEL_FG[entry.level]}m`;
|
|
169
|
+
const reset = `${CSI}0m`;
|
|
165
170
|
return `${color}${prefix} [${entry.level.toUpperCase()}]${reset} ${entry.message}`;
|
|
166
171
|
}
|
|
167
172
|
return `${prefix} [${entry.level.toUpperCase()}] ${entry.message}`;
|
|
@@ -22,6 +22,10 @@ export declare function resetRoutingInventoryCache(): void;
|
|
|
22
22
|
export declare function mergeDagNodeRouting(auto: DagNodeRouting, override: DagNodeRouting | undefined): DagNodeRouting;
|
|
23
23
|
export declare function dagNodeRoutingEnv(node: DagNode, dag?: import("./dag.js").Dag): Record<string, string>;
|
|
24
24
|
export declare function discoverRoutingInventory(projectRoot?: string): RoutingInventory;
|
|
25
|
+
/** Exported for focused cache-equivalence tests. Returns the cached matcher for an
|
|
26
|
+
* already-normalized alphanumeric keyword (or null when no regex path applies). */
|
|
27
|
+
export declare function getKeywordMatcherRegExp(keyword: string): RegExp | null;
|
|
28
|
+
export declare function textMatchesKeyword(text: string, keyword: string): boolean;
|
|
25
29
|
export declare function redactMcpConfig(cfg: unknown): unknown;
|
|
26
30
|
export declare function loadMergedMcpConfig(projectRoot: string, scope: "project" | "all" | "none"): Promise<{
|
|
27
31
|
servers: Record<string, unknown>;
|
|
@@ -664,13 +664,38 @@ function addDefaultHookHints(hooks, inventory, role, readOnly, evidenceRequired)
|
|
|
664
664
|
function normalizeText(text) {
|
|
665
665
|
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
666
666
|
}
|
|
667
|
-
|
|
667
|
+
// Module-level cache of compiled keyword matchers. `textMatchesKeyword` is invoked
|
|
668
|
+
// ~150x per `selectTaskRouting` (per DAG node), so recompiling `new RegExp(...)` on
|
|
669
|
+
// every call is pure overhead. The keys are normalized keywords drawn from a fixed,
|
|
670
|
+
// bounded route-candidate keyword set, so this Map cannot grow without bound; no
|
|
671
|
+
// eviction policy is required. The compiled RegExps carry no flags (no /g, /y), so
|
|
672
|
+
// they are stateless across `.test()` calls and safe to share between callers.
|
|
673
|
+
const keywordRegExpCache = new Map();
|
|
674
|
+
function getKeywordRegExp(normalized) {
|
|
675
|
+
const cached = keywordRegExpCache.get(normalized);
|
|
676
|
+
if (cached !== undefined)
|
|
677
|
+
return cached;
|
|
678
|
+
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
679
|
+
const regex = new RegExp(`(^|[^a-z0-9])${escaped}($|[^a-z0-9])`);
|
|
680
|
+
keywordRegExpCache.set(normalized, regex);
|
|
681
|
+
return regex;
|
|
682
|
+
}
|
|
683
|
+
/** Exported for focused cache-equivalence tests. Returns the cached matcher for an
|
|
684
|
+
* already-normalized alphanumeric keyword (or null when no regex path applies). */
|
|
685
|
+
export function getKeywordMatcherRegExp(keyword) {
|
|
686
|
+
const normalized = normalizeText(keyword);
|
|
687
|
+
if (normalized.length < 2)
|
|
688
|
+
return null;
|
|
689
|
+
if (!/^[a-z0-9]+$/.test(normalized))
|
|
690
|
+
return null;
|
|
691
|
+
return getKeywordRegExp(normalized);
|
|
692
|
+
}
|
|
693
|
+
export function textMatchesKeyword(text, keyword) {
|
|
668
694
|
const normalized = normalizeText(keyword);
|
|
669
695
|
if (normalized.length < 2)
|
|
670
696
|
return false;
|
|
671
697
|
if (/^[a-z0-9]+$/.test(normalized)) {
|
|
672
|
-
|
|
673
|
-
return new RegExp(`(^|[^a-z0-9])${escaped}($|[^a-z0-9])`).test(text);
|
|
698
|
+
return getKeywordRegExp(normalized).test(text);
|
|
674
699
|
}
|
|
675
700
|
return text.includes(normalized);
|
|
676
701
|
}
|
|
@@ -55,7 +55,14 @@ export function createStatePersister(basePath = ".omk/runs") {
|
|
|
55
55
|
async save(state) {
|
|
56
56
|
const valid = validateRunId(state.runId);
|
|
57
57
|
const filePath = join(basePath, valid, "state.json");
|
|
58
|
-
|
|
58
|
+
// structuredClone (Node >=17) deep-clones without the JSON parse+stringify
|
|
59
|
+
// round-trip. INVARIANT: RunState must stay structuredClone-compatible and
|
|
60
|
+
// JSON-plain — no functions/symbols/non-cloneable handles (structuredClone
|
|
61
|
+
// throws), and no Date/Map/Set whose JSON shape differs from a live value;
|
|
62
|
+
// the final JSON.stringify below is the serializer, so output stays
|
|
63
|
+
// byte-identical to the old clone at ~half the CPU/peak-heap cost. If a
|
|
64
|
+
// non-plain field is ever added to RunState, revisit this clone.
|
|
65
|
+
const cloned = structuredClone(state);
|
|
59
66
|
const toSave = { ...redactSecrets(cloned), schemaVersion: 1 };
|
|
60
67
|
const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
61
68
|
try {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TaskRunner } from "../contracts/orchestration.js";
|
|
2
|
+
import type { ProviderId } from "./types.js";
|
|
3
|
+
export interface AnthropicMessagesRunnerOptions {
|
|
4
|
+
provider: ProviderId;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
apiKeyEnv?: string;
|
|
8
|
+
model: string;
|
|
9
|
+
promptPrefix?: string;
|
|
10
|
+
headers?: Record<string, string | undefined>;
|
|
11
|
+
maxTokens?: number;
|
|
12
|
+
contextWindow?: number;
|
|
13
|
+
reservedOutputTokens?: number;
|
|
14
|
+
safetyMarginTokens?: number;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
export declare function createAnthropicMessagesReadOnlyTaskRunner(options: AnthropicMessagesRunnerOptions): TaskRunner;
|