@matuto-ai/opencode-psst-connector 0.0.0-stage → 0.6.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +157 -2
  4. package/docs/active-conversation.md +68 -0
  5. package/docs/message-lifecycle.md +31 -0
  6. package/docs/migration-0.6.0.md +21 -0
  7. package/docs/native-automation.md +37 -0
  8. package/docs/presence-notices.md +21 -0
  9. package/docs/publishing.md +19 -0
  10. package/docs/reference-snapshot.sha256 +7 -0
  11. package/docs/setup-acceptance.md +96 -0
  12. package/docs/verification.md +39 -0
  13. package/package.json +42 -4
  14. package/plugin/index.mjs +18 -0
  15. package/plugin/tui.mjs +160 -0
  16. package/src/active-presence.mjs +97 -0
  17. package/src/approval-helper.mjs +11 -0
  18. package/src/config.mjs +50 -0
  19. package/src/enrollment.mjs +59 -0
  20. package/src/identity-recovery.mjs +94 -0
  21. package/src/installation.mjs +127 -0
  22. package/src/message-format.mjs +26 -0
  23. package/src/message-preview.mjs +29 -0
  24. package/src/native-delivery.mjs +169 -0
  25. package/src/native-inbox.mjs +37 -0
  26. package/src/native-incarnation.mjs +67 -0
  27. package/src/native-lock.mjs +17 -0
  28. package/src/native-metadata.mjs +15 -0
  29. package/src/native-plugin.mjs +335 -0
  30. package/src/native-setup.mjs +208 -0
  31. package/src/opencode-adapter.mjs +223 -0
  32. package/src/presence-notices.mjs +90 -0
  33. package/src/project-metadata.mjs +21 -0
  34. package/src/reference-pin.mjs +21 -0
  35. package/src/reference.mjs +20 -0
  36. package/src/setup-rpc.mjs +16 -0
  37. package/src/tool-schema.mjs +5 -0
  38. package/src/tool-scope.mjs +26 -0
  39. package/vendor/broker/LICENSE +21 -0
  40. package/vendor/broker/ORIGIN.md +12 -0
  41. package/vendor/broker/reference/client.mjs +439 -0
  42. package/vendor/broker/reference/errors.mjs +43 -0
  43. package/vendor/broker/reference/inbox.mjs +334 -0
  44. package/vendor/broker/reference/mcp-stdio.mjs +160 -0
  45. package/vendor/broker/reference/runtime.mjs +329 -0
  46. package/vendor/broker/reference/tools.mjs +88 -0
  47. package/vendor/broker/src/protocol.mjs +325 -0
  48. package/vendor/enrollment/LICENSE +21 -0
  49. package/vendor/enrollment/ORIGIN.md +20 -0
  50. package/vendor/enrollment/approval.mjs +181 -0
  51. package/vendor/enrollment/enrollment.mjs +137 -0
@@ -0,0 +1,223 @@
1
+ import { createToolScope } from './tool-scope.mjs';
2
+ import { isAbsolute, normalize } from 'node:path';
3
+
4
+ const TOOL_NAMES = ['broker_list_clients', 'broker_send', 'broker_update_metadata', 'broker_message_status', 'broker_receive', 'broker_sent_history', 'broker_message_content', 'broker_resend', 'broker_cancel'];
5
+ // Reviewed SDK adds two cooperative presence tools. Native OpenCode keeps its
6
+ // existing eight-tool surface; do not accidentally widen it during SDK rollout.
7
+ export function nativeToolDefinitions(definitions) {
8
+ const known=[...TOOL_NAMES,'broker_presence_events','broker_acknowledge_presence_event'];
9
+ if(!Array.isArray(definitions)||definitions.length!==known.length||new Set(definitions.map(d=>d.name)).size!==known.length||definitions.some(d=>!known.includes(d.name)))throw new Error('Shared tool catalog changed; review it before enabling this adapter.');
10
+ return definitions.filter(d=>TOOL_NAMES.includes(d.name));
11
+ }
12
+ const localId = value => typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,256}$/.test(value);
13
+ const absolute = value => typeof value === 'string' && isAbsolute(value) && normalize(value) === value;
14
+ 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;
15
+
16
+ // Adapt the shared bounded tool catalog to OpenCode's documented Zod shape.
17
+ // This is a native tool schema adapter, not another broker wire contract.
18
+ function nativeSchema(z, rule) {
19
+ const nullableString = Array.isArray(rule.type) && rule.type.length === 2
20
+ && rule.type.includes('string') && rule.type.includes('null');
21
+ const allowed = rule.enum ? ['enum'] : rule.type === 'object'
22
+ ? ['type', 'properties', 'required', 'additionalProperties'] : rule.type === 'integer'
23
+ ? ['type', 'minimum', 'maximum'] : rule.type === 'boolean' ? ['type'] : ['type', 'maxLength', 'format'];
24
+ if (Object.keys(rule).some(key => !allowed.includes(key))
25
+ || Array.isArray(rule.type) && !nullableString
26
+ || rule.format !== undefined && rule.format !== 'uuid'
27
+ || rule.maxLength !== undefined && (!Number.isSafeInteger(rule.maxLength) || rule.maxLength < 1)) {
28
+ throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
29
+ }
30
+ let schema;
31
+ if (rule.enum) schema = z.enum(rule.enum);
32
+ else if (rule.type === 'object') schema = z.object(nativeShape(z, rule)).strict();
33
+ else if (rule.type === 'boolean') schema = z.boolean();
34
+ else if (rule.type === 'integer') {
35
+ schema = z.number().int().safe();
36
+ if (rule.minimum !== undefined) schema = schema.min(rule.minimum);
37
+ if (rule.maximum !== undefined) schema = schema.max(rule.maximum);
38
+ } else if (rule.type === 'string' || Array.isArray(rule.type) && rule.type.includes('string')) {
39
+ schema = z.string().refine(value => value.isWellFormed()
40
+ && (rule.maxLength === undefined || [...value].length <= rule.maxLength), 'Use bounded, well-formed text.');
41
+ if (rule.format === 'uuid') schema = schema.refine(value => uuid.test(value), 'Use an exact UUID address.');
42
+ if (Array.isArray(rule.type) && rule.type.includes('null')) schema = schema.nullable();
43
+ } else throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
44
+ return schema;
45
+ }
46
+ export function nativeShape(z, schema) {
47
+ if (schema.type !== 'object' || schema.additionalProperties !== false || !schema.properties
48
+ || !Array.isArray(schema.required) || schema.required.some(key => !Object.hasOwn(schema.properties, key))
49
+ || Object.keys(schema).some(key => !['type', 'properties', 'required', 'additionalProperties'].includes(key))) {
50
+ throw new Error('Unsupported shared tool schema; update this adapter before enabling it.');
51
+ }
52
+ return Object.fromEntries(Object.entries(schema.properties).map(([key, rule]) => {
53
+ const value = nativeSchema(z, rule);
54
+ return [key, schema.required?.includes(key) ? value : value.optional()];
55
+ }));
56
+ }
57
+ function displayJSON(value) {
58
+ const result = JSON.stringify(value).replace(/[\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu,
59
+ char => `\\u${char.codePointAt(0).toString(16).padStart(4, '0')}`);
60
+ if (Buffer.byteLength(result) > 512 * 1024) throw new Error('bounded_result');
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * Outgoing native tools for ONE verified OpenCode host-session lifetime.
66
+ * The trusted host owner supplies currentBinding() and aborts binding.signal on
67
+ * exact host close. Session IDs, a server process, and an idle observation alone
68
+ * are insufficient lifetime proof. No automatic plugin attachment is implied.
69
+ */
70
+ export function createOpenCodeAdapter({ reference, tool, binding, currentBinding,
71
+ client, inbox = client?.inbox, observeMetadata = null, onDiagnostic = () => {}, operationContext = null, closeSessionOnStop = true }) {
72
+ if (!reference || typeof reference.AdapterRuntime !== 'function' || typeof reference.createTools !== 'function'
73
+ || typeof reference.invokeTool !== 'function' || typeof reference.safeError !== 'function'
74
+ || typeof reference.LocalError !== 'function' || !Array.isArray(reference.TOOL_DEFINITIONS)
75
+ || typeof tool !== 'function' || !tool.schema || !client || !inbox
76
+ || !binding || !localId(binding.sessionID) || !absolute(binding.directory) || !absolute(binding.worktree)
77
+ || typeof binding.lifetime !== 'symbol' || !(binding.signal instanceof AbortSignal)
78
+ || typeof closeSessionOnStop !== 'boolean'
79
+ || operationContext !== null && typeof operationContext !== 'function'
80
+ || typeof currentBinding !== 'function' || observeMetadata !== null && typeof observeMetadata !== 'function'
81
+ || inbox.identity?.client_type !== 'opencode' || client.inbox !== inbox) {
82
+ throw new Error('Invalid adapter configuration: provide a verified exact host binding and shared broker reference objects.');
83
+ }
84
+ const definitions = nativeToolDefinitions(reference.TOOL_DEFINITIONS);
85
+ if (definitions.length !== TOOL_NAMES.length || definitions.some(definition => !TOOL_NAMES.includes(definition.name))
86
+ || new Set(definitions.map(definition => definition.name)).size !== TOOL_NAMES.length) {
87
+ throw new Error('Shared tool catalog changed; review it before enabling this adapter.');
88
+ }
89
+ const catalog = definitions.filter(definition => definition.name !== 'broker_receive').map(definition => {
90
+ const args = nativeShape(tool.schema, definition.inputSchema);
91
+ return { definition, args, validator: tool.schema.object(args).strict() };
92
+ });
93
+ const expected = Object.freeze({ sessionID: binding.sessionID, directory: binding.directory,
94
+ worktree: binding.worktree, lifetime: binding.lifetime, signal: binding.signal });
95
+ const identity = Object.freeze({ ...inbox.identity });
96
+ const toolScope = createToolScope({ closedError: () => new reference.LocalError('session_mismatch', { effect: 'unknown' }), abortedError: () => new reference.LocalError('request_aborted') });
97
+ const recoveryAbort = new AbortController();
98
+ let recoveryPromise, metadataTail = Promise.resolve();
99
+ let started = false, closed = false, startPromise, runtimeStartPromise, stopPromise;
100
+ const diagnostic = code => { try { onDiagnostic({ code }); } catch { /* No raw exception data. */ } };
101
+ function sameIdentity(candidate) {
102
+ return candidate?.client_id === identity.client_id && candidate?.session_instance_id === identity.session_instance_id
103
+ && candidate?.client_type === identity.client_type;
104
+ }
105
+ async function alive() {
106
+ if (closed || expected.signal.aborted || !sameIdentity(inbox.identity)) return false;
107
+ let now;
108
+ try { now = await currentBinding(); } catch { return false; }
109
+ return !closed && !expected.signal.aborted && sameIdentity(inbox.identity) && now?.sessionID === expected.sessionID
110
+ && now?.directory === expected.directory && now?.worktree === expected.worktree
111
+ && now?.lifetime === expected.lifetime && now?.signal === expected.signal;
112
+ }
113
+ // The generic reference defaults a failed liveness check to terminal close.
114
+ // Native navigation/unload is resumable, including that internal stop path.
115
+ class NativeRuntime extends reference.AdapterRuntime {
116
+ stop(options = { closeSession: closeSessionOnStop }) { return super.stop(options); }
117
+ async checkAlive() {
118
+ const wasClosed = this.closed, alive = await super.checkAlive();
119
+ if (!alive && !wasClosed) diagnostic('native_binding_unavailable');
120
+ return alive;
121
+ }
122
+ }
123
+ const runtime = new NativeRuntime({ client, inbox,
124
+ verifySession: async candidate => sameIdentity(candidate) && await alive(), isAlive: alive,
125
+ onDiagnostic: entry => diagnostic(entry.code) });
126
+ async function stop() {
127
+ if (stopPromise) return stopPromise;
128
+ closed = true; started = false; recoveryAbort.abort();
129
+ const toolsClosed = toolScope.close();
130
+ expected.signal.removeEventListener('abort', onClose);
131
+ // Fence transport lifetime synchronously, before any deferred tool drain.
132
+ const runtimeStopped = runtime.stop({ closeSession: false });
133
+ stopPromise = Promise.resolve().then(async () => {
134
+ try {
135
+ await runtimeStopped; // Cancel, but do not close a stale pre-reconnect epoch.
136
+ await runtimeStartPromise?.catch(() => {});
137
+ // Registration may have committed after cancellation. Keep
138
+ // its journal open until a second close fences that exact late lease.
139
+ await runtime.stop({ closeSession: closeSessionOnStop });
140
+ } finally { await toolsClosed; await recoveryPromise?.catch(() => {}); inbox.close(); }
141
+ });
142
+ return stopPromise;
143
+ }
144
+ const onClose = () => { void stop().catch(() => diagnostic('adapter_close_failed')); };
145
+ expected.signal.addEventListener('abort', onClose, { once: true });
146
+ if (expected.signal.aborted) onClose();
147
+
148
+ async function assertBound(context, effect = 'not_applied') {
149
+ if (!await alive()) {
150
+ onClose();
151
+ throw new reference.LocalError('session_mismatch', { effect });
152
+ }
153
+ if (context && (context.sessionID !== expected.sessionID || context.directory !== expected.directory
154
+ || context.worktree !== expected.worktree || !localId(context.messageID) || !(context.abort instanceof AbortSignal))) {
155
+ throw new reference.LocalError('session_mismatch', { effect });
156
+ }
157
+ if (context?.abort?.aborted) throw new reference.LocalError('request_aborted', { effect });
158
+ }
159
+ async function start(metadata) {
160
+ if (startPromise || started || closed) throw new reference.LocalError('owner_conflict');
161
+ startPromise = (async () => {
162
+ await assertBound();
163
+ try {
164
+ runtimeStartPromise = runtime.start(metadata);
165
+ const result = await runtimeStartPromise;
166
+ await assertBound(undefined, 'unknown');
167
+ // No durable tool-call ID exists in this pinned ToolContext. Reconcile
168
+ // private operations after exact-session reattachment, never guess from text/messageID.
169
+ recoveryPromise = client.recoverOutgoing?.({ limit: 10, signal: AbortSignal.any([recoveryAbort.signal, expected.signal, AbortSignal.timeout(5000)]) });
170
+ await recoveryPromise?.catch(() => diagnostic('outgoing_recovery_incomplete'));
171
+ await assertBound(undefined, 'unknown');
172
+ started = true;
173
+ return result;
174
+ } catch (error) {
175
+ await stop().catch(() => diagnostic('adapter_close_failed'));
176
+ throw error;
177
+ }
178
+ })();
179
+ return startPromise;
180
+ }
181
+ const tools = Object.fromEntries(catalog.map(({ definition, args, validator }) => {
182
+ return [definition.name, tool({ description: definition.description, args,
183
+ async execute(arguments_, context) {
184
+ return toolScope.run(async operationSignal => {
185
+ try {
186
+ if (!started || !context) throw new reference.LocalError('session_mismatch');
187
+ await assertBound(context);
188
+ const parsed = validator.safeParse(arguments_);
189
+ if (!parsed.success) throw new reference.LocalError('invalid_configuration', { field: 'arguments' });
190
+ // Per-invocation closure, never a mutable global current tool context.
191
+ const scopedClient = { inbox, get attachment() { return client.attachment; },
192
+ prepareSend: (...args_) => client.prepareSend(...args_),
193
+ prepareResend: (...args_) => client.prepareResend(...args_) };
194
+ for (const method of ['listClients', 'sendPrepared', 'updateMetadata', 'messageStatus', 'refreshOwnedMetadata', 'resendPrepared', 'sentHistory', 'messageContent', 'cancelMessage']) {
195
+ scopedClient[method] = async (...args_) => {
196
+ await assertBound(context);
197
+ return client[method](...args_);
198
+ };
199
+ }
200
+ const facade = reference.createTools({ client: scopedClient, inbox, observeMetadata,
201
+ cooperativeBoundary: async read => { await assertBound(context); return read(); } });
202
+ const invoke = async () => { await assertBound(context); return reference.invokeTool(facade, definition.name, parsed.data, { ...(operationContext ? operationContext(context) : {}), signal: AbortSignal.any([operationSignal, expected.signal]) }); };
203
+ // Observed metadata is CAS-versioned. Serialize only metadata-writing
204
+ // tools within this exact session; distinct sessions remain independent.
205
+ let result;
206
+ if (observeMetadata && ['broker_send', 'broker_update_metadata'].includes(definition.name)) {
207
+ const pending = metadataTail.then(invoke); metadataTail = pending.catch(() => {}); result = await pending;
208
+ } else result = await invoke();
209
+ // Unknown/cancelled host results never get repackaged as acceptance.
210
+ // The private outgoing ledger retains any uncertain operation and its key.
211
+ try { await assertBound(context, 'unknown'); }
212
+ catch (error) {
213
+ const failure = reference.safeError(error);
214
+ return displayJSON(failure);
215
+ }
216
+ return displayJSON(result);
217
+ } catch (error) { return displayJSON(reference.safeError(error)); }
218
+ }, context?.abort).catch(error => displayJSON(reference.safeError(error)));
219
+ },
220
+ })];
221
+ }));
222
+ return Object.freeze({ tools, start, stop, localMode: 'native_automatic' });
223
+ }
@@ -0,0 +1,90 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { canonical, validateUuid } from '../vendor/broker/src/protocol.mjs';
3
+
4
+ // Copy only bounded transport metadata. Never interpret peer content as instructions.
5
+ export function presencePayload(event) {
6
+ const uuid = value => { if (typeof value !== 'string') throw Error('presence_event_invalid'); return validateUuid(value); };
7
+ const date = value => { if (typeof value !== 'string' || value.length > 32 || !Number.isFinite(Date.parse(value))) throw Error('presence_event_invalid'); return new Date(value).toISOString(); };
8
+ if (event?.type !== 'recipient_offline' || event.presence_state !== 'disconnected'
9
+ || !Array.isArray(event.messages) || event.messages.length < 1 || event.messages.length > 10
10
+ || typeof event.has_more_messages !== 'boolean') throw Error('presence_event_invalid');
11
+ const metadata = { event_id: uuid(event.event_id), recipient_id: uuid(event.recipient_id),
12
+ last_heartbeat_at: date(event.last_heartbeat_at), created_at: date(event.created_at), observed_at: date(event.observed_at),
13
+ presence_state: 'disconnected', messages: event.messages.map(message => {
14
+ if (!['queued', 'claimed'].includes(message.delivery_state) || typeof message.delivery_uncertain !== 'boolean') throw Error('presence_event_invalid');
15
+ return { message_id: uuid(message.message_id), delivery_state: message.delivery_state, delivery_uncertain: message.delivery_uncertain };
16
+ }), has_more_messages: event.has_more_messages };
17
+ return { text: 'Psst transport notice: recipient was offline beyond the broker grace while delivery remained pending at the observation time. '
18
+ + 'This is not a reply timeout or proof of non-receipt. No message was resent, cancelled or closed. Current state may have changed.\n' + canonical(metadata),
19
+ description: 'Psst: recipient offline, delivery pending', metadata: { source: 'psst', presence_event_id: metadata.event_id, format: 'psst-presence-v1' } };
20
+ }
21
+
22
+ /** Uses the existing session journal/owner. Persist before mutation; never retry a
23
+ * potentially admitted synthetic ID. Only exact promoted context permits ACK. */
24
+ export function createPresenceNotices({ client, inbox, session, binding, call, permitted, diagnostic, now = Date.now }) {
25
+ inbox.transaction(() => inbox.db.exec(`CREATE TABLE IF NOT EXISTS v2_presence_notices (
26
+ event_id TEXT PRIMARY KEY, native_id TEXT NOT NULL UNIQUE, payload TEXT NOT NULL,
27
+ presented INTEGER NOT NULL DEFAULT 0, acknowledged INTEGER NOT NULL DEFAULT 0, acknowledged_at INTEGER)`));
28
+ let due = 0, cursor;
29
+ const get = eventID => inbox.db.prepare('SELECT * FROM v2_presence_notices WHERE event_id=?').get(eventID);
30
+ async function reconcile(row) {
31
+ if (!row.presented) {
32
+ const context = await call(session.context.bind(session), { sessionID: binding.sessionID });
33
+ if (!Array.isArray(context) || !context.some(message => message?.id === row.native_id && message.type === 'synthetic'
34
+ && canonical({ text: message.text, description: message.description, metadata: message.metadata }) === row.payload)) return;
35
+ if (!await permitted()) return;
36
+ inbox.transaction(() => inbox.db.prepare('UPDATE v2_presence_notices SET presented=1 WHERE event_id=?').run(row.event_id));
37
+ }
38
+ if (!await permitted()) return;
39
+ let receipt;
40
+ try { receipt = await call((_, options) => client.acknowledgePresenceEvent(row.event_id, options), {}); }
41
+ catch (error) {
42
+ // A verified broker 404 means a reconnect/expiry retired this event. A
43
+ // generic network/permission failure must never be treated as completion.
44
+ if (error?.code !== 'message_not_found' || error.status !== 404) throw error;
45
+ receipt = { event_id: row.event_id, acknowledged: true };
46
+ }
47
+ if (receipt?.event_id !== row.event_id || receipt.acknowledged !== true || !await permitted()) return;
48
+ inbox.transaction(() => inbox.db.prepare('UPDATE v2_presence_notices SET acknowledged=1,acknowledged_at=? WHERE event_id=?').run(now(), row.event_id));
49
+ diagnostic('native_presence_presented');
50
+ }
51
+ return { async tick() {
52
+ if (client?.policy?.presence_events !== true || typeof client.presenceEvents !== 'function'
53
+ || typeof client.acknowledgePresenceEvent !== 'function' || now() < due || !await permitted()) return;
54
+ due = now() + 60000;
55
+ // Retry durable presentations independently: a successfully applied ACK
56
+ // with a lost response already removed the event from subsequent pages.
57
+ const presented = inbox.db.prepare('SELECT * FROM v2_presence_notices WHERE presented=1 AND acknowledged=0 ORDER BY rowid LIMIT 10').all();
58
+ for (const row of presented) { if (!await permitted()) return; await reconcile(row); }
59
+ inbox.transaction(() => inbox.db.prepare('DELETE FROM v2_presence_notices WHERE acknowledged=1 AND acknowledged_at<=?').run(now() - 8 * 86400000));
60
+ const page = await call((_, options) => client.presenceEvents({ limit: 10, ...(cursor ? { cursor } : {}) }, options), {});
61
+ if (!Array.isArray(page?.events) || page.events.length > 10 || Buffer.byteLength(JSON.stringify(page)) > 65536
62
+ || page.next_cursor !== null && (typeof page.next_cursor !== 'string' || !/^(0|[1-9][0-9]{0,15})$/.test(page.next_cursor))) throw Error('presence_page_invalid');
63
+ // One bounded page per minute, including empty continuations. Restart at the
64
+ // beginning after each scan so newly added events are never skipped forever.
65
+ cursor = page.next_cursor ?? undefined;
66
+ for (const event of page.events) {
67
+ const payload = presencePayload(event), eventID = payload.metadata.presence_event_id;
68
+ if (!await permitted()) return;
69
+ let row = get(eventID);
70
+ if (row) { if (!row.acknowledged) await reconcile(row); continue; }
71
+ // Keep confirmed tombstones eight days. At capacity, fail closed rather
72
+ // than evicting an uncertain record and duplicating a native notice.
73
+ if (inbox.db.prepare('SELECT count(*) n FROM v2_presence_notices').get().n >= 10000) { diagnostic('native_presence_capacity'); return; }
74
+ const nativeID = 'msg_psst_notice_' + randomUUID().replaceAll('-', ''), serialized = canonical(payload);
75
+ inbox.transaction(() => inbox.db.prepare('INSERT INTO v2_presence_notices(event_id,native_id,payload) VALUES(?,?,?)').run(eventID, nativeID, serialized),
76
+ { growth: Buffer.byteLength(serialized) * 2 + 8192 });
77
+ let invoked = false;
78
+ try {
79
+ if (!await permitted()) return;
80
+ await call((input, options) => { invoked = true; return session.synthetic(input, options); }, {
81
+ sessionID: binding.sessionID, id: nativeID, ...payload, delivery: 'queue', resume: true });
82
+ if (await permitted()) await reconcile(get(eventID));
83
+ } finally {
84
+ // Only proven non-invocation permits removal. A lost/mismatched receipt
85
+ // stays pinned across restart and can only reconcile through context.
86
+ if (!invoked) inbox.transaction(() => inbox.db.prepare('DELETE FROM v2_presence_notices WHERE event_id=?').run(eventID));
87
+ }
88
+ }
89
+ } };
90
+ }
@@ -0,0 +1,21 @@
1
+ import { basename } from 'node:path';
2
+
3
+ export const DEFAULT_PROJECT_ENV = 'AI_AGENTS_SANDBOX_PROJECT_NAME';
4
+ const unsafe = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/u;
5
+ function label(value) {
6
+ if (value === undefined || value === null || value === '') return null;
7
+ if (typeof value !== 'string' || !value.isWellFormed() || [...value].length > 256 || unsafe.test(value)) throw Error('invalid_project_metadata');
8
+ return value.trim() || null;
9
+ }
10
+ /** Local operator settings only; never accept an environment selector from a peer/tool. */
11
+ export function projectMetadata(config = {}, env = process.env, cwd) {
12
+ const explicit = label(config.project);
13
+ if (explicit !== null) return explicit;
14
+ const key = env.PSST_PROJECT_ENV ?? config.projectEnv ?? DEFAULT_PROJECT_ENV;
15
+ if (typeof key !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(key)) throw Error('invalid_project_environment');
16
+ const selected = label(env[key]);
17
+ if (selected !== null) return selected;
18
+ if (typeof cwd !== 'string') return null;
19
+ const fallback = label(basename(cwd.replaceAll('\\', '/')));
20
+ return fallback === '.' || fallback === '..' ? null : fallback;
21
+ }
@@ -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 = 'f32ddc79ec7b812062ed3f6de7b9a0c12a7128f3';
5
+ const HASHES = Object.freeze({
6
+ "reference/inbox.mjs": "fbf0fa6096996cb9bc3f928e55f3eca496420880a03460e993dda3417b32ad88",
7
+ "reference/client.mjs": "e8ab94e6de6b5afb17b1dded9839fefa26481fef87ed715b041c09d41dbc93d5",
8
+ "reference/runtime.mjs": "8fdaa105b2e81aeaabb628131671989f951148f602844bca6819af827269f2c3",
9
+ "reference/tools.mjs": "e1836d5c33ed80a9e15c1ca9d07888c9db3caf40aeb2a115c813f74a45077c6e",
10
+ "reference/mcp-stdio.mjs": "88c61c995f9285ef7e22c401d7f0cf50fefc7dd6c76ae79c6bcc94097a33a5c3",
11
+ "reference/errors.mjs": "85b0d0736c5a439f7c66697c3b1ce6cee998cda744dd3169c856ebf2b82bd2a7",
12
+ "src/protocol.mjs": "651f7d99987b58621070c5f24107ac621a09e325880506bc2e73cfe998d5b6aa"
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 = 'f32ddc79ec7b812062ed3f6de7b9a0c12a7128f3';
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,16 @@
1
+ // Portable OpenCode 2.2.5 RPC definition. Plain JSON Schema deliberately avoids
2
+ // pulling the server SDK (and its Effect runtime) into the native TUI bundle.
3
+ const sessionID = { type: 'string', minLength: 1, maxLength: 256 };
4
+ const brokerURL = { type: 'string', minLength: 1, maxLength: 2048 };
5
+ const input = (properties = {}, required = []) => ({ type: 'object', properties: { sessionID, ...properties }, required: ['sessionID', ...required], additionalProperties: false });
6
+ const output = { type: 'object', additionalProperties: true };
7
+ export const SETUP_RPC = {
8
+ id: 'matuto.psst.setup',
9
+ methods: {
10
+ status: { input: input({ broker_url: brokerURL }), output },
11
+ setup: { input: input({ broker_url: brokerURL, replacement_confirmed: { type: 'boolean' }, replacement_request_id: { type: 'string', minLength: 1, maxLength: 128 } }, ['broker_url']), output },
12
+ cancel: { input: input(), output },
13
+ },
14
+ events: {},
15
+ };
16
+ export default SETUP_RPC;
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+
3
+ // The native facade needs only a schema builder and an identity definition helper.
4
+ export function tool(definition) { return definition; }
5
+ tool.schema = z;
@@ -0,0 +1,26 @@
1
+ /** An owner's admitted tool calls must settle before its private ledger closes. */
2
+ export function createToolScope({ closedError = () => new Error('tool_scope_closed'), abortedError = () => new Error('tool_call_aborted') } = {}) {
3
+ const controller = new AbortController(), pending = new Set();
4
+ let closed = false, closing;
5
+ return Object.freeze({
6
+ run(task, callerSignal) {
7
+ if (closed) return Promise.reject(closedError());
8
+ const signal = callerSignal instanceof AbortSignal
9
+ ? AbortSignal.any([controller.signal, callerSignal]) : controller.signal;
10
+ const operation = Promise.resolve().then(() => {
11
+ if (closed) throw closedError();
12
+ if (signal.aborted) throw abortedError();
13
+ return task(signal);
14
+ });
15
+ pending.add(operation);
16
+ void operation.then(() => pending.delete(operation), () => pending.delete(operation));
17
+ return operation;
18
+ },
19
+ close() {
20
+ if (closing) return closing;
21
+ closed = true; controller.abort();
22
+ closing = Promise.allSettled([...pending]).then(() => {});
23
+ return closing;
24
+ },
25
+ });
26
+ }
@@ -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,12 @@
1
+ # Pinned Psst runtime candidate
2
+
3
+ Seven runtime/protocol files are copied byte-for-byte from Psst candidate commit
4
+ `f32ddc79ec7b812062ed3f6de7b9a0c12a7128f3` and verified by `src/reference-pin.mjs`. This is a local, unreleased
5
+ candidate, not evidence of registry publication or deployed broker support.
6
+ Source repository: https://github.com/matuto-ai/psst . MIT license retained.
7
+
8
+ The new outgoing history/content/resend/cancel APIs require the matching broker
9
+ candidate. Existing protocol-v1 send/receive remains compatible. Older brokers
10
+ return an explicit unsupported HTTP error for new routes; no local success is fabricated.
11
+ Approval/enrollment helpers and test-only broker fixtures retain their separate
12
+ original pins. Connector display and read acknowledgment changes live in `src/`.