brane 0.0.2 → 0.3.0
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 +362 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +91 -0
- package/DESIGN.md +402 -0
- package/Dockerfile +61 -0
- package/LICENSE +656 -17
- package/README.md +174 -2
- package/SECURITY.md +205 -0
- package/dist/absorb/brane-id.js +11 -0
- package/dist/absorb/chatgpt.js +161 -0
- package/dist/absorb/claude-code.js +504 -0
- package/dist/absorb/index.js +543 -0
- package/dist/absorb/organelle.js +345 -0
- package/dist/absorb/run.js +520 -0
- package/dist/absorb/sources.js +168 -0
- package/dist/absorb/types.js +3 -0
- package/dist/agent-env.js +45 -0
- package/dist/agents.js +1168 -0
- package/dist/claude-live-session.js +124 -0
- package/dist/cli.js +50 -0
- package/dist/codex-tool-log.js +144 -0
- package/dist/commands/absorb.js +291 -0
- package/dist/commands/attach.js +31 -0
- package/dist/commands/certify.js +62 -0
- package/dist/commands/config.js +116 -0
- package/dist/commands/cost.js +126 -0
- package/dist/commands/daemon.js +350 -0
- package/dist/commands/doctor.js +262 -0
- package/dist/commands/fold.js +95 -0
- package/dist/commands/list.js +89 -0
- package/dist/commands/mcp.js +84 -0
- package/dist/commands/meld.js +332 -0
- package/dist/commands/memory.js +215 -0
- package/dist/commands/revive.js +74 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/spawn.js +1094 -0
- package/dist/commands/token.js +153 -0
- package/dist/copilot-tool-log.js +145 -0
- package/dist/cost/pricing.js +116 -0
- package/dist/cost/reprice.js +136 -0
- package/dist/crush-tool-log.js +293 -0
- package/dist/daemon/agent-scope.js +116 -0
- package/dist/daemon/awaiting.js +54 -0
- package/dist/daemon/cert-manager.js +349 -0
- package/dist/daemon/client.js +262 -0
- package/dist/daemon/gated-routes.js +112 -0
- package/dist/daemon/keep-awake.js +103 -0
- package/dist/daemon/local-socket.js +145 -0
- package/dist/daemon/presence-gate.js +206 -0
- package/dist/daemon/server.js +6349 -0
- package/dist/daemon/signal-source.js +35 -0
- package/dist/daemon/tailscale-rebind.js +58 -0
- package/dist/daemon/token-ring.js +263 -0
- package/dist/daemon/token-routes.js +190 -0
- package/dist/db/events.js +152 -0
- package/dist/db/mentions.js +179 -0
- package/dist/db/schema.js +633 -0
- package/dist/db/tokens.js +583 -0
- package/dist/db/tool-uses.js +647 -0
- package/dist/diagram/index.js +85 -0
- package/dist/diagram/label.js +455 -0
- package/dist/diagram/layout.js +476 -0
- package/dist/diagram/links.js +50 -0
- package/dist/diagram/metrics.js +152 -0
- package/dist/diagram/parse.js +487 -0
- package/dist/diagram/svg.js +389 -0
- package/dist/diagram/types.js +9 -0
- package/dist/export.js +629 -0
- package/dist/files/archive-types.js +2 -0
- package/dist/files/archive.js +182 -0
- package/dist/files/browse.js +327 -0
- package/dist/files/bytes.js +162 -0
- package/dist/files/codecs.js +33 -0
- package/dist/files/find.js +212 -0
- package/dist/files/tar.js +223 -0
- package/dist/files/zip.js +209 -0
- package/dist/folds.js +72 -0
- package/dist/grammars/ebnf.js +92 -0
- package/dist/highlight.js +250 -0
- package/dist/ids.js +24 -0
- package/dist/markdown-diagnostics.js +233 -0
- package/dist/math.js +168 -0
- package/dist/mcp/server.js +886 -0
- package/dist/memory/embedder.js +145 -0
- package/dist/memory/embedding-worker.js +71 -0
- package/dist/memory/embeddings.js +91 -0
- package/dist/memory/index.js +232 -0
- package/dist/memory/storage.js +587 -0
- package/dist/memory/wiki.js +688 -0
- package/dist/memory/wordpiece.js +247 -0
- package/dist/mentions-scan.js +40 -0
- package/dist/presence.js +259 -0
- package/dist/session-affinity.js +185 -0
- package/dist/shared/api-request.js +124 -0
- package/dist/shared/block-glyphs.js +114 -0
- package/dist/shared/diagram-css.js +17 -0
- package/dist/shared/export-selection.js +101 -0
- package/dist/shared/find-in-text.js +110 -0
- package/dist/shared/glow-hues.js +225 -0
- package/dist/shared/hex-dump.js +62 -0
- package/dist/shared/hex-pages.js +52 -0
- package/dist/shared/hit-drum.js +79 -0
- package/dist/shared/known-nids.js +58 -0
- package/dist/shared/list-scroll-keys.js +56 -0
- package/dist/shared/markdown-lists.js +72 -0
- package/dist/shared/markdown-math.js +485 -0
- package/dist/shared/markdown-mentions.js +79 -0
- package/dist/shared/markdown-nids.js +41 -0
- package/dist/shared/markdown-protocols.js +53 -0
- package/dist/shared/markdown-refs.js +123 -0
- package/dist/shared/membrane-invasion.js +342 -0
- package/dist/shared/membrane-motion.js +83 -0
- package/dist/shared/membrane-palette.js +253 -0
- package/dist/shared/membrane-terrain.js +212 -0
- package/dist/shared/message-selection.js +123 -0
- package/dist/shared/mo-glyph.js +57 -0
- package/dist/shared/narrative-format.js +237 -0
- package/dist/shared/narrative-menu.js +82 -0
- package/dist/shared/narrative-sort.js +47 -0
- package/dist/shared/narrative-status.js +52 -0
- package/dist/shared/no-pan.js +85 -0
- package/dist/shared/pane-layout.js +50 -0
- package/dist/shared/payload-inline.js +83 -0
- package/dist/shared/speaker-color.js +61 -0
- package/dist/shared/token-rotation.js +14 -0
- package/dist/shared/view-url.js +197 -0
- package/dist/shared/wiki-links.js +70 -0
- package/dist/shared/wiki-window.js +4 -0
- package/dist/tailscale.js +55 -0
- package/dist/test/absorb-claude.test.js +892 -0
- package/dist/test/agent-scope.test.js +193 -0
- package/dist/test/agents.test.js +55 -0
- package/dist/test/api-request.test.js +122 -0
- package/dist/test/awaiting-glow.test.js +54 -0
- package/dist/test/awaiting.test.js +122 -0
- package/dist/test/bind-token.test.js +88 -0
- package/dist/test/block-glyphs.test.js +68 -0
- package/dist/test/cert-manager.test.js +262 -0
- package/dist/test/claude-live-session.test.js +110 -0
- package/dist/test/codex-tool-log.test.js +568 -0
- package/dist/test/config.test.js +71 -0
- package/dist/test/copilot-tool-log.test.js +93 -0
- package/dist/test/cors-preflight.test.js +49 -0
- package/dist/test/cost-linger.test.js +31 -0
- package/dist/test/cost-pricing.test.js +72 -0
- package/dist/test/cost-reprice.test.js +139 -0
- package/dist/test/cost-sessions.test.js +301 -0
- package/dist/test/crush-tool-log.test.js +289 -0
- package/dist/test/daemon-client.test.js +109 -0
- package/dist/test/daemon-socket.test.js +293 -0
- package/dist/test/diagram-arrows.test.js +205 -0
- package/dist/test/diagram-export.test.js +210 -0
- package/dist/test/diagram-labels.test.js +313 -0
- package/dist/test/diagram-parse.test.js +191 -0
- package/dist/test/diagram-render.test.js +175 -0
- package/dist/test/diagram-review.test.js +288 -0
- package/dist/test/diagram-self-loop-labels.test.js +303 -0
- package/dist/test/embedder.test.js +84 -0
- package/dist/test/events.test.js +123 -0
- package/dist/test/export-selection.test.js +102 -0
- package/dist/test/export.test.js +505 -0
- package/dist/test/files-browse.test.js +393 -0
- package/dist/test/files-find.test.js +272 -0
- package/dist/test/find-in-text.test.js +86 -0
- package/dist/test/first-paint-viewport.test.js +59 -0
- package/dist/test/fixtures/embedding-golden.js +23 -0
- package/dist/test/fixtures/wordpiece-golden.js +167 -0
- package/dist/test/glow-hues.test.js +202 -0
- package/dist/test/harness-cost.test.js +423 -0
- package/dist/test/helpers.js +21 -0
- package/dist/test/hex-pages.test.js +68 -0
- package/dist/test/highlight.test.js +69 -0
- package/dist/test/hit-drum.test.js +71 -0
- package/dist/test/hook-config.test.js +262 -0
- package/dist/test/json-gzip.test.js +70 -0
- package/dist/test/key-routing.test.js +58 -0
- package/dist/test/known-nids.test.js +106 -0
- package/dist/test/link-preview.test.js +67 -0
- package/dist/test/list-scroll-keys.test.js +66 -0
- package/dist/test/listen-own-words.test.js +147 -0
- package/dist/test/listen-timeout.test.js +33 -0
- package/dist/test/markdown-diagnostics.test.js +121 -0
- package/dist/test/markdown-lists.test.js +182 -0
- package/dist/test/markdown-math.test.js +256 -0
- package/dist/test/markdown-nids.test.js +146 -0
- package/dist/test/markdown-protocols.test.js +139 -0
- package/dist/test/mcp-bridge-cancel.test.js +98 -0
- package/dist/test/mcp-bridge-reconnect.test.js +121 -0
- package/dist/test/mcp-narrative.test.js +193 -0
- package/dist/test/mcp.test.js +548 -0
- package/dist/test/meld.test.js +85 -0
- package/dist/test/membrane-invasion.test.js +482 -0
- package/dist/test/membrane-motion.test.js +101 -0
- package/dist/test/membrane-palette.test.js +166 -0
- package/dist/test/membrane-terrain.test.js +170 -0
- package/dist/test/memory-log.test.js +202 -0
- package/dist/test/mentions-e2e.test.js +261 -0
- package/dist/test/mentions-ledger.test.js +265 -0
- package/dist/test/mentions-listen.test.js +265 -0
- package/dist/test/mentions.test.js +238 -0
- package/dist/test/message-selection.test.js +114 -0
- package/dist/test/mo-glyph.test.js +22 -0
- package/dist/test/model-resolution.test.js +166 -0
- package/dist/test/narrate-validate.test.js +77 -0
- package/dist/test/narrate-wait.test.js +160 -0
- package/dist/test/narrative-format.test.js +219 -0
- package/dist/test/narrative-menu.test.js +137 -0
- package/dist/test/narrative-sort.test.js +75 -0
- package/dist/test/no-pan.test.js +60 -0
- package/dist/test/pane-layout.test.js +153 -0
- package/dist/test/paths.test.js +37 -0
- package/dist/test/payload-inline.test.js +91 -0
- package/dist/test/precache-fonts.test.js +72 -0
- package/dist/test/presence-gate.test.js +440 -0
- package/dist/test/presence.test.js +160 -0
- package/dist/test/read-events.test.js +220 -0
- package/dist/test/remote-meld.test.js +115 -0
- package/dist/test/revive-cli.test.js +93 -0
- package/dist/test/search-floor.test.js +206 -0
- package/dist/test/search-fusion.test.js +168 -0
- package/dist/test/search.test.js +205 -0
- package/dist/test/security.test.js +48 -0
- package/dist/test/selection-wiring.test.js +61 -0
- package/dist/test/session-affinity.test.js +295 -0
- package/dist/test/signal-source.test.js +32 -0
- package/dist/test/socket-account.test.js +268 -0
- package/dist/test/spawn-toml.test.js +72 -0
- package/dist/test/speaker-color.test.js +90 -0
- package/dist/test/tailscale-rebind.test.js +79 -0
- package/dist/test/terminal-cursor.test.js +61 -0
- package/dist/test/terminal-resync.test.js +23 -0
- package/dist/test/thin-cli.test.js +382 -0
- package/dist/test/token-bootstrap.test.js +272 -0
- package/dist/test/token-devices.test.js +587 -0
- package/dist/test/token-lapse.test.js +94 -0
- package/dist/test/token-ring.test.js +188 -0
- package/dist/test/token-rotate-cli.test.js +58 -0
- package/dist/test/token-routes.test.js +95 -0
- package/dist/test/tool-uses.test.js +1006 -0
- package/dist/test/usage-ledger.test.js +126 -0
- package/dist/test/view-url.test.js +581 -0
- package/dist/test/wiki-links.test.js +57 -0
- package/dist/test/wiki-sources.test.js +85 -0
- package/dist/test/wiki-window.test.js +31 -0
- package/dist/test/wiki.test.js +399 -0
- package/dist/test/wordpiece.test.js +68 -0
- package/dist/tls.js +148 -0
- package/dist/tmux.js +202 -0
- package/dist/user-agent.js +46 -0
- package/docker-entrypoint.sh +141 -0
- package/docs/README.md +3 -0
- package/docs/absorb.md +231 -0
- package/docs/cost-ledger.md +125 -0
- package/docs/daemon-api.md +189 -0
- package/docs/diagrams.md +163 -0
- package/docs/doctor.md +36 -0
- package/docs/export-scope.md +69 -0
- package/docs/files-view.md +133 -0
- package/docs/link-previews.md +38 -0
- package/docs/math.md +32 -0
- package/docs/mcp.md +69 -0
- package/docs/memory-search.md +75 -0
- package/docs/mentions.md +101 -0
- package/docs/multi-brane.md +14 -0
- package/docs/passkeys.md +165 -0
- package/docs/presence.md +202 -0
- package/docs/releasing.md +53 -0
- package/docs/rendering-warnings.md +22 -0
- package/docs/request-layer.md +72 -0
- package/docs/selection-store.md +50 -0
- package/docs/session-affinity.md +61 -0
- package/docs/terminal-mirroring.md +211 -0
- package/docs/token-rotation.md +413 -0
- package/docs/view-urls.md +114 -0
- package/docs/web-app.md +23 -0
- package/docs/wiki.md +43 -0
- package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
- package/expo/dist/apple-touch-icon.png +0 -0
- package/expo/dist/favicon.svg +3 -0
- package/expo/dist/index.html +177 -0
- package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/expo/dist/katex/katex.min.css +1 -0
- package/expo/dist/manifest.json +16 -0
- package/expo/dist/metadata.json +1 -0
- package/expo/dist/og/membrane.v1.png +0 -0
- package/expo/dist/pdf.min.mjs +28 -0
- package/expo/dist/pdf.worker.min.mjs +28 -0
- package/expo/dist/sw.js +154 -0
- package/package.json +81 -23
- package/protocols/LIVE_loop.md +407 -0
- package/protocols/bebrief.md +41 -0
- package/protocols/digest.md +231 -0
- package/protocols/fly.md +266 -0
- package/protocols/idle.md +81 -0
- package/protocols/keeper.md +829 -0
- package/protocols/markdown.md +80 -0
- package/protocols/memory.md +419 -0
- package/protocols/mentions.md +93 -0
- package/protocols/payloads.md +53 -0
- package/protocols/prelude.md +91 -0
- package/protocols/ramify.md +105 -0
- package/protocols/review.md +356 -0
- package/protocols/rrr.md +232 -0
- package/protocols/scribe.md +429 -0
- package/protocols/summarize.md +203 -0
- package/protocols/twin.md +174 -0
- package/protocols/wiki.md +438 -0
- package/protocols/worker.md +38 -0
- package/wiki/shared/append-only-event-log.md +148 -0
- package/wiki/shared/index.md +48 -0
- package/wiki/shared/owning-your-narratives.md +161 -0
- package/wiki/shared/premature-ontologies.md +435 -0
- package/wiki/shared/sedimentary-superintelligence.md +128 -0
- package/wiki/shared/sequestering-the-narrative.md +143 -0
- package/wiki/shared/why-wikis-work.md +216 -0
- package/.npmignore +0 -2
- package/.travis.yml +0 -5
- package/index.js +0 -8
- package/index.js~ +0 -12
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { appendFileSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createTestDb } from "./helpers.js";
|
|
7
|
+
import { readToolUseWindow } from "../db/tool-uses.js";
|
|
8
|
+
import { newCopilotLogCursor, pollCopilotToolLog, toolUseFromCopilotEvent, } from "../copilot-tool-log.js";
|
|
9
|
+
const sessionId = "copilot-test-session";
|
|
10
|
+
function event(type, data, timestamp = "2026-08-16T15:00:00.000Z") {
|
|
11
|
+
return { type, timestamp, data };
|
|
12
|
+
}
|
|
13
|
+
describe("Copilot tool session log", () => {
|
|
14
|
+
it("maps start and completion events through their shared call id", () => {
|
|
15
|
+
const start = toolUseFromCopilotEvent(event("tool.execution_start", {
|
|
16
|
+
toolCallId: "call-1", toolName: "bash", arguments: { command: "true" },
|
|
17
|
+
}));
|
|
18
|
+
const complete = toolUseFromCopilotEvent(event("tool.execution_complete", {
|
|
19
|
+
toolCallId: "call-1", success: false, error: { message: "nope" },
|
|
20
|
+
}));
|
|
21
|
+
assert.equal(start.status, "pending");
|
|
22
|
+
assert.equal(complete.status, "failed");
|
|
23
|
+
assert.equal(complete.dedupKey, start.dedupKey);
|
|
24
|
+
});
|
|
25
|
+
it("records appended starts and completions once, retaining an incomplete trailing line", () => {
|
|
26
|
+
const stateRoot = mkdtempSync(join(tmpdir(), "brane-copilot-log-"));
|
|
27
|
+
const sessionDir = join(stateRoot, sessionId);
|
|
28
|
+
const eventPath = join(sessionDir, "events.jsonl");
|
|
29
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
30
|
+
writeFileSync(eventPath, JSON.stringify(event("tool.execution_start", {
|
|
31
|
+
toolCallId: "call-2", toolName: "bash", arguments: { command: "echo 🦊" },
|
|
32
|
+
})) + "\n");
|
|
33
|
+
const db = createTestDb();
|
|
34
|
+
const cursor = newCopilotLogCursor();
|
|
35
|
+
assert.equal(pollCopilotToolLog(db, "copilot-test", sessionId, cursor, stateRoot).length, 1);
|
|
36
|
+
appendFileSync(eventPath, JSON.stringify(event("tool.execution_complete", {
|
|
37
|
+
toolCallId: "call-2", success: true, result: { output: "done" },
|
|
38
|
+
}, "2026-08-16T15:00:01.000Z")) + "\n" + '{"type":"tool.execution_start"');
|
|
39
|
+
assert.equal(pollCopilotToolLog(db, "copilot-test", sessionId, cursor, stateRoot).length, 1);
|
|
40
|
+
const rows = readToolUseWindow(db, "copilot-test");
|
|
41
|
+
assert.equal(rows.length, 1);
|
|
42
|
+
assert.equal(rows[0].status, "completed");
|
|
43
|
+
assert.equal(rows[0].name, "bash");
|
|
44
|
+
assert.equal(rows[0].duration_ms, 1000);
|
|
45
|
+
assert.equal(pollCopilotToolLog(db, "copilot-test", sessionId, cursor, stateRoot).length, 0);
|
|
46
|
+
});
|
|
47
|
+
it("compacts Brane loop calls and removes a duplicate detailed result", () => {
|
|
48
|
+
const stateRoot = mkdtempSync(join(tmpdir(), "brane-copilot-log-"));
|
|
49
|
+
const sessionDir = join(stateRoot, sessionId);
|
|
50
|
+
const eventPath = join(sessionDir, "events.jsonl");
|
|
51
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
52
|
+
writeFileSync(eventPath, [
|
|
53
|
+
event("tool.execution_start", {
|
|
54
|
+
toolCallId: "bridge-1", toolName: "brane-narrate", arguments: {},
|
|
55
|
+
mcpServerName: "brane", mcpToolName: "narrate",
|
|
56
|
+
}),
|
|
57
|
+
event("tool.execution_complete", {
|
|
58
|
+
toolCallId: "bridge-1", success: true,
|
|
59
|
+
result: { content: JSON.stringify({ seq: 42 }), detailedContent: JSON.stringify({ seq: 42 }) },
|
|
60
|
+
}),
|
|
61
|
+
event("tool.execution_start", {
|
|
62
|
+
toolCallId: "bridge-2", toolName: "brane-narrate", arguments: { nid: "claude-target", say: "status" },
|
|
63
|
+
mcpServerName: "brane", mcpToolName: "narrate",
|
|
64
|
+
}),
|
|
65
|
+
event("tool.execution_complete", {
|
|
66
|
+
toolCallId: "bridge-2", success: true,
|
|
67
|
+
result: {
|
|
68
|
+
content: JSON.stringify({ nid: "claude-target", seq: 12 }),
|
|
69
|
+
detailedContent: JSON.stringify({ nid: "claude-target", seq: 12 }),
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
event("tool.execution_start", {
|
|
73
|
+
toolCallId: "call-3", toolName: "bash", arguments: { command: "true" },
|
|
74
|
+
}),
|
|
75
|
+
event("tool.execution_complete", {
|
|
76
|
+
toolCallId: "call-3", success: true, result: { content: "done", detailedContent: "done" },
|
|
77
|
+
}),
|
|
78
|
+
].map((entry) => JSON.stringify(entry)).join("\n") + "\n");
|
|
79
|
+
const db = createTestDb();
|
|
80
|
+
assert.equal(pollCopilotToolLog(db, "copilot-test", sessionId, newCopilotLogCursor(), stateRoot).length, 6);
|
|
81
|
+
const rows = readToolUseWindow(db, "copilot-test");
|
|
82
|
+
assert.equal(rows.length, 3);
|
|
83
|
+
assert.equal(rows[0].name, "mcp__brane__narrate");
|
|
84
|
+
assert.equal(rows[0].input, "{}");
|
|
85
|
+
assert.equal(rows[0].output, "{}");
|
|
86
|
+
assert.equal(rows[1].name, "mcp__brane__narrate");
|
|
87
|
+
assert.deepEqual(JSON.parse(rows[1].input), { nid: "claude-target" });
|
|
88
|
+
assert.deepEqual(JSON.parse(rows[1].output), { nid: "claude-target", seq: 12 });
|
|
89
|
+
assert.equal(rows[2].name, "bash");
|
|
90
|
+
assert.deepEqual(JSON.parse(rows[2].output), { content: "done" });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=copilot-tool-log.test.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { test, describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { setCorsHeaders } from "../daemon/server.js";
|
|
4
|
+
/** Just enough of a request/response pair to read headers back off. */
|
|
5
|
+
function exchange(origin) {
|
|
6
|
+
const headers = new Map();
|
|
7
|
+
const req = { headers: origin ? { origin } : {} };
|
|
8
|
+
const res = {
|
|
9
|
+
setHeader(name, value) {
|
|
10
|
+
headers.set(name.toLowerCase(), value);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
return { req, res, headers };
|
|
14
|
+
}
|
|
15
|
+
const APP_DOMAIN = "brane.organelle.dev";
|
|
16
|
+
describe("CORS preflight caching", () => {
|
|
17
|
+
test("an allowed origin is told how long it may reuse the answer", () => {
|
|
18
|
+
// Without this header Chrome caches a preflight for 5 seconds, which on a
|
|
19
|
+
// fast link is indistinguishable from caching forever and on a slow one is
|
|
20
|
+
// indistinguishable from not caching at all — every request pays its own
|
|
21
|
+
// extra round trip. At 741ms RTT that was fatal rather than merely slow.
|
|
22
|
+
const { req, res, headers } = exchange(`https://${APP_DOMAIN}`);
|
|
23
|
+
setCorsHeaders(req, res, APP_DOMAIN, 8080);
|
|
24
|
+
const maxAge = headers.get("access-control-max-age");
|
|
25
|
+
assert.ok(maxAge, "preflight answers must be cacheable");
|
|
26
|
+
assert.ok(Number(maxAge) >= 3600, `max-age ${maxAge} is too short to spare a slow link repeated preflights`);
|
|
27
|
+
});
|
|
28
|
+
test("the rest of the CORS contract is unchanged", () => {
|
|
29
|
+
const { req, res, headers } = exchange(`https://${APP_DOMAIN}`);
|
|
30
|
+
setCorsHeaders(req, res, APP_DOMAIN, 8080);
|
|
31
|
+
assert.equal(headers.get("access-control-allow-origin"), `https://${APP_DOMAIN}`);
|
|
32
|
+
assert.match(headers.get("access-control-allow-methods") ?? "", /GET/);
|
|
33
|
+
assert.match(headers.get("access-control-allow-headers") ?? "", /Authorization/);
|
|
34
|
+
assert.equal(headers.get("access-control-allow-credentials"), "true");
|
|
35
|
+
});
|
|
36
|
+
test("a disallowed origin is told nothing at all", () => {
|
|
37
|
+
// Caching must not become a way to hand headers to an origin that would
|
|
38
|
+
// otherwise have been refused.
|
|
39
|
+
const { req, res, headers } = exchange("https://evil.example.com");
|
|
40
|
+
setCorsHeaders(req, res, APP_DOMAIN, 8080);
|
|
41
|
+
assert.equal(headers.size, 0);
|
|
42
|
+
});
|
|
43
|
+
test("a request with no Origin gets no CORS headers", () => {
|
|
44
|
+
const { req, res, headers } = exchange();
|
|
45
|
+
setCorsHeaders(req, res, APP_DOMAIN, 8080);
|
|
46
|
+
assert.equal(headers.size, 0);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=cors-preflight.test.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { costSampleTargets, lingerCostSampling } from "../daemon/server.js";
|
|
4
|
+
// The sampler's target set: connected narratives plus the recently
|
|
5
|
+
// disconnected, so a session's final turns — written after its MCP bridge
|
|
6
|
+
// tears down — still reach the ledger without a manual backfill.
|
|
7
|
+
describe("costSampleTargets", () => {
|
|
8
|
+
it("samples a disconnected narrative until its linger expires", () => {
|
|
9
|
+
const linger = new Map([["claude-gone", 1_090_000]]);
|
|
10
|
+
// Within the window: sampled alongside the connected.
|
|
11
|
+
assert.deepEqual(costSampleTargets(new Set(["claude-live"]), linger, 1_000_000), new Set(["claude-live", "claude-gone"]));
|
|
12
|
+
assert.ok(linger.has("claude-gone"), "not evicted while the window is open");
|
|
13
|
+
// At the deadline: evicted, and no longer sampled.
|
|
14
|
+
assert.deepEqual(costSampleTargets(new Set(["claude-live"]), linger, 1_090_000), new Set(["claude-live"]));
|
|
15
|
+
assert.equal(linger.size, 0);
|
|
16
|
+
});
|
|
17
|
+
it("evicts a lingering narrative that reconnects, so a later disconnect starts a fresh window", () => {
|
|
18
|
+
const linger = new Map([["claude-back", 1_090_000]]);
|
|
19
|
+
const targets = costSampleTargets(new Set(["claude-back"]), linger, 1_000_000);
|
|
20
|
+
assert.deepEqual(targets, new Set(["claude-back"]), "sampled once, as connected");
|
|
21
|
+
assert.equal(linger.size, 0, "the stale deadline does not survive the reconnect");
|
|
22
|
+
});
|
|
23
|
+
it("lingerCostSampling feeds the sampler's own map, which linger defaults to", () => {
|
|
24
|
+
lingerCostSampling("claude-observed", 5_000);
|
|
25
|
+
assert.ok(costSampleTargets(new Set(), undefined, 10_000).has("claude-observed"));
|
|
26
|
+
// 5_000 + the 90 s window has passed: expired and evicted, so this test
|
|
27
|
+
// leaves no residue in the shared map.
|
|
28
|
+
assert.ok(!costSampleTargets(new Set(), undefined, 200_000).has("claude-observed"));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=cost-linger.test.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
// The price table memoizes ~/.brane/pricing.json on first use, so HOME is
|
|
7
|
+
// sandboxed — with an override file in it — before anything prices a message.
|
|
8
|
+
// node --test runs each file in its own process, so this one owns the memo.
|
|
9
|
+
const home = mkdtempSync(join(tmpdir(), "brane-pricing-home-"));
|
|
10
|
+
process.env.HOME = home;
|
|
11
|
+
mkdirSync(join(home, ".brane"));
|
|
12
|
+
writeFileSync(join(home, ".brane", "pricing.json"), JSON.stringify({
|
|
13
|
+
// More specific than the built-in `opus` — and than `opus-5-5`, which
|
|
14
|
+
// would win if overrides were still appended after the defaults.
|
|
15
|
+
"opus-5-5-fast-test": { input: 1, output: 2 },
|
|
16
|
+
"kimi-latest": { input: 3, output: 15, cacheRead: 0.25 },
|
|
17
|
+
}));
|
|
18
|
+
const { priceMessage } = await import("../cost/pricing.js");
|
|
19
|
+
const M = 1e6;
|
|
20
|
+
const only = (k) => ({
|
|
21
|
+
input: 0,
|
|
22
|
+
output: 0,
|
|
23
|
+
cacheRead: 0,
|
|
24
|
+
cacheWrite5m: 0,
|
|
25
|
+
cacheWrite1h: 0,
|
|
26
|
+
[k]: M,
|
|
27
|
+
});
|
|
28
|
+
// $ for 1 MTok in each category: input, output, cache read, 5m write, 1h write.
|
|
29
|
+
const rates = (model) => ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h"].map((k) => Math.round(priceMessage(model, only(k)) * 1e6) / 1e6);
|
|
30
|
+
describe("price table (checked against the published rates, 2026-09-23)", () => {
|
|
31
|
+
it("prices Fable 5.1 and Mythos 5.1 cache reads at $0.25, not 0.1× input", () => {
|
|
32
|
+
assert.deepEqual(rates("claude-fable-5-1"), [10, 50, 0.25, 12.5, 20]);
|
|
33
|
+
assert.deepEqual(rates("claude-mythos-5-1"), [10, 50, 0.25, 12.5, 20]);
|
|
34
|
+
});
|
|
35
|
+
it("keeps Fable 5 and Mythos 5 at the standard 0.1× cache read", () => {
|
|
36
|
+
assert.deepEqual(rates("claude-fable-5"), [10, 50, 1, 12.5, 20]);
|
|
37
|
+
assert.deepEqual(rates("claude-mythos-5"), [10, 50, 1, 12.5, 20]);
|
|
38
|
+
});
|
|
39
|
+
it("prices Opus 5.5 at $4/$20 with $0.20 cache reads, apart from the rest of the Opus line", () => {
|
|
40
|
+
assert.deepEqual(rates("claude-opus-5-5"), [4, 20, 0.2, 5, 8]);
|
|
41
|
+
for (const m of ["claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"]) {
|
|
42
|
+
assert.deepEqual(rates(m), [5, 25, 0.5, 6.25, 10], m);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it("prices Sonnet 5 at $2/$10, apart from Sonnet 4.x", () => {
|
|
46
|
+
assert.deepEqual(rates("claude-sonnet-5"), [2, 10, 0.2, 2.5, 4]);
|
|
47
|
+
assert.deepEqual(rates("claude-sonnet-4-6"), [3, 15, 0.3, 3.75, 6]);
|
|
48
|
+
});
|
|
49
|
+
it("leaves Haiku 4.5 and Kimi K3 where they were", () => {
|
|
50
|
+
assert.deepEqual(rates("claude-haiku-4-5-20251001"), [1, 5, 0.1, 1.25, 2]);
|
|
51
|
+
assert.deepEqual(rates("accounts/fireworks/models/kimi-k3"), [3, 15, 0.3, 3.75, 6]);
|
|
52
|
+
assert.deepEqual(rates("accounts/fireworks/routers/kimi-k3-fast"), [4.5, 22.5, 0.45, 5.625, 9]);
|
|
53
|
+
});
|
|
54
|
+
it("doubles every category in fast mode, cache multipliers on top", () => {
|
|
55
|
+
const u = { input: M, output: M, cacheRead: M, cacheWrite5m: M, cacheWrite1h: M };
|
|
56
|
+
const standard = priceMessage("claude-opus-5-5", u);
|
|
57
|
+
assert.equal(priceMessage("claude-opus-5-5", { ...u, speed: "standard" }), standard);
|
|
58
|
+
assert.equal(priceMessage("claude-opus-5-5", { ...u, speed: "fast" }), 2 * standard);
|
|
59
|
+
// $8 / $40 per MTok, as published for Opus 5.5 fast mode.
|
|
60
|
+
assert.equal(priceMessage("claude-opus-5-5", { ...only("input"), speed: "fast" }), 8);
|
|
61
|
+
assert.equal(priceMessage("claude-opus-5-5", { ...only("output"), speed: "fast" }), 40);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("pricing.json overrides", () => {
|
|
65
|
+
it("match before the built-in keys, however general those are", () => {
|
|
66
|
+
assert.deepEqual(rates("claude-opus-5-5-fast-test").slice(0, 2), [1, 2]);
|
|
67
|
+
});
|
|
68
|
+
it("can price a model the defaults don't know, including its cache-read rate", () => {
|
|
69
|
+
assert.deepEqual(rates("kimi-latest"), [3, 15, 0.25, 3.75, 6]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=cost-pricing.test.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { mkdtempSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
// No pricing.json may leak in from the real HOME: the current table must be
|
|
7
|
+
// exactly the built-in one for the amounts below to hold.
|
|
8
|
+
process.env.HOME = mkdtempSync(join(tmpdir(), "brane-reprice-home-"));
|
|
9
|
+
const { createTestDb } = await import("./helpers.js");
|
|
10
|
+
const { priceMessage, priceMessageV1 } = await import("../cost/pricing.js");
|
|
11
|
+
const { recoverCacheWrite1h, repriceLedger } = await import("../cost/reprice.js");
|
|
12
|
+
const { recordUsageEntries } = await import("../daemon/server.js");
|
|
13
|
+
/** A row as the pre-split code wrote it: V1 amount, one cache_write count. */
|
|
14
|
+
function insertV1(db, r) {
|
|
15
|
+
const amount = priceMessageV1(r.model, {
|
|
16
|
+
input: r.input,
|
|
17
|
+
output: r.output,
|
|
18
|
+
cacheRead: r.cacheRead,
|
|
19
|
+
cacheWrite5m: r.w5,
|
|
20
|
+
cacheWrite1h: r.w1,
|
|
21
|
+
});
|
|
22
|
+
db.prepare(`INSERT INTO usage_ledger (key, narrative_id, session_id, agent_type, unit, amount, model,
|
|
23
|
+
input, output, cache_read, cache_write, first_seen)
|
|
24
|
+
VALUES (?, ?, 's', 'claude', 'USD', ?, ?, ?, ?, ?, ?, 0)`).run(r.key, r.nid, amount, r.model, r.input, r.output, r.cacheRead, r.w5 + r.w1);
|
|
25
|
+
return amount;
|
|
26
|
+
}
|
|
27
|
+
const current = (r) => priceMessage(r.model, { input: r.input, output: r.output, cacheRead: r.cacheRead, cacheWrite5m: r.w5, cacheWrite1h: r.w1 });
|
|
28
|
+
function narrative(db, nid, cost) {
|
|
29
|
+
db.prepare(`INSERT INTO narratives (id, brane_id, agent_type, role, created_at, status, cost_amount, cost_unit)
|
|
30
|
+
VALUES (?, 'brane-test', 'claude', 'worker', 0, 'ended', ?, ?)`).run(nid, cost, cost === null ? null : "USD");
|
|
31
|
+
}
|
|
32
|
+
const col = (db, nid) => db.prepare("SELECT cost_amount FROM narratives WHERE id = ?").get(nid).cost_amount;
|
|
33
|
+
const ledger = (db, key) => db.prepare("SELECT amount, cache_write_1h FROM usage_ledger WHERE key = ?").get(key);
|
|
34
|
+
// Token shapes taken from real rows: a long session's cache-read-heavy turn,
|
|
35
|
+
// with mixed 5m / 1h writes so the split actually matters.
|
|
36
|
+
const FABLE51 = { key: "claude:f1", nid: "claude-f", model: "claude-fable-5-1", input: 6, output: 843, cacheRead: 203752, w5: 1200, w1: 16270 };
|
|
37
|
+
const OPUS55 = { key: "claude:o1", nid: "claude-o", model: "claude-opus-5-5", input: 2, output: 650, cacheRead: 227000, w5: 0, w1: 7000 };
|
|
38
|
+
const SONNET5 = { key: "claude:s1", nid: "claude-s", model: "claude-sonnet-5", input: 2, output: 332, cacheRead: 0, w5: 0, w1: 102607 };
|
|
39
|
+
const OPUS5 = { key: "claude:u1", nid: "claude-u", model: "claude-opus-5", input: 10, output: 900, cacheRead: 150000, w5: 300, w1: 4000 };
|
|
40
|
+
describe("recoverCacheWrite1h", () => {
|
|
41
|
+
it("recovers the exact split from a V1 amount", () => {
|
|
42
|
+
for (const r of [FABLE51, OPUS55, SONNET5, OPUS5]) {
|
|
43
|
+
const amount = priceMessageV1(r.model, { input: r.input, output: r.output, cacheRead: r.cacheRead, cacheWrite5m: r.w5, cacheWrite1h: r.w1 });
|
|
44
|
+
const row = { model: r.model, amount, input: r.input, output: r.output, cache_read: r.cacheRead, cache_write: r.w5 + r.w1 };
|
|
45
|
+
assert.equal(recoverCacheWrite1h(row), r.w1, r.model);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
it("refuses an amount no split explains (e.g. priced under an override)", () => {
|
|
49
|
+
const row = { model: "claude-opus-5", amount: 123.456, input: 1, output: 1, cache_read: 0, cache_write: 10 };
|
|
50
|
+
assert.equal(recoverCacheWrite1h(row), null);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("repriceLedger", () => {
|
|
54
|
+
it("corrects mispriced rows, leaves correct ones, and records the recovered split", () => {
|
|
55
|
+
const db = createTestDb();
|
|
56
|
+
for (const r of [FABLE51, OPUS55, SONNET5, OPUS5]) {
|
|
57
|
+
narrative(db, r.nid, null);
|
|
58
|
+
insertV1(db, r);
|
|
59
|
+
}
|
|
60
|
+
const result = repriceLedger(db);
|
|
61
|
+
assert.equal(result.scanned, 4);
|
|
62
|
+
assert.equal(result.recovered, 4);
|
|
63
|
+
assert.equal(result.unrecoverable, 0);
|
|
64
|
+
assert.equal(result.changed, 3);
|
|
65
|
+
for (const r of [FABLE51, OPUS55, SONNET5, OPUS5]) {
|
|
66
|
+
const row = ledger(db, r.key);
|
|
67
|
+
assert.ok(Math.abs(row.amount - current(r)) < 1e-12, r.model);
|
|
68
|
+
assert.equal(row.cache_write_1h, r.w1, r.model);
|
|
69
|
+
}
|
|
70
|
+
// A second pass reads the recorded split and finds nothing to do.
|
|
71
|
+
const again = repriceLedger(db);
|
|
72
|
+
assert.equal(again.recovered, 0);
|
|
73
|
+
assert.equal(again.changed, 0);
|
|
74
|
+
});
|
|
75
|
+
it("writes nothing on a dry run, and reports what the real run then does", () => {
|
|
76
|
+
const db = createTestDb();
|
|
77
|
+
narrative(db, FABLE51.nid, 5);
|
|
78
|
+
const before = insertV1(db, FABLE51);
|
|
79
|
+
const dry = repriceLedger(db, { dryRun: true });
|
|
80
|
+
assert.equal(ledger(db, FABLE51.key).amount, before);
|
|
81
|
+
assert.equal(ledger(db, FABLE51.key).cache_write_1h, null);
|
|
82
|
+
assert.equal(col(db, FABLE51.nid), 5);
|
|
83
|
+
const real = repriceLedger(db);
|
|
84
|
+
assert.deepEqual({ ...dry, dry_run: false }, real);
|
|
85
|
+
});
|
|
86
|
+
it("moves the card total by the correction, keeping pre-ledger cost above the ledger sum", () => {
|
|
87
|
+
const db = createTestDb();
|
|
88
|
+
// The column holds $40 more than the ledger — cost from transcripts
|
|
89
|
+
// that predate the ledger. The correction applies; that $40 survives.
|
|
90
|
+
const amount = insertV1(db, FABLE51);
|
|
91
|
+
narrative(db, FABLE51.nid, amount + 40);
|
|
92
|
+
const result = repriceLedger(db);
|
|
93
|
+
const delta = current(FABLE51) - amount;
|
|
94
|
+
assert.ok(delta < 0);
|
|
95
|
+
assert.ok(Math.abs(col(db, FABLE51.nid) - (amount + 40 + delta)) < 1e-6);
|
|
96
|
+
assert.equal(result.narratives[0].nid, FABLE51.nid);
|
|
97
|
+
});
|
|
98
|
+
it("brings a column equal to the ledger sum down to the new sum", () => {
|
|
99
|
+
const db = createTestDb();
|
|
100
|
+
const amount = insertV1(db, OPUS55);
|
|
101
|
+
narrative(db, OPUS55.nid, amount);
|
|
102
|
+
repriceLedger(db);
|
|
103
|
+
assert.ok(Math.abs(col(db, OPUS55.nid) - current(OPUS55)) < 1e-6);
|
|
104
|
+
});
|
|
105
|
+
it("leaves session high-water rows and model-less rows alone", () => {
|
|
106
|
+
const db = createTestDb();
|
|
107
|
+
db.prepare(`INSERT INTO usage_ledger (key, narrative_id, unit, amount, first_seen)
|
|
108
|
+
VALUES ('session:goose:x', 'goose-a', 'USD', 22.09, 0)`).run();
|
|
109
|
+
const result = repriceLedger(db);
|
|
110
|
+
assert.equal(result.scanned, 0);
|
|
111
|
+
assert.equal(ledger(db, "session:goose:x").amount, 22.09);
|
|
112
|
+
});
|
|
113
|
+
it("new rows carry the split and speed, so they reprice without inference", () => {
|
|
114
|
+
const db = createTestDb();
|
|
115
|
+
const u = { input: 1, output: 100, cacheRead: 1000, cacheWrite5m: 50, cacheWrite1h: 70, speed: "fast" };
|
|
116
|
+
recordUsageEntries(db, "claude-n", "claude", [
|
|
117
|
+
{
|
|
118
|
+
sessionId: "s",
|
|
119
|
+
sig: "1",
|
|
120
|
+
unit: "USD",
|
|
121
|
+
entries: [
|
|
122
|
+
{
|
|
123
|
+
key: "claude:n1",
|
|
124
|
+
amount: priceMessage("claude-opus-5-5", u),
|
|
125
|
+
model: "claude-opus-5-5",
|
|
126
|
+
tokens: { input: 1, output: 100, cacheRead: 1000, cacheWrite: 120, cacheWrite1h: 70 },
|
|
127
|
+
speed: "fast",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
const row = db.prepare("SELECT cache_write_1h, speed FROM usage_ledger WHERE key = 'claude:n1'").get();
|
|
133
|
+
assert.deepEqual({ ...row }, { cache_write_1h: 70, speed: "fast" });
|
|
134
|
+
const result = repriceLedger(db);
|
|
135
|
+
assert.equal(result.recovered, 0);
|
|
136
|
+
assert.equal(result.changed, 0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=cost-reprice.test.js.map
|