@matuto-ai/opencode-broker-connector 0.0.0-stage → 0.1.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.
@@ -0,0 +1,105 @@
1
+ import { ACK_DEFINITION } from './tool-catalog.mjs';
2
+ import { mkdtempSync, realpathSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { createOpenCodeAdapter, nativeShape } from './opencode-adapter.mjs';
6
+ import { privateDirectory } from './config.mjs';
7
+
8
+ const validId = value => typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,256}$/.test(value);
9
+ /** Native, session-keyed cooperative tools. Does not attach to or control a TUI. */
10
+ export async function createNativePlugin({ native, tool, reference, config, clientFactory,
11
+ adapterFactory = createOpenCodeAdapter }) {
12
+ const directory = realpathSync(native.directory), worktree = realpathSync(native.worktree);
13
+ const runDirectory = mkdtempSync(join(privateDirectory(config.stateDirectory), 'opencode-'));
14
+ const sessions = new Map();
15
+ let disposed = false;
16
+ async function sessionInfo(id, signal) {
17
+ if (!validId(id)) throw new reference.LocalError('session_mismatch');
18
+ const bounded = AbortSignal.any([signal, AbortSignal.timeout(2000)]);
19
+ let listener;
20
+ const result = await Promise.race([
21
+ Promise.resolve().then(() => native.client.session.get({ path: { id } }, { signal: bounded })),
22
+ new Promise((_, reject) => {
23
+ listener = () => reject(new reference.LocalError('session_mismatch'));
24
+ if (bounded.aborted) listener(); else bounded.addEventListener('abort', listener, { once: true });
25
+ }),
26
+ ]).finally(() => bounded.removeEventListener('abort', listener));
27
+ if (disposed || bounded.aborted || result?.error || result?.data?.id !== id
28
+ || realpathSync(result.data.directory) !== directory) throw new reference.LocalError('session_mismatch');
29
+ return result.data;
30
+ }
31
+ async function close(id) {
32
+ const state = sessions.get(id);
33
+ if (!state) return;
34
+ state.controller.abort();
35
+ if (state.closing) return state.closing;
36
+ state.closing = (async () => {
37
+ await state.starting?.catch(() => {});
38
+ await state.adapter?.stop();
39
+ if (sessions.get(id) === state) sessions.delete(id);
40
+ })();
41
+ return state.closing;
42
+ }
43
+ async function getSession(id) {
44
+ if (disposed || !validId(id)) throw new reference.LocalError('session_mismatch');
45
+ let state = sessions.get(id);
46
+ if (state?.closing) { await state.closing; state = undefined; }
47
+ if (state) { await state.starting; return state; }
48
+ if (sessions.size >= 64) throw new reference.LocalError('owner_conflict');
49
+ state = { controller: new AbortController(), lifetime: Symbol('opencode plugin session'), adapter: null };
50
+ sessions.set(id, state);
51
+ state.starting = (async () => {
52
+ await sessionInfo(id, state.controller.signal);
53
+ if (state.controller.signal.aborted) throw new reference.LocalError('session_mismatch');
54
+ const identity = reference.newSessionIdentity('opencode');
55
+ const inbox = new reference.Inbox({ directory: join(runDirectory, createHash('sha256').update(id).digest('hex') + '-' + identity.session_instance_id), identity });
56
+ const binding = { sessionID: id, directory, worktree, lifetime: state.lifetime, signal: state.controller.signal };
57
+ const currentBinding = async () => {
58
+ if (disposed || state.controller.signal.aborted || sessions.get(id) !== state) return null;
59
+ try { await sessionInfo(id, state.controller.signal); } catch { return null; }
60
+ return !disposed && !state.controller.signal.aborted && sessions.get(id) === state ? binding : null;
61
+ };
62
+ try {
63
+ const client = clientFactory ? clientFactory(inbox) : new reference.BrokerClient({ ...config.broker, inbox });
64
+ state.adapter = adapterFactory({ reference, tool, client, binding, currentBinding, cooperativeAck: true });
65
+ await state.adapter.start({ name: config.name ?? 'OpenCode', model: { provider: null, id: null, source: 'unknown' }, project: null });
66
+ if (!await currentBinding()) throw new reference.LocalError('session_mismatch');
67
+ } catch (error) { if (state.adapter) await state.adapter.stop(); else inbox.close(); throw error; }
68
+ return state;
69
+ })();
70
+ try { return await state.starting; }
71
+ catch (error) { if (sessions.get(id) === state) sessions.delete(id); throw error; }
72
+ }
73
+ const catalog = [...reference.TOOL_DEFINITIONS, ACK_DEFINITION];
74
+ const proxies = Object.fromEntries(catalog.map(definition => [definition.name, tool({
75
+ description: definition.description,
76
+ args: nativeShape(tool.schema, definition.inputSchema),
77
+ async execute(args, context) {
78
+ try {
79
+ if (!context || !validId(context.messageID) || !(context.abort instanceof AbortSignal)
80
+ || context.abort.aborted || realpathSync(context.directory) !== directory || realpathSync(context.worktree) !== worktree) throw new reference.LocalError('session_mismatch');
81
+ const state = await getSession(context.sessionID);
82
+ return await state.adapter.tools[definition.name].execute(args, context);
83
+ } catch (error) { return JSON.stringify(reference.safeError(error)); }
84
+ },
85
+ })]));
86
+ async function dispose() {
87
+ if (disposed) return;
88
+ disposed = true;
89
+ await Promise.all([...sessions.keys()].map(close));
90
+ }
91
+ return {
92
+ tool: proxies,
93
+ async event({ event }) {
94
+ if (event.type === 'session.deleted') await close(event.properties?.info?.id);
95
+ if (event.type === 'server.instance.disposed') await dispose();
96
+ },
97
+ async 'chat.message'(input, output) {
98
+ if (output?.message?.role === 'user' && output.message.sessionID === input.sessionID) {
99
+ // A missing broker must never prevent the user's ordinary chat.
100
+ void getSession(input.sessionID).catch(() => {}); // Non-blocking warm-up; tools await their own binding.
101
+ }
102
+ },
103
+ dispose,
104
+ };
105
+ }
@@ -0,0 +1,189 @@
1
+ import { createCooperativeInbox } from './cooperative-inbox.mjs';
2
+ import { ACK_DEFINITION } from './tool-catalog.mjs';
3
+ import { isAbsolute, normalize } from 'node:path';
4
+
5
+ const TOOL_NAMES = ['broker_list_clients', 'broker_send', 'broker_update_metadata', 'broker_message_status', 'broker_receive'];
6
+ const localId = value => typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,256}$/.test(value);
7
+ const absolute = value => typeof value === 'string' && isAbsolute(value) && normalize(value) === value;
8
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
9
+
10
+ // Adapt the shared bounded tool catalog to OpenCode's documented Zod shape.
11
+ // This is a native tool schema adapter, not another broker wire contract.
12
+ function nativeSchema(z, rule) {
13
+ const nullableString = Array.isArray(rule.type) && rule.type.length === 2
14
+ && rule.type.includes('string') && rule.type.includes('null');
15
+ const allowed = rule.enum ? ['enum'] : rule.type === 'object'
16
+ ? ['type', 'properties', 'required', 'additionalProperties'] : rule.type === 'integer'
17
+ ? ['type', 'minimum', 'maximum'] : rule.type === 'boolean' ? ['type'] : ['type', 'maxLength', 'format'];
18
+ if (Object.keys(rule).some(key => !allowed.includes(key))
19
+ || Array.isArray(rule.type) && !nullableString
20
+ || rule.format !== undefined && rule.format !== 'uuid'
21
+ || rule.maxLength !== undefined && (!Number.isSafeInteger(rule.maxLength) || rule.maxLength < 1)) {
22
+ throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
23
+ }
24
+ let schema;
25
+ if (rule.enum) schema = z.enum(rule.enum);
26
+ else if (rule.type === 'object') schema = z.object(nativeShape(z, rule)).strict();
27
+ else if (rule.type === 'boolean') schema = z.boolean();
28
+ else if (rule.type === 'integer') {
29
+ schema = z.number().int().safe();
30
+ if (rule.minimum !== undefined) schema = schema.min(rule.minimum);
31
+ if (rule.maximum !== undefined) schema = schema.max(rule.maximum);
32
+ } else if (rule.type === 'string' || Array.isArray(rule.type) && rule.type.includes('string')) {
33
+ schema = z.string().refine(value => value.isWellFormed()
34
+ && (rule.maxLength === undefined || [...value].length <= rule.maxLength), 'Use bounded, well-formed text.');
35
+ if (rule.format === 'uuid') schema = schema.refine(value => uuid.test(value), 'Use an exact UUID address.');
36
+ if (Array.isArray(rule.type) && rule.type.includes('null')) schema = schema.nullable();
37
+ } else throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
38
+ return schema;
39
+ }
40
+ export function nativeShape(z, schema) {
41
+ if (schema.type !== 'object' || schema.additionalProperties !== false || !schema.properties
42
+ || !Array.isArray(schema.required) || schema.required.some(key => !Object.hasOwn(schema.properties, key))
43
+ || Object.keys(schema).some(key => !['type', 'properties', 'required', 'additionalProperties'].includes(key))) {
44
+ throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
45
+ }
46
+ return Object.fromEntries(Object.entries(schema.properties).map(([key, rule]) => {
47
+ const value = nativeSchema(z, rule);
48
+ return [key, schema.required?.includes(key) ? value : value.optional()];
49
+ }));
50
+ }
51
+ function displayJSON(value) {
52
+ const result = JSON.stringify(value).replace(/[\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu,
53
+ char => `\\u${char.codePointAt(0).toString(16).padStart(4, '0')}`);
54
+ if (Buffer.byteLength(result) > 512 * 1024) throw new Error('bounded_result');
55
+ return result;
56
+ }
57
+
58
+ /**
59
+ * Cooperative native tools for ONE verified OpenCode host-session lifetime.
60
+ * The trusted host owner supplies currentBinding() and aborts binding.signal on
61
+ * exact host close. Session IDs, a server process, and an idle observation alone
62
+ * are insufficient lifetime proof. No automatic plugin attachment is implied.
63
+ */
64
+ export function createOpenCodeAdapter({ reference, tool, binding, currentBinding,
65
+ client, inbox = client?.inbox, observeMetadata = null, onDiagnostic = () => {}, cooperativeAck = false }) {
66
+ if (!reference || typeof reference.AdapterRuntime !== 'function' || typeof reference.createTools !== 'function'
67
+ || typeof reference.invokeTool !== 'function' || typeof reference.safeError !== 'function'
68
+ || typeof reference.LocalError !== 'function' || !Array.isArray(reference.TOOL_DEFINITIONS)
69
+ || typeof tool !== 'function' || !tool.schema || !client || !inbox
70
+ || !binding || !localId(binding.sessionID) || !absolute(binding.directory) || !absolute(binding.worktree)
71
+ || typeof binding.lifetime !== 'symbol' || !(binding.signal instanceof AbortSignal)
72
+ || typeof currentBinding !== 'function' || observeMetadata !== null && typeof observeMetadata !== 'function'
73
+ || inbox.identity?.client_type !== 'opencode' || client.inbox !== inbox) {
74
+ throw new Error('Invalid adapter configuration: provide a verified exact host binding and shared broker reference objects.');
75
+ }
76
+ const definitions = reference.TOOL_DEFINITIONS;
77
+ if (definitions.length !== TOOL_NAMES.length || definitions.some(definition => !TOOL_NAMES.includes(definition.name))
78
+ || new Set(definitions.map(definition => definition.name)).size !== TOOL_NAMES.length) {
79
+ throw new Error('Shared tool catalog changed; review it before enabling this adapter.');
80
+ }
81
+ const catalog = [...definitions, ...(cooperativeAck ? [ACK_DEFINITION] : [])].map(definition => {
82
+ const args = nativeShape(tool.schema, definition.inputSchema);
83
+ return { definition, args, validator: tool.schema.object(args).strict() };
84
+ });
85
+ const expected = Object.freeze({ sessionID: binding.sessionID, directory: binding.directory,
86
+ worktree: binding.worktree, lifetime: binding.lifetime, signal: binding.signal });
87
+ const identity = Object.freeze({ ...inbox.identity });
88
+ let started = false, closed = false, startPromise, runtimeStartPromise, stopPromise;
89
+ const diagnostic = code => { try { onDiagnostic({ code }); } catch { /* No raw exception data. */ } };
90
+ function sameIdentity(candidate) {
91
+ return candidate?.client_id === identity.client_id && candidate?.session_instance_id === identity.session_instance_id
92
+ && candidate?.client_type === identity.client_type;
93
+ }
94
+ async function alive() {
95
+ if (closed || expected.signal.aborted || !sameIdentity(inbox.identity)) return false;
96
+ let now;
97
+ try { now = await currentBinding(); } catch { return false; }
98
+ return !closed && !expected.signal.aborted && sameIdentity(inbox.identity) && now?.sessionID === expected.sessionID
99
+ && now?.directory === expected.directory && now?.worktree === expected.worktree
100
+ && now?.lifetime === expected.lifetime && now?.signal === expected.signal;
101
+ }
102
+ const runtime = new reference.AdapterRuntime({ client, inbox,
103
+ verifySession: async candidate => sameIdentity(candidate) && await alive(), isAlive: alive,
104
+ onDiagnostic: entry => diagnostic(entry.code) });
105
+ async function stop() {
106
+ if (stopPromise) return stopPromise;
107
+ closed = true; started = false;
108
+ expected.signal.removeEventListener('abort', onClose);
109
+ stopPromise = Promise.resolve().then(async () => {
110
+ try {
111
+ await runtime.stop({ closeSession: false }); // Cancel, but do not close a stale pre-reconnect epoch.
112
+ await runtimeStartPromise?.catch(() => {});
113
+ // Registration may have committed after cancellation. Keep
114
+ // its journal open until a second close fences that exact late lease.
115
+ await runtime.stop();
116
+ } finally { inbox.close(); }
117
+ });
118
+ return stopPromise;
119
+ }
120
+ const cooperativeInbox = cooperativeAck ? createCooperativeInbox({ inbox, isActive: () => started && !closed && !expected.signal.aborted }) : null;
121
+ const onClose = () => { void stop().catch(() => diagnostic('adapter_close_failed')); };
122
+ expected.signal.addEventListener('abort', onClose, { once: true });
123
+ if (expected.signal.aborted) onClose();
124
+
125
+ async function assertBound(context, effect = 'not_applied') {
126
+ if (!await alive()) {
127
+ onClose();
128
+ throw new reference.LocalError('session_mismatch', { effect });
129
+ }
130
+ if (context && (context.sessionID !== expected.sessionID || context.directory !== expected.directory
131
+ || context.worktree !== expected.worktree || !localId(context.messageID) || !(context.abort instanceof AbortSignal))) {
132
+ throw new reference.LocalError('session_mismatch', { effect });
133
+ }
134
+ if (context?.abort?.aborted) throw new reference.LocalError('request_aborted', { effect });
135
+ }
136
+ async function start(metadata) {
137
+ if (startPromise || started || closed) throw new reference.LocalError('owner_conflict');
138
+ startPromise = (async () => {
139
+ await assertBound();
140
+ try {
141
+ runtimeStartPromise = runtime.start(metadata);
142
+ const result = await runtimeStartPromise;
143
+ await assertBound(undefined, 'unknown');
144
+ started = true;
145
+ return result;
146
+ } catch (error) {
147
+ await stop().catch(() => diagnostic('adapter_close_failed'));
148
+ throw error;
149
+ }
150
+ })();
151
+ return startPromise;
152
+ }
153
+ const tools = Object.fromEntries(catalog.map(({ definition, args, validator }) => {
154
+ return [definition.name, tool({ description: definition.description, args,
155
+ async execute(arguments_, context) {
156
+ try {
157
+ if (!started || !context) throw new reference.LocalError('session_mismatch');
158
+ await assertBound(context);
159
+ const parsed = validator.safeParse(arguments_);
160
+ if (!parsed.success) throw new reference.LocalError('invalid_configuration', { field: 'arguments' });
161
+ // Per-invocation closure, never a mutable global current tool context.
162
+ const scopedClient = { inbox, get attachment() { return client.attachment; },
163
+ prepareSend: (...args_) => client.prepareSend(...args_) };
164
+ for (const method of ['listClients', 'sendPrepared', 'updateMetadata', 'messageStatus', 'refreshOwnedMetadata']) {
165
+ scopedClient[method] = async (...args_) => {
166
+ await assertBound(context);
167
+ return client[method](...args_);
168
+ };
169
+ }
170
+ const facade = reference.createTools({ client: scopedClient, inbox, observeMetadata,
171
+ cooperativeBoundary: async read => { await assertBound(context); return read(); } });
172
+ if (cooperativeInbox) Object.assign(facade, cooperativeInbox);
173
+ const result = await reference.invokeTool(facade, definition.name, parsed.data);
174
+ // Unknown/cancelled host results never get repackaged as acceptance.
175
+ // A write may already have committed; retain its stable send key.
176
+ try { await assertBound(context, 'unknown'); }
177
+ catch (error) {
178
+ const failure = reference.safeError(error);
179
+ const sendKey = result?.send_key ?? parsed.data.send_key;
180
+ if (definition.name === 'broker_send' && typeof sendKey === 'string') failure.send_key = sendKey;
181
+ return displayJSON(failure);
182
+ }
183
+ return displayJSON(result);
184
+ } catch (error) { return displayJSON(reference.safeError(error)); }
185
+ },
186
+ })];
187
+ }));
188
+ return Object.freeze({ tools, start, stop, localMode: 'cooperative_pull' });
189
+ }
@@ -0,0 +1,193 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, lstatSync, realpathSync, unlinkSync, openSync, closeSync, fstatSync, constants, renameSync } from 'node:fs';
2
+ import { join, isAbsolute } from 'node:path';
3
+ import { pathToFileURL, fileURLToPath } from 'node:url';
4
+ import { createHash, randomUUID } from 'node:crypto';
5
+ import { spawn } from 'node:child_process';
6
+ import { loadBrokerReference } from './reference.mjs';
7
+ import { createOpenCodeAdapter, nativeShape } from './opencode-adapter.mjs';
8
+
9
+ export const OWNED_VERSION = '1.19.4';
10
+ export const LINUX_X64_SHA256 = '538fcb1b6408c8d4608822acee19f7ff5b3a3eb3383c0633c4f06170857312a7';
11
+ const fail = () => { throw new Error('owned_run_configuration_invalid'); };
12
+ const plain = value => value && typeof value === 'object' && !Array.isArray(value);
13
+ function privateJSON(path) {
14
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
15
+ try {
16
+ const stat = fstatSync(fd);
17
+ if (!stat.isFile() || stat.mode & 0o077 || stat.size > 32768
18
+ || process.getuid && stat.uid !== process.getuid()) fail();
19
+ const text = readFileSync(fd, 'utf8');
20
+ if (Buffer.byteLength(text) > 32768) fail();
21
+ const value = JSON.parse(text);
22
+ if (!plain(value)) fail();
23
+ return value;
24
+ } finally { closeSync(fd); }
25
+ }
26
+ export function processIncarnation(pid = process.pid) {
27
+ if (!Number.isSafeInteger(pid) || pid < 1) fail();
28
+ const text = readFileSync(`/proc/${pid}/stat`, 'utf8');
29
+ const fields = text.slice(text.lastIndexOf(')') + 2).trim().split(/\s+/);
30
+ if (!/^[0-9]+$/.test(fields[19]) || fields[0] === 'Z') fail();
31
+ return { pid, starttime: fields[19], bootId: readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim() };
32
+ }
33
+ function sameProcess(expected) {
34
+ if (!plain(expected)) return false;
35
+ try { const actual = processIncarnation(expected.pid); return actual.starttime === expected.starttime && actual.bootId === expected.bootId; }
36
+ catch { return false; }
37
+ }
38
+ export function ownedArguments(prompt, model) {
39
+ if (typeof prompt !== 'string' || !prompt.trim() || !prompt.isWellFormed() || Buffer.byteLength(prompt) > 65536
40
+ || model !== undefined && (typeof model !== 'string' || !/^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,255}$/.test(model))) fail();
41
+ return ['run', '--format', 'json', ...(model ? ['--model', model] : []), '--', prompt];
42
+ }
43
+ export function ownedEnvironment(env, pluginPath, recordPath, nonce) {
44
+ let overlay = {};
45
+ if (env.OPENCODE_CONFIG_CONTENT) {
46
+ try { overlay = JSON.parse(env.OPENCODE_CONFIG_CONTENT); } catch { throw new Error('existing_config_content_must_be_json_preserve_it_before_launch'); }
47
+ if (!plain(overlay) || overlay.plugin !== undefined && !Array.isArray(overlay.plugin)) fail();
48
+ }
49
+ return { ...env, OPENCODE_CONFIG_CONTENT: JSON.stringify({ ...overlay,
50
+ plugin: [...(overlay.plugin ?? []), pathToFileURL(pluginPath).href] }),
51
+ OPENCODE_REVIEW_RUN_FILE: recordPath, OPENCODE_REVIEW_RUN_NONCE: nonce };
52
+ }
53
+ function readOwnedRecord(path, nonce) {
54
+ const value = privateJSON(path);
55
+ if (value.version !== 1 || value.nonce !== nonce || typeof nonce !== 'string' || nonce.length !== 36
56
+ || value.opencodeVersion !== OWNED_VERSION || !isAbsolute(value.directory) || !isAbsolute(value.referenceDirectory)
57
+ || !isAbsolute(value.inboxDirectory) || !plain(value.identity) || value.identity.client_type !== 'opencode'
58
+ || !plain(value.broker) || !Number.isFinite(value.createdAt)
59
+ || value.child?.pid !== process.pid || !sameProcess(value.child) || !sameProcess(value.launcher)) fail();
60
+ return value;
61
+ }
62
+
63
+ /** A normal native plugin, enabled ONLY by our private fresh-run launcher. */
64
+ export async function createOwnedHooks({ native, tool, recordPath, nonce,
65
+ reference: suppliedReference, clientFactory, adapterFactory = createOpenCodeAdapter }) {
66
+ const record = readOwnedRecord(recordPath, nonce);
67
+ if (realpathSync(native.directory) !== record.directory) fail();
68
+ const reference = suppliedReference ?? await loadBrokerReference(record.referenceDirectory);
69
+ const lifetime = Symbol('owned-opencode-run');
70
+ const controller = new AbortController();
71
+ let rootId, seenRootId, disposed = false, adapter, bindingPromise, inbox, stopPromise;
72
+ const stop = () => stopPromise ??= (async () => { disposed = true; controller.abort(); await bindingPromise?.catch(() => {}); await adapter?.stop(); })();
73
+ async function sessionInfo(id) {
74
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(2000)]);
75
+ let onAbort;
76
+ const result = await Promise.race([
77
+ Promise.resolve().then(() => native.client.session.get({ path: { id } }, { signal })),
78
+ new Promise((_, reject) => {
79
+ onAbort = () => reject(new reference.LocalError('session_mismatch'));
80
+ if (signal.aborted) onAbort(); else signal.addEventListener('abort', onAbort, { once: true });
81
+ }),
82
+ ]).finally(() => signal.removeEventListener('abort', onAbort));
83
+ const info = result?.data;
84
+ if (result?.error || !info || info.id !== id || info.parentID || realpathSync(info.directory) !== record.directory
85
+ || !Number.isFinite(info.time?.created) || info.time.created < record.createdAt) throw new reference.LocalError('session_mismatch');
86
+ return info;
87
+ }
88
+ async function currentBinding() {
89
+ if (disposed || controller.signal.aborted || !rootId) return null;
90
+ try { readOwnedRecord(recordPath, nonce); await sessionInfo(rootId); readOwnedRecord(recordPath, nonce); if (disposed || controller.signal.aborted) return null; }
91
+ catch { return null; }
92
+ return { sessionID: rootId, directory: native.directory, worktree: native.worktree, lifetime, signal: controller.signal };
93
+ }
94
+ const proxies = Object.fromEntries(reference.TOOL_DEFINITIONS.map(definition => [definition.name, tool({
95
+ description: definition.description, args: nativeShape(tool.schema, definition.inputSchema),
96
+ async execute(args, context) {
97
+ if (disposed || !adapter || context?.sessionID !== rootId) return JSON.stringify(new reference.LocalError('session_mismatch').toJSON());
98
+ return adapter.tools[definition.name].execute(args, context);
99
+ },
100
+ })]));
101
+ return {
102
+ tool: proxies,
103
+ async event({ event }) {
104
+ if (event.type === 'session.created' && !event.properties?.info?.parentID) {
105
+ const id = event.properties?.info?.id;
106
+ if (seenRootId && seenRootId !== id) { await stop(); return; }
107
+ seenRootId = id;
108
+ }
109
+ if (event.type === 'session.deleted' && event.properties?.info?.id === rootId) await stop();
110
+ },
111
+ async 'chat.message'(input, output) {
112
+ if (disposed || output?.message?.role !== 'user' || output.message.sessionID !== input.sessionID
113
+ || !seenRootId || seenRootId !== input.sessionID) throw new reference.LocalError('session_mismatch');
114
+ if (rootId && rootId !== input.sessionID) throw new reference.LocalError('session_mismatch');
115
+ if (bindingPromise) return bindingPromise;
116
+ rootId = input.sessionID;
117
+ bindingPromise = (async () => {
118
+ await sessionInfo(rootId);
119
+ if (disposed) throw new reference.LocalError('session_mismatch');
120
+ inbox = new reference.Inbox({ directory: record.inboxDirectory, identity: record.identity });
121
+ try {
122
+ const client = clientFactory ? clientFactory(inbox) : new reference.BrokerClient({ ...record.broker, inbox });
123
+ adapter = adapterFactory({ reference, tool, client, binding: { sessionID: rootId, directory: native.directory,
124
+ worktree: native.worktree, lifetime, signal: controller.signal }, currentBinding });
125
+ await adapter.start({ name: 'OpenCode owned run', model: { provider: null, id: null, source: 'unknown' }, project: null });
126
+ if (!await currentBinding()) throw new reference.LocalError('session_mismatch');
127
+ writeFileSync(`${recordPath}.bound`, JSON.stringify({ nonce, child: record.child, rootId }), { mode: 0o600, flag: 'wx' });
128
+ } catch (error) { controller.abort(); if (adapter) await adapter.stop(); else inbox.close(); throw error; }
129
+ })();
130
+ return bindingPromise;
131
+ },
132
+ dispose: stop,
133
+ };
134
+ }
135
+
136
+ /** Fixed fresh headless process only; never attach, resume, change permissions or inject a prompt into another host. */
137
+ export async function launchOwnedRun({ configPath, prompt, env = process.env, cwd = process.cwd(), spawnImpl = spawn }) {
138
+ const config = privateJSON(configPath);
139
+ if (Object.keys(config).some(key => !['opencodeExecutable', 'referenceDirectory', 'stateDirectory', 'broker', 'model'].includes(key))
140
+ || !isAbsolute(config.opencodeExecutable) || !isAbsolute(config.referenceDirectory) || !isAbsolute(config.stateDirectory)
141
+ || !plain(config.broker) || Object.keys(config.broker).some(key => !['url', 'tokenFile', 'caFile'].includes(key))
142
+ || !isAbsolute(config.broker.tokenFile)) fail();
143
+ const binary = realpathSync(config.opencodeExecutable);
144
+ if (process.platform !== 'linux' || process.arch !== 'x64'
145
+ || createHash('sha256').update(readFileSync(binary)).digest('hex') !== LINUX_X64_SHA256) throw new Error('unsupported_opencode_binary_use_verified_linux_x64_1_19_4');
146
+ const reference = await loadBrokerReference(config.referenceDirectory);
147
+ const args = ownedArguments(prompt, config.model);
148
+ mkdirSync(config.stateDirectory, { recursive: true, mode: 0o700 });
149
+ const stat = lstatSync(config.stateDirectory);
150
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 0o077 || realpathSync(config.stateDirectory) !== config.stateDirectory || process.getuid && stat.uid !== process.getuid()) fail();
151
+ const runDirectory = mkdtempSync(join(config.stateDirectory, 'run-'));
152
+ const recordPath = join(runDirectory, 'owner.json'), nonce = randomUUID();
153
+ const record = { version: 1, nonce, opencodeVersion: OWNED_VERSION, createdAt: Date.now(), directory: realpathSync(cwd),
154
+ launcher: processIncarnation(), child: null, referenceDirectory: config.referenceDirectory, inboxDirectory: join(runDirectory, 'inbox'), identity: reference.newSessionIdentity('opencode'), broker: config.broker };
155
+ writeFileSync(recordPath, JSON.stringify(record), { mode: 0o600, flag: 'wx' });
156
+ const pluginPath = new URL('../plugin/owned-run.ts', import.meta.url);
157
+ let child;
158
+ try {
159
+ const childEnv = ownedEnvironment(env, fileURLToPath(pluginPath), recordPath, nonce);
160
+ child = spawnImpl(binary, args, { cwd: record.directory, env: childEnv, stdio: ['ignore', 'inherit', 'inherit'] });
161
+ if (child.pid) {
162
+ record.child = processIncarnation(child.pid);
163
+ const next = `${recordPath}.next`;
164
+ writeFileSync(next, JSON.stringify(record), { mode: 0o600, flag: 'wx' });
165
+ renameSync(next, recordPath);
166
+ }
167
+ const result = await new Promise((resolve, reject) => {
168
+ child.once('error', reject); child.once('exit', (code, signal) => resolve({ code, signal }));
169
+ });
170
+ if (result.code === 0) {
171
+ let proof;
172
+ try { proof = privateJSON(`${recordPath}.bound`); } catch { throw new Error('owned_native_broker_unavailable'); }
173
+ if (proof.nonce !== nonce || JSON.stringify(proof.child) !== JSON.stringify(record.child)
174
+ || typeof proof.rootId !== 'string' || !proof.rootId) throw new Error('owned_native_broker_unavailable');
175
+ }
176
+ return result;
177
+ } finally {
178
+ // Revoke the private lifetime marker. Durable inbox content is never deleted.
179
+ try { unlinkSync(recordPath); } catch {}
180
+ try { unlinkSync(`${recordPath}.bound`); } catch {}
181
+ // Native dispose normally closes the lease. A killed child cannot; after exit
182
+ // its SQLite ownership lock is released, so the same exact identity can close.
183
+ if (child && (child.exitCode !== null || child.signalCode)) {
184
+ let journal;
185
+ try {
186
+ journal = new reference.Inbox({ directory: record.inboxDirectory, identity: record.identity });
187
+ const client = new reference.BrokerClient({ ...record.broker, inbox: journal, maxAttempts: 1, requestTimeoutMs: 5000 });
188
+ if (client.attachment.lease_epoch) await client.closeSession({ signal: AbortSignal.timeout(5000) });
189
+ } catch { /* No raw credential-bearing diagnostics; broker lease remains bounded. */ }
190
+ finally { journal?.close(); }
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,21 @@
1
+ import { readFileSync, lstatSync } from 'node:fs';
2
+ import { join, isAbsolute } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ export const REFERENCE_COMMIT = 'ba2219cd87a1feac23e01b23d2c2b3d3bdd65359';
5
+ const HASHES = Object.freeze({
6
+ "reference/inbox.mjs": "93f75786eab29429ace024974e0f61d6ad46d2c5fe065dae387e54fd2373b167",
7
+ "reference/client.mjs": "f661b45acba106be03d97dacba3cd90ac18b09e7ff9eb93b995d5036d9d1c785",
8
+ "reference/runtime.mjs": "8733076b73a42ab8f12fe20ba9908acdd754d89734ffd335e84b49aee9d737ca",
9
+ "reference/tools.mjs": "5e6355cef1bec56170dca14b93142a7da8c102f411c5dbf437d84acda7351acf",
10
+ "reference/mcp-stdio.mjs": "10a93788af426ce898c398e38dd41ac77b6e0129a7697bdc36c8d4f6d6e20138",
11
+ "reference/errors.mjs": "e33231d94f514b5a1b59fdd4184f5881dd3712bde725a1cf53352c26293452ac",
12
+ "src/protocol.mjs": "f3ff24b4429dddc8a9c10513ab72dbc613c91244591132458c7866a812d6316b"
13
+ });
14
+ /** Verify reviewed source bytes before importing any trusted local module. */
15
+ export function verifyReference(directory) {
16
+ if (typeof directory !== 'string' || !isAbsolute(directory)) throw new Error('absolute_reference_required');
17
+ for (const [file, digest] of Object.entries(HASHES)) {
18
+ const path = join(directory, file), stat = lstatSync(path);
19
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024 || createHash('sha256').update(readFileSync(path)).digest('hex') !== digest) throw new Error('reference_snapshot_mismatch');
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ import { verifyReference } from './reference-pin.mjs';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+
5
+ export const BROKER_REFERENCE_COMMIT = 'ba2219cd87a1feac23e01b23d2c2b3d3bdd65359';
6
+
7
+ /** Trusted operator path to a pinned broker checkout; never a model argument. */
8
+ export async function loadBrokerReference(directory = fileURLToPath(new URL('../vendor/broker', import.meta.url))) {
9
+ if (typeof directory !== 'string' || !isAbsolute(directory)) throw new Error('Use an absolute path to the pinned broker reference checkout.');
10
+ verifyReference(directory);
11
+ const load = file => import(pathToFileURL(join(directory, file)).href);
12
+ const [inbox, client, runtime, tools, stdio, errors] = await Promise.all([
13
+ load('reference/inbox.mjs'), load('reference/client.mjs'), load('reference/runtime.mjs'),
14
+ load('reference/tools.mjs'), load('reference/mcp-stdio.mjs'), load('reference/errors.mjs'),
15
+ ]);
16
+ return { Inbox: inbox.Inbox, newSessionIdentity: inbox.newSessionIdentity,
17
+ BrokerClient: client.BrokerClient, AdapterRuntime: runtime.AdapterRuntime,
18
+ createTools: tools.createTools, invokeTool: tools.invokeTool,
19
+ TOOL_DEFINITIONS: stdio.TOOL_DEFINITIONS, LocalError: errors.LocalError, safeError: errors.safeError };
20
+ }
@@ -0,0 +1,6 @@
1
+ export const ACK_DEFINITION = Object.freeze({ name: 'broker_acknowledge',
2
+ description: 'Explicitly acknowledge reading the exact message returned by broker_receive, using its receipt_token. Advances your local inbox. This is not approval of its instructions, proof of action success, or a native injection receipt.',
3
+ inputSchema: { type: 'object', additionalProperties: false, properties: {
4
+ message_id: { type: 'string', format: 'uuid' }, receipt_token: { type: 'string', format: 'uuid' },
5
+ }, required: ['message_id', 'receipt_token'] },
6
+ });
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno Orsolon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ # Vendored runtime
2
+
3
+ Source: https://github.com/matuto-ai/psst/tree/ba2219cd87a1feac23e01b23d2c2b3d3bdd65359
4
+ Seven runtime/protocol files are copied byte-for-byte and verified by src/reference-pin.mjs before import. The MIT license is retained. Connector cooperative read acknowledgments are implemented separately in src/cooperative-inbox.mjs, not by changing the pinned runtime or inventing native receipts.
5
+ Test-only broker fixtures are copied from the same commit into test/broker-fixture; they are excluded from the npm package. No broker daemon is bundled in the installable runtime.