@volter/supercode-interchange 0.1.22 → 0.1.23

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.
Files changed (129) hide show
  1. package/LICENSE-diffy-MIT +23 -0
  2. package/README.md +422 -2
  3. package/channel-records.mjs +71 -0
  4. package/channel-session-counts.mjs +81 -0
  5. package/channels-read.mjs +90 -0
  6. package/corpus-audit-records.mjs +192 -0
  7. package/corpus-audit-render.mjs +47 -0
  8. package/corpus-audit-schema.mjs +89 -0
  9. package/corpus-audit.d.ts +16 -0
  10. package/corpus-audit.mjs +132 -0
  11. package/file-io.mjs +21 -2
  12. package/harness-settings.mjs +19 -0
  13. package/hermes-fire-links.mjs +135 -0
  14. package/hermes-fires.mjs +106 -0
  15. package/hermes-read-home.d.ts +3 -0
  16. package/hermes-read-home.mjs +235 -0
  17. package/hermes-read-store.mjs +49 -0
  18. package/hermes-read-validation.mjs +170 -0
  19. package/hermes-read-workflow.mjs +164 -0
  20. package/hermes-read-yaml.mjs +68 -0
  21. package/hermes-store.mjs +1 -1
  22. package/homes.mjs +2 -2
  23. package/index.d.ts +14 -1
  24. package/index.mjs +1 -1
  25. package/inspection-builtin-tools.json +261 -0
  26. package/inspection-config-fields.json +765 -0
  27. package/inspection-schemas.d.ts +4 -0
  28. package/inspection-schemas.mjs +117 -0
  29. package/job-notepad-read.mjs +97 -0
  30. package/jobs-control.mjs +250 -0
  31. package/jobs-read.mjs +198 -0
  32. package/jsonl.mjs +4 -3
  33. package/memory-show.mjs +187 -0
  34. package/metadata.mjs +45 -3
  35. package/native-json.mjs +46 -3
  36. package/ontology-contracts.d.ts +5 -0
  37. package/ontology-contracts.mjs +29 -0
  38. package/ontology-operations.json +198 -0
  39. package/ontology-schemas.json +7946 -0
  40. package/openclaw-jobs-read.mjs +117 -0
  41. package/openclaw-profile-home.mjs +142 -0
  42. package/orchestrator-read-records.mjs +112 -0
  43. package/orchestrator-store.mjs +21 -9
  44. package/package.json +59 -4
  45. package/parity-ledger.json +6984 -0
  46. package/parity-orchestration.json +5538 -0
  47. package/parity-report.d.ts +7 -0
  48. package/parity-report.mjs +46 -0
  49. package/preset-profiles.mjs +27 -0
  50. package/profile-command.mjs +32 -0
  51. package/profile-orchestrator-door.mjs +104 -0
  52. package/profile-paths.mjs +25 -0
  53. package/profiles-control.mjs +98 -0
  54. package/profiles-read.mjs +119 -0
  55. package/reduction-attribution.mjs +54 -0
  56. package/reduction-diff.mjs +165 -0
  57. package/reduction-log.mjs +177 -0
  58. package/reduction-view.mjs +145 -0
  59. package/retained-projection.mjs +9 -0
  60. package/routes-read.mjs +103 -0
  61. package/runs-read.mjs +224 -0
  62. package/session-activity.d.ts +23 -0
  63. package/session-activity.mjs +124 -0
  64. package/session-append-scan.mjs +27 -17
  65. package/session-branch-handoff.mjs +115 -0
  66. package/session-claude-graph.mjs +29 -7
  67. package/session-claude-index.mjs +732 -114
  68. package/session-claude-runtime.d.ts +28 -0
  69. package/session-claude-runtime.mjs +171 -6
  70. package/session-claude-scan-store.mjs +164 -0
  71. package/session-claude.mjs +9 -0
  72. package/session-codex-activity.d.ts +6 -0
  73. package/session-codex-activity.mjs +120 -0
  74. package/session-codex-window.mjs +24 -4
  75. package/session-config-scan.mjs +141 -0
  76. package/session-convert.d.ts +33 -0
  77. package/session-convert.mjs +179 -0
  78. package/session-emission-version.json +1 -0
  79. package/session-file.mjs +11 -1
  80. package/session-follower.mjs +17 -5
  81. package/session-fresh-id.mjs +15 -0
  82. package/session-hermes-import.mjs +61 -0
  83. package/session-hermes.mjs +1 -1
  84. package/session-import.mjs +37 -0
  85. package/session-inspect.d.ts +18 -0
  86. package/session-inspect.mjs +122 -0
  87. package/session-machine-tree.d.ts +33 -0
  88. package/session-machine-tree.mjs +274 -0
  89. package/session-materialize-files.mjs +78 -0
  90. package/session-materialize-opencode.mjs +137 -0
  91. package/session-materialize.mjs +186 -0
  92. package/session-message.mjs +85 -0
  93. package/session-model.d.ts +9 -0
  94. package/session-model.mjs +1 -0
  95. package/session-opencode-store.mjs +39 -3
  96. package/session-path.mjs +6 -3
  97. package/session-pi-content.mjs +7 -2
  98. package/session-read.d.ts +134 -2
  99. package/session-read.mjs +86 -9
  100. package/session-recorded-config.mjs +3 -2
  101. package/session-records.mjs +12 -6
  102. package/session-residue-store.mjs +90 -0
  103. package/session-residue.mjs +150 -0
  104. package/session-resume-instructions.mjs +1 -1
  105. package/session-saved-read.mjs +105 -0
  106. package/session-transfer.mjs +141 -0
  107. package/session-views.mjs +1 -0
  108. package/session-write-claude.mjs +135 -0
  109. package/session-write-codex.mjs +146 -0
  110. package/session-write-gemini.mjs +77 -0
  111. package/session-write-goose.mjs +99 -0
  112. package/session-write-grok.mjs +70 -0
  113. package/session-write-native.mjs +41 -0
  114. package/session-write-opencode.mjs +197 -0
  115. package/session-write-pi.mjs +130 -0
  116. package/session-write-residue.mjs +19 -0
  117. package/session-write-utils.mjs +77 -0
  118. package/session-write.d.ts +10 -0
  119. package/session-write.mjs +37 -0
  120. package/skill-toml.mjs +80 -0
  121. package/skills-control.mjs +193 -0
  122. package/skills-list.mjs +237 -0
  123. package/source-files.mjs +12 -2
  124. package/sqlite-reader.mjs +134 -18
  125. package/support-registry.json +402 -0
  126. package/support-report.mjs +69 -0
  127. package/trigger-records.mjs +77 -0
  128. package/triggers-read.mjs +67 -0
  129. package/workflow-read.mjs +165 -0
@@ -0,0 +1,7 @@
1
+ export type ParityPreset = 'cc-parity' | 'cx-parity' | 'hermes' | 'openclaw' | 'orchestrator';
2
+ export type ParityStatus = 'implemented' | 'partial' | 'absent' | 'irreducible' | 'not_applicable' | 'unaudited';
3
+ export interface ParityGap { id: string; domain: number; capability: string; status: ParityStatus; cost?: 'trivial' | 'architectural'; note: string }
4
+ export interface ParityReport { summary: { preset: ParityPreset; harness_column: string; rows: number; counts: Partial<Record<ParityStatus, number>>; gaps: number }; gaps: ParityGap[] }
5
+ export const PARITY_PRESETS: readonly ParityPreset[];
6
+ export function parityReport(preset: string): ParityReport | undefined;
7
+ export function formatParityReport(report: ParityReport, options?: { json?: boolean }): string;
@@ -0,0 +1,46 @@
1
+ // The frozen ledgers are an audited inventory, not a runtime verification.
2
+ import { readFileSync } from 'node:fs';
3
+ import { parseNativeJson, stringifyNativeJsonPretty } from './native-json.mjs';
4
+
5
+ const catalog = parseNativeJson(readFileSync(new URL('./parity-ledger.json', import.meta.url), 'utf8'));
6
+ const orchestration = parseNativeJson(readFileSync(new URL('./parity-orchestration.json', import.meta.url), 'utf8'));
7
+ export const PARITY_PRESETS = Object.freeze(['cc-parity', 'cx-parity', 'hermes', 'openclaw', 'orchestrator']);
8
+ const gaps = new Set(['partial', 'absent', 'irreducible', 'unaudited']);
9
+ const labels = ['implemented', 'partial', 'absent', 'irreducible', 'not_applicable', 'unaudited'];
10
+
11
+ /** Fresh report over committed rows, preserving the native denominator and row order. */
12
+ export function parityReport(preset) {
13
+ if (!PARITY_PRESETS.includes(preset)) return undefined;
14
+ const column = preset === 'cc-parity' ? 'cc' : preset === 'cx-parity' ? 'cx' : preset;
15
+ const own = preset === 'orchestrator', gateway = column !== 'cc' && column !== 'cx';
16
+ const rows = (gateway ? orchestration : catalog).filter(row => own || row[column] !== 'no');
17
+ const counts = new Map(), missing = [];
18
+ for (const row of rows) {
19
+ const status = own ? row.orchestrator_status : row.status;
20
+ counts.set(status, (counts.get(status) ?? 0) + 1);
21
+ if (!gaps.has(status)) continue;
22
+ const cost = own ? row.orchestrator_cost : row.cost;
23
+ missing.push({ id: row.id, domain: gateway ? 11 : row.domain,
24
+ capability: row.capability, status, ...(cost == null ? {} : { cost }),
25
+ note: (own ? row.orchestrator_note : row.note) ?? '' });
26
+ }
27
+ return { summary: { preset, harness_column: column, rows: rows.length,
28
+ counts: Object.fromEntries([...counts].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)), gaps: missing.length }, gaps: missing };
29
+ }
30
+
31
+ export function formatParityReport(report, { json = false } = {}) {
32
+ if (json) return `${stringifyNativeJsonPretty(report)}\n`;
33
+ const summary = report.summary;
34
+ let text = `${summary.preset}: ${summary.rows} rows · ${summary.gaps} gaps`;
35
+ for (const status of labels) if (Object.hasOwn(summary.counts, status)) text += ` · ${summary.counts[status]} ${status}`;
36
+ text += '\n';
37
+ let domain = 0;
38
+ for (const row of report.gaps) {
39
+ if (row.domain !== domain) { domain = row.domain; text += `\nDomain ${domain}\n`; }
40
+ const cost = row.cost === 'trivial' ? ' [trivial]' : row.cost === 'architectural' ? ' [architectural]' : '';
41
+ text += ` ${row.status.padEnd(12)}${cost} ${row.capability} (${row.id})`;
42
+ if (row.note) text += ` — ${row.note}`;
43
+ text += '\n';
44
+ }
45
+ return text;
46
+ }
@@ -0,0 +1,27 @@
1
+ // Generated by scripts/preset-profiles.mjs from crates/harness/src/presets.rs.
2
+ export const PRESET_PROFILES = Object.freeze([
3
+ {
4
+ "name": "cc-parity",
5
+ "model": "anthropic/claude-opus-4-8"
6
+ },
7
+ {
8
+ "name": "cx-parity",
9
+ "model": null
10
+ },
11
+ {
12
+ "name": "oc-parity",
13
+ "model": null
14
+ },
15
+ {
16
+ "name": "pi-core",
17
+ "model": null
18
+ },
19
+ {
20
+ "name": "supercode-default",
21
+ "model": null
22
+ },
23
+ {
24
+ "name": "token-saver",
25
+ "model": null
26
+ }
27
+ ]);
@@ -0,0 +1,32 @@
1
+ // Profile commands run directly with null stdin, in the caller's selected
2
+ // environment. Only the child created here is stopped when that caller closes.
3
+ import { spawn } from 'node:child_process';
4
+ import { nativeTrim } from './text.mjs';
5
+
6
+ export const profileCommandFailure = message => { throw Object.assign(new Error(message), { rpcCode: -32000 }); };
7
+ export const profileShellQuote = value => value && /^[A-Za-z0-9\-_./:@=+,]+$/u.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
8
+ export const profileCommandStatus = output => output.signal ? `signal: ${output.signal}` : process.platform === 'win32' ? `exit code: ${output.code}` : `exit status: ${output.code}`;
9
+ export async function runProfileCommand(command, context) {
10
+ const closed = () => profileCommandFailure(`${command.owner ?? 'profile'} client closed before the harness command answered`);
11
+ if (context.signal?.aborted) closed();
12
+ const output = await new Promise((resolve, reject) => {
13
+ let child, force; const stdout = [], stderr = [];
14
+ try { child = spawn(command.program, command.args, { cwd: context.cwd, env: command.env ?? context.env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }); }
15
+ catch (error) { reject(error); return; }
16
+ const abort = () => { child.kill('SIGTERM'); force = setTimeout(() => child.kill('SIGKILL'), 500); };
17
+ child.stdout.on('data', data => stdout.push(data)); child.stderr.on('data', data => stderr.push(data));
18
+ child.once('error', reject);
19
+ child.once('close', (code, signal) => {
20
+ clearTimeout(force); context.signal?.removeEventListener('abort', abort);
21
+ resolve({ code, signal, stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8') });
22
+ });
23
+ context.signal?.addEventListener('abort', abort, { once: true }); if (context.signal?.aborted) abort();
24
+ }).catch(error => profileCommandFailure(`\`${command.ran}\` could not be executed: ${error.message}`));
25
+ if (context.signal?.aborted) closed();
26
+ return output;
27
+ }
28
+ export function requireProfileCommandSuccess(command, output) {
29
+ if (output.code === 0) return;
30
+ const detail = nativeTrim(output.stderr) || nativeTrim(output.stdout) || 'the harness printed nothing';
31
+ profileCommandFailure(`\`${command.ran}\` failed (${profileCommandStatus(output)}): ${detail}`);
32
+ }
@@ -0,0 +1,104 @@
1
+ // The orchestrator package owns profile writes. This client selects its live
2
+ // socket or cold operator entry; it never edits a profile folder itself.
3
+ import { readFileSync, statSync } from 'node:fs';
4
+ import { connect } from 'node:net';
5
+ import { hostname } from 'node:os';
6
+ import { dirname, basename, isAbsolute, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { createRequire } from 'node:module';
9
+ import { parseNativeJson, nativeInteger, nativeDuplicateObjectKeys, stringifyNativeJson } from './native-json.mjs';
10
+ import { profilePathJoin } from './profile-paths.mjs';
11
+ import { nativeTrim } from './text.mjs';
12
+ import { profileCommandFailure as failed, profileShellQuote as quote, runProfileCommand, profileCommandStatus } from './profile-command.mjs';
13
+
14
+ const utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
15
+ const actual = (path, cwd) => !path.length || isAbsolute(path) || process.platform === 'win32' && /^[A-Za-z]:/u.test(path) ? path : profilePathJoin(cwd, path);
16
+ const file = path => { try { return statSync(path).isFile(); } catch { return false; } };
17
+ const exists = path => { try { statSync(path); return true; } catch { return false; } };
18
+ function liveLease(root, context) {
19
+ let lease;
20
+ try { lease = parseNativeJson(utf8.decode(readFileSync(actual(profilePathJoin(root, 'orchestrator.lock'), context.cwd)))); } catch { return false; }
21
+ // Lease is read as a typed struct, unlike the RPC Value followed by a struct.
22
+ const fields = ['pid', 'started_at', 'root', 'host', 'boot'];
23
+ if (Array.isArray(lease)) {
24
+ if (lease.length < 3 || lease.length > fields.length) return false;
25
+ const pid = nativeInteger(lease, '0'); lease = Object.fromEntries(lease.map((value, at) => [fields[at], value])); lease.pid = pid;
26
+ } else {
27
+ if (lease == null || typeof lease !== 'object' || nativeDuplicateObjectKeys(lease).some(key => fields.includes(key))) return false;
28
+ lease = { ...lease, pid: nativeInteger(lease, 'pid') };
29
+ }
30
+ if (lease.pid == null || lease.pid < 0n || lease.pid > 0xffffffffn || typeof lease.started_at !== 'string' || typeof lease.root !== 'string'
31
+ || lease.host != null && typeof lease.host !== 'string' || lease.boot != null && typeof lease.boot !== 'string') return false;
32
+ const host = process.platform === 'win32' ? context.env.COMPUTERNAME ?? null : hostname() || null;
33
+ if (lease.host != null && lease.host !== host) return false;
34
+ let boot; try { boot = nativeTrim(utf8.decode(readFileSync('/proc/sys/kernel/random/boot_id'))); } catch { /* no boot identity on this platform */ }
35
+ if (lease.boot != null && boot != null && lease.boot !== boot) return false;
36
+ if (process.platform === 'win32') return true;
37
+ if (lease.pid === 0n) return false;
38
+ const pid = Number(lease.pid); try { process.kill(pid > 0x7fffffff ? pid - 0x100000000 : pid, 0); return true; } catch { return false; }
39
+ }
40
+ function interpret(ran, door, line) {
41
+ let value; try { value = parseNativeJson(line); }
42
+ catch (error) { throw Object.assign(new Error(`\`${ran}\` answered something that is not JSON: ${error.message}`), { rpcCode: -32000, brokenDoor: true }); }
43
+ if (value?.ok === true) return { ran, door, result: value.result ?? null };
44
+ throw Object.assign(new Error(typeof value?.error === 'string' ? value.error : `\`${ran}\` answered \`${line}\``), { rpcCode: -32000, refusedDoor: true });
45
+ }
46
+ function live(socketPath, op, args, profile, context) {
47
+ const ran = `${socketPath} ${op} --profile ${profile} --json ${quote(stringifyNativeJson(args))}`;
48
+ return new Promise((resolve, reject) => {
49
+ let socket, settled = false; const buffers = [];
50
+ const finish = (error, answer) => {
51
+ if (settled) return; settled = true; context.signal?.removeEventListener('abort', abort); socket?.destroy(); error ? reject(error) : resolve(answer);
52
+ };
53
+ const abort = () => finish(Object.assign(new Error(`${context.owner ?? 'profile'} client closed before the orchestrator answered`), { rpcCode: -32000, closedDoor: true }));
54
+ if (context.signal?.aborted) { abort(); return; }
55
+ try { socket = connect(actual(socketPath, context.cwd)); }
56
+ catch (error) { finish(error); return; }
57
+ context.signal?.addEventListener('abort', abort, { once: true }); if (context.signal?.aborted) { abort(); return; }
58
+ const answer = line => { try { finish(null, interpret(ran, 'live', nativeTrim(line))); } catch (error) { finish(error); } };
59
+ const read = bytes => { try { answer(utf8.decode(bytes)); } catch (error) { finish(error); } };
60
+ socket.once('connect', () => socket.write(`${stringifyNativeJson({ op, args, profile })}\n`));
61
+ socket.on('data', data => {
62
+ buffers.push(data); const bytes = Buffer.concat(buffers), newline = bytes.indexOf(10);
63
+ if (newline >= 0) read(bytes.subarray(0, newline));
64
+ });
65
+ socket.once('error', error => finish(error));
66
+ socket.once('end', () => {
67
+ let line; try { line = utf8.decode(Buffer.concat(buffers)); } catch (error) { finish(error); return; }
68
+ if (nativeTrim(line)) answer(line); else finish(new Error(`\`${ran}\`: the orchestrator closed the connection without answering`));
69
+ });
70
+ socket.once('close', () => { if (!settled) finish(new Error(`\`${ran}\`: the orchestrator closed the connection without answering`)); });
71
+ });
72
+ }
73
+ function entry(context) {
74
+ const searched = [], take = path => { if (file(actual(path, context.cwd))) return path; searched.push(path); return null; };
75
+ if (context.env.SUPERCODE_ORCHESTRATOR_ENTRY != null) {
76
+ const found = take(context.env.SUPERCODE_ORCHESTRATOR_ENTRY); if (found != null) return found;
77
+ }
78
+ const roots = []; let root = dirname(fileURLToPath(import.meta.url));
79
+ for (;;) { roots.push(root); const parent = dirname(root); if (parent === root) break; root = parent; }
80
+ for (const parent of roots) { const found = take(join(parent, 'sdk', 'orchestrator', 'bin', 'orchestrator.mjs')); if (found != null) return found; }
81
+ for (const parent of roots) if (basename(parent) === 'node_modules') {
82
+ const found = take(join(parent, '@volter', 'supercode-orchestrator', 'bin', 'orchestrator.mjs')); if (found != null) return found;
83
+ }
84
+ try {
85
+ const resolved = createRequire(import.meta.url).resolve('@volter/supercode-orchestrator/package.json');
86
+ const found = take(join(dirname(resolved), 'bin', 'orchestrator.mjs')); if (found != null) return found;
87
+ } catch { /* package without an exported manifest is covered by module ancestry */ }
88
+ failed(`the orchestrator's write door is its own package, and it could not be located: the orchestrator daemon entry was not found (searched: ${searched.join(', ')})`);
89
+ }
90
+ export async function callProfileOrchestrator(root, op, args, context, profile = 'default') {
91
+ const socketPath = context.env.SUPERCODE_ORCHESTRATOR_SOCK || profilePathJoin(root, 'orchestrator.sock');
92
+ if (process.platform !== 'win32' && liveLease(root, context) && exists(actual(socketPath, context.cwd))) {
93
+ try { return await live(socketPath, op, args, profile, context); }
94
+ catch (error) { if (error.refusedDoor || error.closedDoor || context.signal?.aborted) throw error; /* broken socket uses the package's cold door */ }
95
+ }
96
+ const program = nativeTrim(context.env.SUPERCODE_NODE_BIN ?? '') || 'node';
97
+ const argv = [entry(context), op, '--root', root, '--profile', profile, '--json', stringifyNativeJson(args)];
98
+ const ran = [program, ...argv].map(quote).join(' '), output = await runProfileCommand({ program, args: argv, ran, owner: context.owner }, context);
99
+ // The package's last nonempty JSON line is authoritative even on a nonzero
100
+ // exit, just as in the replaced operator client.
101
+ const line = output.stdout.split('\n').reverse().find(line => nativeTrim(line));
102
+ if (line === undefined) failed(`\`${ran}\` printed nothing (${profileCommandStatus(output)})${nativeTrim(output.stderr) ? `: ${nativeTrim(output.stderr)}` : ''}`);
103
+ return interpret(ran, 'cold', nativeTrim(line));
104
+ }
@@ -0,0 +1,25 @@
1
+ // PathBuf's lexical join/parent rules. Reads retain spelling and '..'; they
2
+ // never canonicalize a profile name into a different selected home.
3
+ import { childPath } from './session-spend-files.mjs';
4
+ const prefixOf = path => process.platform === 'win32' ? /^(?:\\\\[?.]\\(?:UNC\\[^\\]+\\[^\\]+|[A-Za-z]:|[^\\]+)|\\\\[^\\/]+[\\/][^\\/]+|[A-Za-z]:)/iu.exec(path)?.[0] ?? '' : '';
5
+ const separator = char => char === '/' || process.platform === 'win32' && char === '\\';
6
+ export function profilePathJoin(root, name) {
7
+ const parent = Buffer.isBuffer(root) ? root.toString('utf8') : root, child = Buffer.isBuffer(name) ? name.toString('utf8') : name;
8
+ if (process.platform === 'win32' && prefixOf(child)) return child;
9
+ if (separator(child[0])) return process.platform === 'win32' ? prefixOf(parent) + child : child;
10
+ return childPath(root, name).toString('utf8');
11
+ }
12
+ export function profilePathParent(path) {
13
+ const prefix = prefixOf(path).length, rooted = separator(path[prefix]), minimum = prefix + Number(rooted);
14
+ let end = path.length;
15
+ while (end > minimum && separator(path[end - 1])) end--;
16
+ for (;;) {
17
+ if (end <= minimum) return null;
18
+ let begin = end; while (begin > prefix && !separator(path[begin - 1])) begin--;
19
+ if (path.slice(begin, end) !== '.' || begin === prefix) {
20
+ end = begin; while (end > minimum && separator(path[end - 1])) end--;
21
+ return path.slice(0, end);
22
+ }
23
+ end = begin; while (end > minimum && separator(path[end - 1])) end--;
24
+ }
25
+ }
@@ -0,0 +1,98 @@
1
+ // Controlled profiles belong to their harness's CLI/operator package. The
2
+ // existing profile reader establishes the outcome after that command answers.
3
+ import { resolveHarnessHomes } from './homes.mjs';
4
+ import { listProfiles, PROFILE_HARNESSES } from './profiles-read.mjs';
5
+ import { profilePathJoin, profilePathParent } from './profile-paths.mjs';
6
+ import { nativeTrim } from './text.mjs';
7
+ import { supportRegistryReport } from './support-report.mjs';
8
+ import { callProfileOrchestrator } from './profile-orchestrator-door.mjs';
9
+ import { profileCommandFailure as failed, profileShellQuote as quote, runProfileCommand, requireProfileCommandSuccess } from './profile-command.mjs';
10
+
11
+ export const CONTROLLED_PROFILE_HARNESSES = Object.freeze(['hermes', 'openclaw', 'orchestrator']);
12
+ const invalid = message => { throw Object.assign(new Error(message), { rpcCode: -32602 }); };
13
+ const unsupported = message => { throw Object.assign(new Error(message), { rpcCode: -32020 }); };
14
+ const display = path => Buffer.isBuffer(path) ? path.toString('utf8') : path;
15
+ function struct(value, fields, name) {
16
+ if (Array.isArray(value)) {
17
+ if (value.length > fields.length) invalid(`${name} has too many fields`);
18
+ return Object.fromEntries(value.map((entry, at) => [fields[at], entry]));
19
+ }
20
+ if (value == null || typeof value !== 'object') invalid(`${name} must be a struct`);
21
+ return value;
22
+ }
23
+ function mutationParams(params, context) {
24
+ const value = struct(params, ['harness', 'name', 'from', 'workspace', 'homes'], 'profile mutation');
25
+ for (const key of ['harness', 'name']) if (typeof value[key] !== 'string') invalid(`${key} must be a string`);
26
+ if (Object.hasOwn(value, 'from') && Object.hasOwn(value, 'template')) invalid('duplicate field `from`');
27
+ const from = Object.hasOwn(value, 'from') ? value.from : value.template;
28
+ if (from != null && typeof from !== 'string') invalid('from must be a string');
29
+ if (value.workspace != null && typeof value.workspace !== 'string') invalid('workspace must be a string');
30
+ const homes = Object.fromEntries(Object.entries(resolveHarnessHomes(context.env)).map(([key, path]) => [key, display(path)]));
31
+ if (Object.hasOwn(value, 'homes')) {
32
+ const supplied = struct(value.homes, Object.keys(homes), 'homes');
33
+ for (const key of Object.keys(homes)) if (Object.hasOwn(supplied, key)) {
34
+ if (typeof supplied[key] !== 'string') invalid(`homes.${key} must be a string`);
35
+ homes[key] = supplied[key];
36
+ }
37
+ }
38
+ return { ...value, from, homes };
39
+ }
40
+ function unsupportedHarness(harness) {
41
+ if (harness === 'codex') unsupported('codex profiles are file-authored: a profile IS a `[profiles.<name>]` table in `$CODEX_HOME/config.toml`, created by adding that table and deleted by removing it. Codex publishes no `codex profile create|delete` verb a client can call, so supercode names the door rather than editing another harness\'s config file behind its back');
42
+ if (harness === 'supercode') unsupported('a supercode preset is CODE — one of the compiled-in preset bundles, not a config home a verb can make or remove. supercode publishes no preset create/delete verb, so the profile noun refuses rather than inventing one');
43
+ unsupported(`harness \`${harness}\` has no profile concept (profiles exist for: ${PROFILE_HARNESSES.join(', ')})`);
44
+ }
45
+ function harnessProgram(harness, context) {
46
+ const override = nativeTrim(context.env[harness === 'hermes' ? 'SUPERCODE_HERMES_BIN' : 'SUPERCODE_OPENCLAW_BIN'] ?? '');
47
+ if (override) return override;
48
+ const program = supportRegistryReport(context).harnesses.find(row => row.id === harness)?.runtime?.default_launch?.program;
49
+ if (program == null) unsupported(`the registry has no launch for \`${harness}\`, so its CLI cannot be located`);
50
+ return program.replace(/-acp$/u, '');
51
+ }
52
+ function command(verb, mutation, name, context) {
53
+ const env = { ...context.env }, args = []; let program;
54
+ if (mutation.harness === 'hermes') {
55
+ if (mutation.workspace != null) unsupported('a hermes profile IS its own home (`HERMES_HOME/profiles/<name>`); `hermes profile create` has no workspace flag, so Volter Harness refuses rather than dropping the field');
56
+ program = harnessProgram('hermes', context); env.HERMES_HOME = profilePathParent(mutation.homes.hermes) ?? '.'; args.push('profile');
57
+ if (verb === 'create') {
58
+ args.push('create'); const from = nativeTrim(mutation.from ?? ''); if (from) args.push('--clone-from', from); args.push('--no-alias', name);
59
+ } else args.push('delete', '--yes', name);
60
+ } else {
61
+ if (mutation.from != null) unsupported('`openclaw agents add` takes a workspace, a model and bindings; it has no clone / template source, so Volter Harness refuses rather than dropping `from`');
62
+ program = harnessProgram('openclaw', context); env.OPENCLAW_STATE_DIR = mutation.homes.openclaw; env.OPENCLAW_CONFIG_PATH = profilePathJoin(mutation.homes.openclaw, 'openclaw.json'); args.push('agents');
63
+ if (verb === 'create') {
64
+ const workspace = nativeTrim(mutation.workspace ?? '');
65
+ if (!workspace) invalid('`openclaw agents add` requires the new agent\'s workspace directory in non-interactive mode (its own message: "Non-interactive agent creation requires --workspace"), so `profiles.create --harness openclaw` needs `workspace`');
66
+ args.push('add', name, '--workspace', workspace, '--non-interactive', '--json');
67
+ } else args.push('delete', name, '--force', '--json');
68
+ }
69
+ return { program, args, env, ran: [program, ...args].map(quote).join(' ') };
70
+ }
71
+ function readBack(mutation, name, ran, context) {
72
+ let rows; try { rows = listProfiles({ harness: mutation.harness, homes: mutation.homes }, context).profiles; }
73
+ catch (error) { failed(`\`${ran}\` succeeded but the profile store could not be re-read: ${error.message}`); }
74
+ const asciiLower = text => text.replace(/[A-Z]/gu, char => char.toLowerCase());
75
+ return rows.find(row => row.name === name) ?? rows.find(row => asciiLower(row.name) === asciiLower(name));
76
+ }
77
+ export async function mutateProfile(verb, params, { env = process.env, cwd = process.cwd(), signal } = {}) {
78
+ const context = { env, cwd, signal }, mutation = mutationParams(params, context);
79
+ if (!CONTROLLED_PROFILE_HARNESSES.includes(mutation.harness)) unsupportedHarness(mutation.harness);
80
+ if (!['create', 'delete'].includes(verb)) invalid(`unknown profile mutation: ${verb}`);
81
+ const name = nativeTrim(mutation.name); if (!name) invalid(`\`profiles.${verb}\` needs the profile name to act on`);
82
+ if (verb === 'delete' && (mutation.from != null || mutation.workspace != null)) invalid('`profiles.delete` sets no fields; pass definition fields to `profiles.create`');
83
+ let ran;
84
+ if (mutation.harness === 'orchestrator') {
85
+ if (mutation.from != null) unsupported('an orchestrator profile is a FOLDER the package\'s `save()` writes from an empty record (`docs/ORCHESTRATOR-IR.md` §6); the operator door has no clone verb, so Volter Harness refuses rather than dropping `from`');
86
+ if (mutation.workspace != null) unsupported('an orchestrator profile IS its own home (`<root>/profiles/<name>`), and where its worker runs is the `worker.cwd` key inside that folder\'s `config.yaml`, not a creation argument; Volter Harness refuses rather than dropping `workspace`');
87
+ const answer = await callProfileOrchestrator(mutation.homes.orchestrator, `profiles.${verb}`, { name }, context); ran = `${answer.ran} [${answer.door}]`;
88
+ } else {
89
+ const request = command(verb, mutation, name, context); requireProfileCommandSuccess(request, await runProfileCommand(request, context)); ran = request.ran;
90
+ }
91
+ const profile = readBack(mutation, name, ran, context);
92
+ if (verb === 'delete') {
93
+ if (profile) failed(`\`${ran}\` reported success but \`${name}\` is still ${mutation.harness === 'orchestrator' ? 'an orchestrator' : `a ${mutation.harness}`} profile`);
94
+ return { harness: mutation.harness, verb, ran, name, deleted: true };
95
+ }
96
+ if (!profile) failed(`\`${ran}\` reported success but ${mutation.harness === 'orchestrator' ? 'the orchestrator' : `\`${mutation.harness}\``} has no profile \`${name}\` afterwards`);
97
+ return { harness: mutation.harness, verb, ran, name: profile.name, profile };
98
+ }
@@ -0,0 +1,119 @@
1
+ // The read-only profile noun. A missing/invalid home contributes no rows;
2
+ // absent route and session stores remain unknown rather than becoming zero.
3
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
4
+ import { isAbsolute } from 'node:path';
5
+ import { resolveHarnessHomes } from './homes.mjs';
6
+ import { parseSkillToml } from './skill-toml.mjs';
7
+ import { childPath, textName } from './session-spend-files.mjs';
8
+ import { profilePathJoin as joined, profilePathParent as parent } from './profile-paths.mjs';
9
+ import { readProfileFolderHome } from './hermes-read-home.mjs';
10
+ import { readOpenclawProfileHome } from './openclaw-profile-home.mjs';
11
+ import { readHermesRows } from './hermes-read-store.mjs';
12
+ import { nativeInteger, setNativeInteger } from './native-json.mjs';
13
+ import { PRESET_PROFILES } from './preset-profiles.mjs';
14
+
15
+ export const PROFILE_HARNESSES = Object.freeze(['supercode', 'codex', 'hermes', 'openclaw', 'orchestrator']);
16
+ const schema = 'supercode.profiles.v1';
17
+ const object = value => value != null && typeof value === 'object' && !Array.isArray(value);
18
+ const string = value => typeof value === 'string' ? value : null;
19
+ const utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
20
+ const compare = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b));
21
+ const display = path => Buffer.isBuffer(path) ? path.toString('utf8') : path;
22
+ const directory = path => { try { return statSync(path).isDirectory(); } catch { return false; } };
23
+ const file = path => { try { return statSync(path).isFile(); } catch { return false; } };
24
+ const invalid = message => { throw Object.assign(new Error(message), { rpcCode: -32602 }); };
25
+ function struct(value, fields, name) {
26
+ if (Array.isArray(value)) {
27
+ if (value.length > fields.length) invalid(`${name} has too many fields`);
28
+ return Object.fromEntries(value.map((member, at) => [fields[at], member]));
29
+ }
30
+ if (!object(value)) invalid(`${name} must be a struct`);
31
+ return value;
32
+ }
33
+ function query(params, context) {
34
+ const query = struct(params, ['harness', 'name', 'homes'], 'profiles params');
35
+ for (const key of ['harness', 'name']) if (query[key] != null && typeof query[key] !== 'string') invalid(`${key} must be a string`);
36
+ const homes = resolveHarnessHomes(context.env);
37
+ if (Object.hasOwn(query, 'homes')) {
38
+ const supplied = struct(query.homes, Object.keys(homes), 'homes');
39
+ for (const key of Object.keys(homes)) if (Object.hasOwn(supplied, key)) {
40
+ if (typeof supplied[key] !== 'string') invalid(`homes.${key} must be a string`);
41
+ homes[key] = supplied[key];
42
+ }
43
+ }
44
+ return { ...query, homes };
45
+ }
46
+ const filesystem = (path, cwd) => !path.length || isAbsolute(path) || process.platform === 'win32' && /^[A-Za-z]:/u.test(path) ? path : joined(cwd, path);
47
+ function row(harness, kind, name, home = null, defaults = false, model = null) {
48
+ return { name, harness, kind, home, default: defaults, routes: null, sessions: null, model };
49
+ }
50
+ function codexRows(path, cwd) {
51
+ const home = parent(path); if (home == null) return [];
52
+ let doc; try { doc = parseSkillToml(utf8.decode(readFileSync(filesystem(joined(home, 'config.toml'), cwd)))); } catch { return []; }
53
+ if (!object(doc.profiles) || doc.profiles instanceof Date) return [];
54
+ return Object.keys(doc.profiles).sort(compare).map(name => row('codex', 'codex_profile', name, null, doc.profile === name, string(doc.profiles[name]?.model)));
55
+ }
56
+ function hermesCount(path, profile) {
57
+ try {
58
+ const rows = readHermesRows(path, `SELECT COUNT(*) AS count FROM sessions WHERE profile_name ${profile == null ? 'IS NULL' : '= ?'}`, profile == null ? [] : [profile]);
59
+ const count = rows?.[0] && nativeInteger(rows[0], 'count', { signed: true });
60
+ return count == null ? null : count < 0n ? 0n : count;
61
+ } catch { return null; }
62
+ }
63
+ function folderRows(harness, supplied, cwd) {
64
+ const home = harness === 'hermes' ? parent(supplied) : supplied; if (home == null) return [];
65
+ let profiles; try { profiles = readProfileFolderHome(filesystem(home, cwd), harness); } catch { return []; }
66
+ const routesKnown = harness === 'hermes' ? profiles.get('default').configPresent : [...profiles.values()].some(profile => profile.configPresent);
67
+ const counts = new Map();
68
+ for (const profile of profiles.values()) for (const route of profile.routes) { const name = route.agent ?? route.profile; counts.set(name, (counts.get(name) ?? 0n) + 1n); }
69
+ return [...profiles.keys()].sort((a, b) => Number(a !== 'default') - Number(b !== 'default') || compare(a, b)).map(name => {
70
+ const profile = profiles.get(name), defaultProfile = name === 'default';
71
+ const result = row(harness, `${harness}_profile`, name, defaultProfile ? home : joined(joined(home, 'profiles'), name), defaultProfile, profile.model);
72
+ if (routesKnown) setNativeInteger(result, 'routes', counts.get(name) ?? 0n);
73
+ const sessions = harness === 'hermes' ? hermesCount(filesystem(supplied, cwd), defaultProfile ? null : name) : file(profile.store) ? BigInt(profile.bindings) : null;
74
+ if (sessions != null) setNativeInteger(result, 'sessions', sessions);
75
+ if (harness === 'orchestrator' && profile.worker != null) result.worker = profile.worker;
76
+ return result;
77
+ });
78
+ }
79
+ function openclawRows(home, cwd) {
80
+ const root = filesystem(home, cwd); let loaded;
81
+ try { loaded = readOpenclawProfileHome(root); } catch { return []; }
82
+ const names = new Set(loaded.byAgent.keys());
83
+ try {
84
+ const agents = childPath(root, 'agents');
85
+ for (const entry of readdirSync(agents, { encoding: 'buffer' })) {
86
+ const path = childPath(agents, entry); if (!directory(path)) continue;
87
+ try { if (!readdirSync(path, { encoding: 'buffer' }).length) continue; } catch { /* unread does not establish empty */ }
88
+ const name = textName(entry); if (name != null) names.add(name);
89
+ }
90
+ } catch { /* unavailable directory adds no undeclared names */ }
91
+ return [...names].sort(compare).map(name => {
92
+ const result = row('openclaw', 'openclaw_agent', name, joined(joined(home, 'agents'), name), loaded.defaultAgent === name, loaded.byAgent.get(name)?.model ?? null);
93
+ if (loaded.routes != null) setNativeInteger(result, 'routes', loaded.routes.get(name) ?? 0n);
94
+ try { setNativeInteger(result, 'sessions', BigInt(readdirSync(joined(joined(joined(root, 'agents'), name), 'sessions'), { encoding: 'buffer' }).filter(entry => entry.toString('utf8').endsWith('.jsonl') && !entry.toString('utf8').endsWith('.trajectory.jsonl')).length)); } catch { /* unknown session count */ }
95
+ return result;
96
+ });
97
+ }
98
+ function rows(query, context) {
99
+ if (query.harness != null && !PROFILE_HARNESSES.includes(query.harness)) throw Object.assign(new Error(`harness \`${query.harness}\` has no profile concept (profiles exist for: ${PROFILE_HARNESSES.join(', ')})`), { rpcCode: -32020 });
100
+ return PROFILE_HARNESSES.flatMap(harness => {
101
+ if (query.harness != null && query.harness !== harness) return [];
102
+ if (harness === 'supercode') return PRESET_PROFILES.map(preset => row(harness, 'preset', preset.name, null, preset.name === 'supercode-default', preset.model));
103
+ if (harness === 'codex') return codexRows(query.homes.codex, context.cwd);
104
+ if (harness === 'openclaw') return openclawRows(query.homes.openclaw, context.cwd);
105
+ return folderRows(harness, query.homes[harness], context.cwd);
106
+ });
107
+ }
108
+ export function listProfiles(params, { env = process.env, cwd = process.cwd() } = {}) {
109
+ const context = { env, cwd }, selected = query(params, context);
110
+ return { schema, profiles: rows(selected, context) };
111
+ }
112
+ export function getProfile(params, { env = process.env, cwd = process.cwd() } = {}) {
113
+ const context = { env, cwd }, selected = query(params, context);
114
+ if (selected.harness == null) invalid('`harness` is required');
115
+ if (selected.name == null) invalid('`name` is required');
116
+ const profile = rows(selected, context).find(row => row.name === selected.name);
117
+ if (profile == null) invalid(`\`${selected.harness}\` has no profile \`${selected.name}\``);
118
+ return { schema, profile };
119
+ }
@@ -0,0 +1,54 @@
1
+ // Offline fallback for old logs that did not persist attribution. Runtime
2
+ // policy/candidate generation remains with its own consumer; this uses only
3
+ // the validated saved records, in the native pipeline's fixed pass order.
4
+ import { cloneNativeJson, nativeInteger, setNativeInteger, stringifyNativeJson } from './native-json.mjs';
5
+ import { estimateViewTokens, messageWireText } from './reduction-log.mjs';
6
+ import { reapplyReductionLog } from './reduction-view.mjs';
7
+
8
+ const max = (1n << 64n) - 1n, add = (a, b) => a + b > max ? max : a + b;
9
+ const sub = (a, b) => a > b ? a - b : 0n;
10
+ const bytes = messages => messages.reduce((sum, message) => (sum + BigInt(Buffer.byteLength(messageWireText(message)))) & max, 0n);
11
+ const kinds = [['DuplicateOutput', 'duplicate'], ['Superseded', 'superseded'], ['OutputNormalized', 'output-normalized'],
12
+ ['ToolOutputTruncated', 'tool-output'], ['FileReadElided', 'file-read'], ['FileReadDiffed', 'file-read-diffed'],
13
+ ['ImageRedacted', 'image'], ['ToolInputElided', 'tool-input'], ['TurnsCleared', 'turns-cleared']];
14
+ function recordBytes(record, messages) {
15
+ const type = Object.keys(record.kind)[0], data = record.kind[type];
16
+ if (Object.hasOwn(data, 'original_bytes')) return nativeInteger(data, 'original_bytes');
17
+ if (type === 'TurnsCleared') {
18
+ const first = nativeInteger(data, 'first'), last = nativeInteger(data, 'last');
19
+ return first <= last && last < BigInt(messages.length) ? bytes(messages.slice(Number(first), Number(last) + 1)) : 0n;
20
+ }
21
+ const message = messages[Number(nativeInteger(record.ptr.addr, 'index'))];
22
+ if (type === 'FileReadElided') return BigInt(Buffer.byteLength(message?.content ?? ''));
23
+ const part = message?.content_parts?.[Number(nativeInteger(data, 'part_index'))];
24
+ return part == null ? 0n : BigInt(Buffer.byteLength(stringifyNativeJson(part)));
25
+ }
26
+ function counts(target, values) { for (const [key, value] of Object.entries(values)) setNativeInteger(target, key, value); return target; }
27
+ export function savedReductionAttribution(log, messages) {
28
+ if (log.attribution != null) return cloneNativeJson(log.attribution);
29
+ const fullBytes = bytes(messages), fullTokens = estimateViewTokens(messages), passes = [], cumulative = [];
30
+ let retainedBytes = fullBytes, retainedTokens = fullTokens, totalBytes = 0n, totalTokens = 0n;
31
+ const view = records => reapplyReductionLog(messages, { ...log, reductions: records, expanded: [] });
32
+ for (const [variant, kind] of kinds) {
33
+ const reductions = log.reductions.filter(record => Object.hasOwn(record.kind, variant));
34
+ const originalBytes = reductions.reduce((sum, record) => (sum + recordBytes(record, messages)) & max, 0n);
35
+ const standalone = view(reductions), savedBytes = sub(fullBytes, bytes(standalone)), savedTokens = sub(fullTokens, estimateViewTokens(standalone));
36
+ totalBytes = add(totalBytes, savedBytes); totalTokens = add(totalTokens, savedTokens);
37
+ cumulative.push(...reductions);
38
+ const after = view(cumulative), nextBytes = bytes(after), nextTokens = estimateViewTokens(after);
39
+ const marginalBytes = sub(retainedBytes, nextBytes), marginalTokens = sub(retainedTokens, nextTokens);
40
+ passes.push(counts({ kind, enabled: reductions.length > 0 }, {
41
+ candidate_count: BigInt(reductions.length), candidate_original_bytes: originalBytes, applied_count: BigInt(reductions.length),
42
+ applied_original_bytes: originalBytes, suppressed_by_later_pass_count: 0n, suppressed_by_later_pass_bytes: 0n,
43
+ standalone_saved_bytes: savedBytes, standalone_saved_tokens: savedTokens, marginal_saved_bytes: marginalBytes,
44
+ marginal_saved_tokens: marginalTokens, suppressed_bytes: sub(savedBytes, marginalBytes), suppressed_tokens: sub(savedTokens, marginalTokens),
45
+ retained_bytes: nextBytes, retained_tokens: nextTokens,
46
+ }));
47
+ retainedBytes = nextBytes; retainedTokens = nextTokens;
48
+ }
49
+ const final = view(log.reductions), viewBytes = bytes(final), viewTokens = estimateViewTokens(final);
50
+ const aggregateBytes = sub(fullBytes, viewBytes), aggregateTokens = sub(fullTokens, viewTokens);
51
+ return counts({ passes }, { full_bytes: fullBytes, view_bytes: viewBytes, full_tokens: fullTokens, view_tokens: viewTokens,
52
+ aggregate_saved_bytes: aggregateBytes, aggregate_saved_tokens: aggregateTokens, standalone_saved_bytes: totalBytes,
53
+ standalone_saved_tokens: totalTokens, overlap_suppressed_bytes: sub(totalBytes, aggregateBytes), overlap_suppressed_tokens: sub(totalTokens, aggregateTokens) });
54
+ }