totemlayer 0.1.96 → 0.1.115

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 (54) hide show
  1. package/README.md +48 -6
  2. package/bin/automatic-runtime.mjs +2 -1
  3. package/bin/cloud-supervisor.mjs +44 -0
  4. package/bin/codex-mission-bridge.mjs +6 -2
  5. package/bin/configured-native-installation.mjs +57 -0
  6. package/bin/connected-agent-service.mjs +5 -1
  7. package/bin/device-login.mjs +3 -1
  8. package/bin/inbox-queue.mjs +8 -5
  9. package/bin/mcp.mjs +18 -5
  10. package/bin/mission-agent-service.mjs +13 -4
  11. package/bin/native/darwin-lifetime +0 -0
  12. package/bin/native/darwin-lifetime.json +1 -1
  13. package/bin/native-claude-login.mjs +131 -0
  14. package/bin/native-claude-receiver.mjs +272 -0
  15. package/bin/native-cli-process.py +105 -0
  16. package/bin/native-harness-environment.mjs +20 -0
  17. package/bin/native-hermes-bridge.mjs +99 -0
  18. package/bin/native-hermes-installation.mjs +9 -0
  19. package/bin/native-hermes-mcp.mjs +107 -0
  20. package/bin/native-hermes-mcp.py +205 -0
  21. package/bin/native-hermes-metadata.py +40 -0
  22. package/bin/native-hermes-process.py +62 -0
  23. package/bin/native-hermes-receiver.mjs +28 -0
  24. package/bin/native-inbox-installation.mjs +144 -0
  25. package/bin/native-inbox-receiver.mjs +133 -0
  26. package/bin/native-kilo-installation.mjs +4 -0
  27. package/bin/native-kilo-receiver.mjs +1 -0
  28. package/bin/native-mcp-source.mjs +244 -0
  29. package/bin/native-opencode-bridge.mjs +108 -0
  30. package/bin/native-opencode-installation.mjs +15 -0
  31. package/bin/native-opencode-mcp.mjs +112 -0
  32. package/bin/native-opencode-receiver.mjs +22 -0
  33. package/bin/native-pi-bridge.mjs +170 -0
  34. package/bin/native-pi-installation.mjs +9 -0
  35. package/bin/native-pi-mcp.mjs +139 -0
  36. package/bin/native-pi-receiver.mjs +28 -0
  37. package/bin/network-retry.mjs +9 -1
  38. package/bin/platform-client.mjs +2 -5
  39. package/bin/runtime-installation.mjs +9 -4
  40. package/bin/sdk/errors.d.ts +1 -1
  41. package/bin/sdk/errors.js +1 -1
  42. package/bin/sdk/listener.d.ts +1 -0
  43. package/bin/sdk/listener.js +1 -1
  44. package/bin/sdk/transport.js +1 -1
  45. package/bin/setup-assets/api-manifest.json +1 -1
  46. package/bin/setup-assets/manifest.json +1 -1
  47. package/bin/setup-assets/setup.backup.md +2 -2
  48. package/bin/setup-assets/setup.md +2 -2
  49. package/bin/startup-diagnostics.mjs +19 -2
  50. package/bin/strict-installation.mjs +29 -3
  51. package/bin/supervisor-watchdog.mjs +44 -0
  52. package/bin/totem.mjs +6 -0
  53. package/bin/windows-private-path.mjs +227 -32
  54. package/package.json +2 -2
@@ -0,0 +1,244 @@
1
+ import { randomUUID, createHash } from "node:crypto";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import { privateDirectory, privateRead, privateWrite } from "./runtime-installation.mjs";
5
+ import { acquireSupervisorLock } from "./runtime-lock.mjs";
6
+
7
+ const fail = code => Object.assign(new Error(code), { code });
8
+ const uuid = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
9
+ const stopped = new Set(["LISTENER_STOPPED", "LISTENER_PAUSED", "AGENT_IDENTITY_MISMATCH", "LISTENER_ALREADY_ACTIVE", "LISTENER_KIND_MISMATCH", "AGENT_AUTH_REQUIRED", "MCP_AUTOMATIC_START_UNAVAILABLE", "MCP_NATIVE_INCOMING_UNAVAILABLE", "MCP_TOOL_UNAVAILABLE", "NATIVE_PI_MCP_CONFIG_CHANGED", "NATIVE_PI_MCP_CONFIG_INVALID", "NATIVE_PI_VERSION_CHANGED"]);
10
+ const expired = new Set(["LISTENER_EXPIRED", "RUNTIME_SUPERSEDED", "LISTENER_NOT_READY", "READINESS_EXPIRED"]);
11
+ export function validNativePeerScope(scope) {
12
+ if (scope === undefined) return true;
13
+ if (!scope || typeof scope !== "object" || Array.isArray(scope) || Object.keys(scope).length !== 2 ||
14
+ typeof scope.conversationId !== "string" || !uuid.test(scope.conversationId) || typeof scope.after !== "string")
15
+ return false;
16
+ const after = Date.parse(scope.after);
17
+ return Number.isFinite(after) && new Date(after).toISOString() === scope.after;
18
+ }
19
+
20
+ export function unwrapNativeMcp(result) {
21
+ let value = result?.structuredContent;
22
+ if (!value) {
23
+ try { value = JSON.parse(result?.content?.find(item => item.type === "text")?.text); }
24
+ catch { throw fail("MCP_RESPONSE_INVALID"); }
25
+ }
26
+ if (value?.error || result?.isError) {
27
+ const backend = value?.error;
28
+ const error = fail(/^[A-Z0-9_]{1,100}$/.test(backend?.code ?? "") ? backend.code : "MCP_CALL_FAILED");
29
+ if (Number.isInteger(backend?.status)) error.status = backend.status;
30
+ if (Number.isSafeInteger(backend?.retryAfterSeconds) && backend.retryAfterSeconds > 0 && backend.retryAfterSeconds <= 86400) error.retryAfterSeconds = backend.retryAfterSeconds;
31
+ throw error;
32
+ }
33
+ if (!value || typeof value !== "object") throw fail("MCP_RESPONSE_INVALID");
34
+ return value.data ?? value;
35
+ }
36
+ const readMissing = async path => privateRead(path).catch(error => { if (error.code !== "ENOENT") throw error; return undefined; });
37
+
38
+ /** One durable, model-free checker. onSnapshot enqueues work synchronously;
39
+ * it must not wait for model execution. Server unread queues remain authoritative.
40
+ */
41
+ export async function runNativeMcpSource(config, options) {
42
+ if (!uuid.test(config?.agentId ?? "") || !["PRODUCTION", "SANDBOX"].includes(config.environment) ||
43
+ typeof config.directory !== "string" || !isAbsolute(config.directory) || /[\0\r\n]/.test(config.directory) ||
44
+ (config.hostKind !== undefined && !["local", "hosted"].includes(config.hostKind)) ||
45
+ (config.receivingMode !== undefined && !["fallback", "native"].includes(config.receivingMode)) ||
46
+ !validNativePeerScope(config.peerScope) ||
47
+ !/^[a-f0-9]{64}$/.test(config.bindingFingerprint ?? "") || typeof config.ownerChats !== "boolean" ||
48
+ typeof options?.connect !== "function" || typeof options.onSnapshot !== "function") throw fail("NATIVE_SOURCE_CONFIG_INVALID");
49
+ const intervalMs = options.intervalMs ?? 5000;
50
+ if (!Number.isSafeInteger(intervalMs) || intervalMs < 10 || intervalMs > 10000) throw fail("NATIVE_SOURCE_CONFIG_INVALID");
51
+ const now = options.now ?? Date.now, signal = options.signal;
52
+ await privateDirectory(config.directory);
53
+ const release = await acquireSupervisorLock(join(config.directory, "source-owner"));
54
+ const path = join(config.directory, "receive.json"), statusPath = join(config.directory, "status.json"), pausePath = join(config.directory, "paused.json");
55
+ const nativeMode = config.receivingMode === "native";
56
+ const bindingParts = [config.agentId, config.environment, config.bindingFingerprint, config.ownerChats];
57
+ if (nativeMode) bindingParts.push("native"); // Retain existing fallback bindings unchanged.
58
+ const peerScope = config.peerScope;
59
+ const peerAfter = peerScope ? Date.parse(peerScope.after) : undefined;
60
+ if (peerScope) bindingParts.push([peerScope.conversationId, peerScope.after]);
61
+ const binding = createHash("sha256").update(JSON.stringify(bindingParts)).digest("hex");
62
+ let surface, state, failures = 0, unexpected = 0, identityAt = 0, identityRequired = true, recovery = true;
63
+ const sleep = async ms => { try { await (options.wait ?? delay)(ms, undefined, { signal }); } catch (error) { if (!signal?.aborted) throw error; } };
64
+ const status = extra => privateWrite(statusPath, { version: 1, pid: process.pid, agentId: config.agentId, intervalMs, updatedAt: new Date(now()).toISOString(), ...extra });
65
+ try {
66
+ state = await readMissing(path) ?? { version: 1, binding, cursor: "0", messageCursor: "0", notified: {} };
67
+ if (state.version !== 1 || state.binding !== binding || !state.notified || typeof state.notified !== "object" || Array.isArray(state.notified) ||
68
+ ![state.cursor, state.messageCursor].every(cursor => typeof cursor === "string" && cursor.length <= 4096) ||
69
+ (state.runId !== undefined && !uuid.test(state.runId)) ||
70
+ (state.retryAt !== undefined && (!Number.isSafeInteger(state.retryAt) || state.retryAt < 0)) ||
71
+ Object.entries(state.notified).some(([id, at]) => !/^[mc]:/.test(id) || !uuid.test(id.slice(2)) || !Number.isSafeInteger(at)) ||
72
+ (state.ownerCommands !== undefined && (!config.ownerChats || !state.ownerCommands || typeof state.ownerCommands !== "object" || Array.isArray(state.ownerCommands) || Object.keys(state.ownerCommands).length > 128 || Object.entries(state.ownerCommands).some(([id, chatId]) => !uuid.test(id) || !uuid.test(chatId))))) throw fail("NATIVE_SOURCE_STATE_CHANGED");
73
+ async function call(name, args = {}) {
74
+ return unwrapNativeMcp(await surface.call(name, args, { signal: AbortSignal.any([
75
+ ...(signal ? [signal] : []), AbortSignal.timeout(10000),
76
+ ]) }));
77
+ }
78
+ async function collect(name, args) {
79
+ const rows = [], seen = new Set(); let cursor;
80
+ do {
81
+ const page = await call(name, { ...args, ...(cursor ? { cursor } : {}) });
82
+ if (!Array.isArray(page.data) || rows.length + page.data.length > 2000) throw fail("MCP_RESPONSE_INVALID");
83
+ rows.push(...page.data);
84
+ cursor = page.nextCursor;
85
+ if (cursor && (typeof cursor !== "string" || cursor.length > 4096 || seen.has(cursor))) throw fail("MCP_CURSOR_STALLED");
86
+ if (cursor) seen.add(cursor);
87
+ } while (cursor && !signal?.aborted);
88
+ return rows;
89
+ }
90
+ async function identity() {
91
+ const own = await call("totem_get_identity");
92
+ if (own.id !== config.agentId || own.environment !== config.environment) throw fail("AGENT_IDENTITY_MISMATCH");
93
+ const current = own.runtime?.currentConnection;
94
+ if (["LISTENER_STOPPED", "LISTENER_PAUSED"].includes(current?.errorCode)) throw fail("LISTENER_PAUSED");
95
+ if (nativeMode) {
96
+ const seen = Date.parse(current?.lastSeenAt ?? "");
97
+ if (current?.listenerKind !== "mcp" || current.connectionState !== "connected" || current.state !== "online" ||
98
+ current.listenerReady !== true || !Number.isFinite(seen) || seen > now() + 5000 || now() - seen > 45000) throw fail("MCP_NATIVE_RECEIVE_NOT_READY");
99
+ }
100
+ identityAt = now();
101
+ identityRequired = false;
102
+ }
103
+ while (!signal?.aborted) {
104
+ if (await readMissing(pausePath)) { await status({ state: "paused" }); return { state: "paused" }; }
105
+ if (state.retryAt > now()) {
106
+ await status({ state: "reconnecting", retryAt: state.retryAt });
107
+ await sleep(Math.min(1000, state.retryAt - now())); continue;
108
+ }
109
+ const started = now(); let backoff = 0;
110
+ try {
111
+ surface ??= await options.connect({ signal });
112
+ if (nativeMode && surface.nativeIncoming !== true) throw fail("MCP_NATIVE_INCOMING_UNAVAILABLE");
113
+ let identityChecked = false;
114
+ if (identityRequired || (!nativeMode && (!state.runId || now() - identityAt >= 30000))) {
115
+ await identity(); identityChecked = true;
116
+ }
117
+ const continuingFallback = !nativeMode && state.runId && !state.readinessReply;
118
+ if (!nativeMode && !state.runId) {
119
+ const start = surface.listTools().find(tool => tool.name === "totem_start_listener");
120
+ // An older schema cannot atomically preserve owner pause. Never retry
121
+ // without the flag or convert a policy denial to an explicit resume.
122
+ if (start?.inputSchema?.properties?.automatic?.type !== "boolean") throw fail("MCP_AUTOMATIC_START_UNAVAILABLE");
123
+ const lease = await call("totem_start_listener", { runId: randomUUID(), hostKind: config.hostKind ?? "local", automatic: true });
124
+ if (!uuid.test(lease.runId ?? "")) throw fail("MCP_RESPONSE_INVALID");
125
+ state.runId = lease.runId; state.readinessReply = lease.readinessReply;
126
+ await privateWrite(path, state);
127
+ }
128
+ async function readFeed() {
129
+ let feed; const pages = new Set();
130
+ do {
131
+ feed = nativeMode
132
+ ? await call("totem_get_activity", { cursor: state.cursor, messageCursor: state.messageCursor, limit: 100 })
133
+ : await call("totem_listen", { runId: state.runId, cursor: state.cursor, messageCursor: state.messageCursor,
134
+ ...(state.readinessReply ? { readinessReply: state.readinessReply } : {}), timeoutSeconds: 0, limit: 100 });
135
+ if (typeof feed.activity?.cursor !== "string" || typeof feed.messages?.cursor !== "string") throw fail("MCP_RESPONSE_INVALID");
136
+ state.cursor = feed.activity.cursor; state.messageCursor = feed.messages.cursor;
137
+ delete state.readinessReply; await privateWrite(path, state);
138
+ if (feed.activity.nextCursor || feed.messages.nextCursor) {
139
+ const pair = JSON.stringify([state.cursor, state.messageCursor]);
140
+ if (pages.has(pair) || pages.size > 100) throw fail("MCP_CURSOR_STALLED");
141
+ pages.add(pair);
142
+ }
143
+ } while ((feed.activity.nextCursor || feed.messages.nextCursor) && !signal?.aborted);
144
+ if (!nativeMode && feed.listenerReady !== true) throw fail("LISTENER_NOT_READY");
145
+ return feed;
146
+ }
147
+ const unreadPeers = { direction: "inbound", unread: true, limit: 100 };
148
+ if (peerScope) unreadPeers.conversationId = peerScope.conversationId;
149
+ // Finish the initial fallback readiness exchange before reading queues.
150
+ // Established listeners can overlap independent reads, but a failed
151
+ // feed or queue read must suppress the entire wake snapshot.
152
+ if (!nativeMode && !continuingFallback) await readFeed();
153
+ const results = await Promise.allSettled([
154
+ nativeMode && !identityChecked ? identity() : undefined,
155
+ nativeMode || continuingFallback ? readFeed() : undefined,
156
+ collect("totem_list_messages", unreadPeers),
157
+ config.ownerChats ? collect("totem_list_chat_commands", { limit: 100 }) : [],
158
+ ]);
159
+ const failed = results.find(result => result.status === "rejected");
160
+ if (failed) throw failed.reason;
161
+ let [, , messages, commands] = results.map(result => result.value);
162
+ if (peerScope)
163
+ messages = messages.filter(row => row?.conversationId === peerScope.conversationId &&
164
+ typeof row.createdAt === "string" && Date.parse(row.createdAt) > peerAfter);
165
+ const availableCommands = new Set(commands.map(row => row.id));
166
+ if (config.ownerChats) {
167
+ state.ownerCommands ??= {};
168
+ for (const row of commands) {
169
+ if (!uuid.test(row.id) || !uuid.test(row.chatId) || (state.ownerCommands[row.id] && state.ownerCommands[row.id] !== row.chatId)) throw fail("MCP_RESPONSE_INVALID");
170
+ if (!state.ownerCommands[row.id] && Object.keys(state.ownerCommands).length >= 128) continue;
171
+ state.ownerCommands[row.id] = row.chatId;
172
+ }
173
+ // Claiming a website command removes it from list_chat_commands.
174
+ // Queue absence is never completion: retain its journal until the
175
+ // original chat confirms a terminal run, including after a crash.
176
+ const chats = new Map();
177
+ for (const [id, chatId] of Object.entries(state.ownerCommands)) {
178
+ if (availableCommands.has(id)) continue;
179
+ if (!chats.has(chatId)) chats.set(chatId, await call("totem_get_chat", { chatId, limit: 1 }));
180
+ const detail = chats.get(chatId), run = detail.runs?.find(row => row.id === id);
181
+ if (detail.chat?.id !== chatId || detail.chat.agentId !== config.agentId || !run || !["queued", "running", "completed", "failed", "canceled"].includes(run.state)) throw fail("MCP_OWNER_COMMAND_UNVERIFIED");
182
+ if (["completed", "failed", "canceled"].includes(run.state)) delete state.ownerCommands[id];
183
+ else if (run.state === "queued") availableCommands.add(id);
184
+ }
185
+ commands = Object.keys(state.ownerCommands).map(id => ({ id }));
186
+ }
187
+ const ids = [...new Set([...messages.map(row => "m:" + row.id), ...commands.map(row => "c:" + row.id)])];
188
+ const readyIds = ids.filter(id => id.startsWith("m:") || availableCommands.has(id.slice(2)));
189
+ if (ids.some(id => !uuid.test(id.slice(2)))) throw fail("MCP_RESPONSE_INVALID");
190
+ const fresh = readyIds.filter(id => recovery || !state.notified[id] || now() - state.notified[id] >= 120000);
191
+ if (fresh.length) await identity(); // Recheck owner pause before waking work.
192
+ const at = now();
193
+ // Delivery to this callback is a wake hint, never a processed/ack proof.
194
+ // Persist owner/chat bindings before dispatch can claim the command.
195
+ if (config.ownerChats) await privateWrite(path, state);
196
+ const queued = options.onSnapshot({ ids, readyIds, fresh, checkedAt: new Date(at).toISOString() });
197
+ if (queued?.then) throw fail("NATIVE_SOURCE_ASYNC_SNAPSHOT");
198
+ for (const id of fresh) state.notified[id] = at;
199
+ state.notified = Object.fromEntries(Object.entries(state.notified).filter(([id]) => ids.includes(id)).slice(-512));
200
+ recovery = false; failures = 0; unexpected = 0; delete state.retryAt;
201
+ state.lastCheckAt = new Date(at).toISOString(); await privateWrite(path, state);
202
+ await status({ state: "listening", listenerReady: true, lastCheckAt: state.lastCheckAt, pendingPeers: messages.length, pendingOwnerChats: commands.length });
203
+ } catch (error) {
204
+ if (signal?.aborted) break;
205
+ const code = /^[A-Z0-9_]{1,100}$/.test(error.code ?? "") ? error.code : "NATIVE_SOURCE_CHECK_FAILED";
206
+ if (expired.has(code)) { delete state.runId; delete state.readinessReply; }
207
+ failures++;
208
+ const transient = expired.has(code) || [408, 429, 500, 502, 503, 504].includes(error.status) || ["NETWORK_ERROR", "TIMEOUT", "RUN_CANCELLED", "ENOTFOUND", "EAI_AGAIN", "ECONNRESET", "ETIMEDOUT", "NATIVE_PI_MCP_CLOSED", "NATIVE_HERMES_MCP_CLOSED", "NATIVE_PI_MCP_CALL_FAILED", "NATIVE_HERMES_MCP_CALL_FAILED", "NATIVE_OPENCODE_MCP_CLOSED", "NATIVE_KILO_MCP_CLOSED", "NATIVE_OPENCODE_MCP_CALL_FAILED", "NATIVE_KILO_MCP_CALL_FAILED", "NATIVE_OPENCODE_MCP_AUTH_REFRESH_REQUIRED", "NATIVE_KILO_MCP_AUTH_REFRESH_REQUIRED", "MCP_NATIVE_RECEIVE_NOT_READY"].includes(code);
209
+ unexpected = transient ? 0 : unexpected + 1;
210
+ const fatal = stopped.has(code) || /^NATIVE_(?:HERMES|OPENCODE|KILO|CLAUDE)_(?:MCP_CONFIG_CHANGED|MCP_CONFIG_INVALID|MCP_PROFILE_CHANGED|VERSION_CHANGED|MCP_AUTH_UNSUPPORTED)$/.test(code) || [401, 403].includes(error.status) || unexpected >= 5;
211
+ backoff = Math.max(intervalMs, Math.min(30000, intervalMs * 2 ** Math.min(failures - 1, 10)), (error.retryAfterSeconds ?? 0) * 1000);
212
+ state.retryAt = now() + backoff; await privateWrite(path, state);
213
+ await status({ state: fatal ? "paused" : "reconnecting", errorCode: code, retryAt: state.retryAt });
214
+ if (fatal) {
215
+ await privateWrite(pausePath, { code, at: new Date(now()).toISOString() });
216
+ return { state: "paused", code };
217
+ }
218
+ // A timed-out native call may retire its entire MCP client. Reusing
219
+ // that handle turns a short service restart into repeated cancellation
220
+ // failures and an automatic failure pause. Reconnect only after the
221
+ // saved cooldown, and fence the new connection with a fresh identity.
222
+ // Readiness and explicit server cooldown responses keep a healthy
223
+ // channel open; a native incoming channel may need time to become ready.
224
+ const keepChannel = expired.has(code) || code === "MCP_NATIVE_RECEIVE_NOT_READY" ||
225
+ [429, 500, 502, 503, 504].includes(error.status);
226
+ if (!keepChannel) {
227
+ const retired = surface;
228
+ surface = undefined;
229
+ identityRequired = true;
230
+ try { await retired?.close(); } catch { /* Retain the original failure and cooldown. */ }
231
+ }
232
+ }
233
+ await sleep(backoff ? Math.min(1000, backoff) : Math.max(0, intervalMs - (now() - started)));
234
+ }
235
+ await status({ state: "stopped" });
236
+ return { state: "stopped" };
237
+ } catch (error) {
238
+ const code = /^[A-Z0-9_]{1,100}$/.test(error.code ?? "") ? error.code : "NATIVE_SOURCE_STATE_INVALID";
239
+ await status({ state: "error", errorCode: code }).catch(() => {});
240
+ throw fail(code);
241
+ } finally {
242
+ try { await surface?.close(); } finally { await release(); }
243
+ }
244
+ }
@@ -0,0 +1,108 @@
1
+ import { spawn, execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { open, unlink, readFile, lstat } from "node:fs/promises";
4
+ import { randomUUID, createHash } from "node:crypto";
5
+ import { join, isAbsolute } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { StringDecoder } from "node:string_decoder";
8
+ import { localClientEnvironment } from "./platform-client.mjs";
9
+
10
+ const execute = promisify(execFile), fail = code => Object.assign(new Error(code), { code });
11
+
12
+ /** Uses an existing dedicated native session and native model OAuth. Configuration
13
+ * is inspected before and after each turn; a process exit alone is not a receipt.
14
+ */
15
+ export function createOpenCodeNativeBridge(binding, options = {}) {
16
+ if (!["opencode", "kilo"].includes(binding?.kind) || binding.dedicated !== true ||
17
+ !/^ses_[A-Za-z0-9]{12,80}$/.test(binding.sessionId ?? "") ||
18
+ ![binding.binary, binding.python, binding.profile, binding.configPath, binding.cwd].every(p => typeof p === "string" && isAbsolute(p) && !/[\0\r\n]/.test(p)) ||
19
+ ![binding.model, binding.provider].every(s => typeof s === "string" && /^[a-zA-Z0-9._:-]{1,128}$/.test(s)) ||
20
+ !/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(binding.nativeVersion ?? "") ||
21
+ !/^[a-f0-9]{64}$/.test(binding.policyFingerprint ?? "")) throw fail("NATIVE_OPENCODE_BINDING_INVALID");
22
+ const saved = structuredClone(binding), prefix = "NATIVE_" + saved.kind.toUpperCase();
23
+ const env = localClientEnvironment(options.env ?? process.env);
24
+ for (const key of Object.keys(env)) if (/^(TOTEM|AGENTTRUST)_|^(OPENAI_API_KEY|ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|OPENCODE_CONFIG_DIR|KILO_CONFIG)$/i.test(key)) delete env[key];
25
+ Object.assign(env, { XDG_CONFIG_HOME: join(saved.profile, "config"), XDG_DATA_HOME: join(saved.profile, "data"), XDG_CACHE_HOME: join(saved.profile, "cache"),
26
+ [saved.kind === "opencode" ? "OPENCODE_CONFIG" : "KILO_CONFIG"]: saved.configPath, DO_NOT_TRACK: "1", OPENCODE_DISABLE_CODEBASE_INDEXING: "true" });
27
+ let closed = false, busy = false, controller, active;
28
+ async function exportMetadata() {
29
+ const path = join(saved.profile, ".totem-session-inspect-" + randomUUID());
30
+ const file = await open(path, "wx+", 0o600);
31
+ await unlink(path); // Native stdout stays a private regular file, avoiding its 128KiB pipe truncation.
32
+ try {
33
+ await new Promise((resolve, reject) => {
34
+ const child = spawn(saved.binary, ["--pure", "export", saved.sessionId, "--sanitize"], { env, cwd: saved.cwd, windowsHide: true, stdio: ["ignore", file.fd, "pipe"] });
35
+ let bytes = 0, failed;
36
+ const timer = setTimeout(() => { failed = prefix + "_METADATA_TIMEOUT"; child.kill(); }, 10000);
37
+ child.stderr.on("data", chunk => { bytes += chunk.length; if (bytes > 65536) { failed = prefix + "_METADATA_OUTPUT_LIMIT"; child.kill(); } });
38
+ child.once("error", () => { failed = prefix + "_METADATA_UNAVAILABLE"; });
39
+ child.once("close", code => { clearTimeout(timer); if (failed || code !== 0) reject(fail(failed ?? prefix + "_METADATA_UNAVAILABLE")); else resolve(); });
40
+ });
41
+ const { size } = await file.stat();
42
+ if (size < 2 || size > 16 * 1024 * 1024) throw fail(prefix + "_METADATA_OUTPUT_LIMIT");
43
+ const buffer = Buffer.alloc(size), result = await file.read(buffer, 0, size, 0);
44
+ if (result.bytesRead !== size) throw fail(prefix + "_METADATA_UNAVAILABLE");
45
+ const value = JSON.parse(buffer.toString("utf8"));
46
+ return { sessionId: value.info?.id, model: value.info?.model?.id, provider: value.info?.model?.providerID,
47
+ variant: value.info?.model?.variant ?? "default", messages: value.messages?.length, routes: [...new Set((value.messages ?? []).filter(m => m.info?.role === "assistant").map(m => m.info.providerID + "/" + m.info.modelID))] };
48
+ } finally { await file.close(); }
49
+ }
50
+ async function inspect() {
51
+ if (closed) throw fail(prefix + "_CLOSED");
52
+ const info = await lstat(saved.configPath);
53
+ if (!info.isFile() || info.isSymbolicLink() || (typeof process.getuid === "function" && info.uid !== process.getuid()) ||
54
+ (process.platform !== "win32" && (info.mode & 0o777) !== 0o600) ||
55
+ createHash("sha256").update(await readFile(saved.configPath)).digest("hex") !== saved.policyFingerprint) throw fail(prefix + "_POLICY_CHANGED");
56
+ const version = await (options.execute ?? execute)(saved.binary, ["--pure", "--version"], { env, cwd: saved.cwd, timeout: 10000, maxBuffer: 65536, windowsHide: true });
57
+ if (version.stdout.trim() !== saved.nativeVersion) throw fail(prefix + "_VERSION_CHANGED");
58
+ const metadata = await (options.readMetadata ?? exportMetadata)();
59
+ if (metadata.sessionId !== saved.sessionId) throw fail(prefix + "_SESSION_CHANGED");
60
+ if ((metadata.variant ?? "default") !== "default" || metadata.model !== saved.model || metadata.provider !== saved.provider ||
61
+ !Array.isArray(metadata.routes) || metadata.routes.length !== 1 || metadata.routes[0] !== saved.provider + "/" + saved.model) throw fail(prefix + "_MODEL_CHANGED");
62
+ if (!Number.isSafeInteger(metadata.messages) || metadata.messages < 1) throw fail(prefix + "_METADATA_UNAVAILABLE");
63
+ return metadata;
64
+ }
65
+ async function runProcess(prompt, signal, timeoutMs) {
66
+ return new Promise((resolve, reject) => {
67
+ const args = [fileURLToPath(new URL("./native-cli-process.py", import.meta.url)), saved.binary, "--pure", "run", "--model", saved.provider + "/" + saved.model, "--format", "json", "--session", saved.sessionId, prompt];
68
+ let child;
69
+ try { child = (options.spawn ?? spawn)(saved.python, args, { env, cwd: saved.cwd, windowsHide: true, stdio: ["pipe", "pipe", "pipe"] }); }
70
+ catch { return reject(fail(prefix + "_START_FAILED")); }
71
+ let output = "", bytes = 0, failure, force;
72
+ const decoder = new StringDecoder("utf8");
73
+ const stop = code => { if (failure) return; failure = code; child.stdin.end(); child.kill("SIGTERM"); force = setTimeout(() => child.kill("SIGKILL"), 2000); };
74
+ child.stdin.on("error", () => {});
75
+ const abort = () => stop(prefix + "_CANCELLED"), timer = setTimeout(() => stop(prefix + "_TURN_TIMEOUT"), timeoutMs);
76
+ signal.addEventListener("abort", abort, { once: true }); if (signal.aborted) abort();
77
+ child.stdout.on("data", chunk => { bytes += chunk.length; if (bytes > 2 * 1024 * 1024) stop(prefix + "_OUTPUT_LIMIT"); else output += decoder.write(chunk); });
78
+ child.stderr.on("data", chunk => { bytes += chunk.length; if (bytes > 2 * 1024 * 1024) stop(prefix + "_OUTPUT_LIMIT"); });
79
+ child.once("error", () => { failure ??= prefix + "_START_FAILED"; });
80
+ child.once("close", code => {
81
+ clearTimeout(timer); clearTimeout(force); signal.removeEventListener("abort", abort);
82
+ if (failure || code !== 0) return reject(fail(failure ?? prefix + "_EXIT_FAILED"));
83
+ output += decoder.end();
84
+ let records;
85
+ try { records = output.trim().split("\n").map(line => JSON.parse(line)); } catch { return reject(fail(prefix + "_PROTOCOL_INVALID")); }
86
+ const sessions = [...new Set(records.map(row => row.sessionID).filter(Boolean))];
87
+ if (records.some(row => row.type === "error") || sessions.length !== 1 || sessions[0] !== saved.sessionId) return reject(fail(prefix + "_TRANSCRIPT_UNVERIFIED"));
88
+ resolve();
89
+ });
90
+ });
91
+ }
92
+ return { open: inspect, inspect,
93
+ async run(prompt, { signal, timeoutMs = 120000 } = {}) {
94
+ if (busy) throw fail(prefix + "_BUSY");
95
+ if (closed || signal?.aborted) throw fail(prefix + "_CANCELLED");
96
+ if (typeof prompt !== "string" || !prompt.trim() || Buffer.byteLength(prompt) > 65536 || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 900000) throw fail(prefix + "_PROMPT_INVALID");
97
+ busy = true; controller = new AbortController();
98
+ const combined = AbortSignal.any([controller.signal, ...(signal ? [signal] : [])]);
99
+ active = (async () => { const before = await inspect(); if (combined.aborted) throw fail(prefix + "_CANCELLED");
100
+ await runProcess(prompt, combined, timeoutMs); const after = await inspect();
101
+ if (combined.aborted) throw fail(prefix + "_CANCELLED");
102
+ if (after.messages <= before.messages) throw fail(prefix + "_TRANSCRIPT_UNVERIFIED");
103
+ return { nativeProcessExited: true, nativeTranscriptAdvanced: true, sessionId: after.sessionId, model: after.model, provider: after.provider, totemReplyVerified: false };
104
+ })();
105
+ try { return await active; } finally { busy = false; controller = undefined; active = undefined; }
106
+ }, async close() { closed = true; controller?.abort(); await active?.catch(() => {}); },
107
+ };
108
+ }
@@ -0,0 +1,15 @@
1
+ import { createOpenCodeNativeBridge } from "./native-opencode-bridge.mjs";
2
+ import { createOpenCodeMcpSurface } from "./native-opencode-mcp.mjs";
3
+ import { installNativeInboxReceiver } from "./native-inbox-installation.mjs";
4
+
5
+ export async function installOpenCodeNativeReceiver(root, config, options = {}) {
6
+ return installNativeCliReceiver("opencode", root, config, options);
7
+ }
8
+ export async function installNativeCliReceiver(kind, root, config, options = {}) {
9
+ if (!["opencode", "kilo"].includes(kind) || config?.native?.kind !== kind || config.mcp?.kind !== kind ||
10
+ config.native.profile !== config.mcp.profile || config.native.configPath !== config.mcp.configPath)
11
+ throw Object.assign(new Error("Native profile binding changed"), { code: "NATIVE_" + (kind === "kilo" ? "KILO" : "OPENCODE") + "_INSTALL_CONFIG_INVALID" });
12
+ return installNativeInboxReceiver(root, config, { kind, createBridge: createOpenCodeNativeBridge, createSurface: createOpenCodeMcpSurface,
13
+ files: ["native-opencode-bridge.mjs", "native-opencode-mcp.mjs", "native-cli-process.py", "native-mcp-source.mjs", "native-opencode-receiver.mjs", "native-inbox-receiver.mjs", ...(kind === "kilo" ? ["native-kilo-receiver.mjs"] : [])],
14
+ }, options);
15
+ }
@@ -0,0 +1,112 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { isAbsolute, join, resolve } from "node:path";
5
+ import { lstat, realpath } from "node:fs/promises";
6
+ import { privateRead } from "./runtime-installation.mjs";
7
+ import { localClientEnvironment } from "./platform-client.mjs";
8
+ import { configureEnvironmentProxy } from "./runtime-proxy.mjs";
9
+
10
+ const execute = promisify(execFile), fail = code => Object.assign(new Error(code), { code });
11
+ const checkers = new Set(["totem_get_identity", "totem_start_listener", "totem_listen", "totem_list_messages", "totem_list_chat_commands", "totem_get_chat", "totem_get_conversation", "totem_get_activity"]);
12
+ const canonical = value => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value;
13
+ export const openCodeMcpBindingFingerprint = server => createHash("sha256").update(JSON.stringify(canonical(server))).digest("hex");
14
+
15
+ /** Read the native client's own cache in place. Only the official native CLI
16
+ * refreshes it; no credential is exported, copied, or written by this checker.
17
+ */
18
+ export async function createOpenCodeMcpSurface(binding, options = {}) {
19
+ if (!["opencode", "kilo"].includes(binding?.kind) ||
20
+ ![binding.binary, binding.profile, binding.configPath, binding.cwd].every(p => typeof p === "string" && isAbsolute(p) && !/[\0\r\n]/.test(p)) ||
21
+ !/^[\w-]{1,80}$/.test(binding.serverName ?? "") || !/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(binding.nativeVersion ?? "") ||
22
+ !/^[a-f0-9]{64}$/.test(binding.serverFingerprint ?? "")) throw fail("NATIVE_OPENCODE_MCP_BINDING_INVALID");
23
+ const saved = structuredClone(binding), prefix = "NATIVE_" + saved.kind.toUpperCase();
24
+ const env = localClientEnvironment(options.env ?? process.env);
25
+ for (const key of Object.keys(env)) if (/^(TOTEM|AGENTTRUST)_|^(OPENAI_API_KEY|ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|OPENCODE_CONFIG_DIR|KILO_CONFIG)$/i.test(key)) delete env[key];
26
+ Object.assign(env, { XDG_CONFIG_HOME: join(saved.profile, "config"), XDG_DATA_HOME: join(saved.profile, "data"), XDG_CACHE_HOME: join(saved.profile, "cache"),
27
+ [saved.kind === "opencode" ? "OPENCODE_CONFIG" : "KILO_CONFIG"]: saved.configPath, DO_NOT_TRACK: "1" });
28
+ const cache = join(saved.profile, "data", saved.kind, "mcp-auth.json");
29
+ const now = options.now ?? Date.now;
30
+ let client, connectedToken, connectedUntil, closed = false, closing;
31
+ const refreshRequired = () => fail(prefix + "_MCP_AUTH_REFRESH_REQUIRED");
32
+ async function inspect() {
33
+ if (closed) throw fail(prefix + "_MCP_CLOSED");
34
+ const profile = await lstat(saved.profile);
35
+ if (!profile.isDirectory() || profile.isSymbolicLink() || (typeof process.getuid === "function" && profile.uid !== process.getuid()) ||
36
+ (process.platform !== "win32" && (profile.mode & 0o777) !== 0o700)) throw fail(prefix + "_MCP_PROFILE_CHANGED");
37
+ for (const path of [saved.configPath, cache]) if (await realpath(path) !== resolve(path)) throw fail(prefix + "_MCP_PROFILE_CHANGED");
38
+ const config = await privateRead(saved.configPath), server = config.mcp?.[saved.serverName];
39
+ if (server?.enabled === false) throw fail("LISTENER_PAUSED");
40
+ let url;
41
+ try { url = new URL(server?.url); } catch { /* Reject below. */ }
42
+ if (server?.type !== "remote" || server.oauth === false || Object.keys(server.headers ?? {}).length ||
43
+ !url || url.origin !== "https://totemlayer.com" || url.pathname !== "/mcp" || url.username || url.password || url.hash || url.search ||
44
+ openCodeMcpBindingFingerprint(server) !== saved.serverFingerprint) throw fail(prefix + "_MCP_CONFIG_CHANGED");
45
+ const entry = (await privateRead(cache))[saved.serverName];
46
+ if (entry?.serverUrl !== server.url || typeof entry.tokens?.accessToken !== "string" || !entry.tokens.accessToken ||
47
+ !Number.isFinite(entry.tokens.expiresAt) || entry.tokens.expiresAt <= 0 || entry.tokens.expiresAt > Number.MAX_SAFE_INTEGER / 1000) throw fail("AGENT_AUTH_REQUIRED");
48
+ return { server, entry };
49
+ }
50
+ async function native(args, signal) {
51
+ return (options.execute ?? execute)(saved.binary, ["--pure", ...args], { env, cwd: saved.cwd, signal, timeout: 20000, maxBuffer: 65536, windowsHide: true });
52
+ }
53
+ async function close() { closed = true; closing ??= Promise.resolve().then(() => client?.close()); await closing; }
54
+ try {
55
+ const version = await native(["--version"], options.signal);
56
+ if (version.stdout.trim() !== saved.nativeVersion) throw fail(prefix + "_VERSION_CHANGED");
57
+ let current = await inspect();
58
+ if (current.entry.tokens.expiresAt * 1000 <= now() + 15000) {
59
+ await native(["mcp", "list"], options.signal);
60
+ current = await inspect();
61
+ // A saved refresh token can still recover after a missed renewal.
62
+ const { expiresAt, refreshToken } = current.entry.tokens;
63
+ if (expiresAt * 1000 <= now() + 15000) {
64
+ if (expiresAt * 1000 < now() - 60000 && !(typeof refreshToken === "string" && refreshToken))
65
+ throw fail("AGENT_AUTH_REQUIRED");
66
+ throw refreshRequired();
67
+ }
68
+ }
69
+ if (options.signal?.aborted) throw fail("RUN_CANCELLED");
70
+ await (options.configureProxy ?? configureEnvironmentProxy)();
71
+ const sdk = options.createClient && options.createTransport ? undefined : await import("@modelcontextprotocol/client");
72
+ client = options.createClient ? options.createClient() : new sdk.Client({ name: "totem-native-" + saved.kind + "-checker", version: "1" });
73
+ connectedToken = current.entry.tokens.accessToken;
74
+ connectedUntil = current.entry.tokens.expiresAt * 1000;
75
+ const transport = options.createTransport ? options.createTransport() : new sdk.StreamableHTTPClientTransport(new URL(current.server.url), {
76
+ requestInit: { headers: { authorization: "Bearer " + current.entry.tokens.accessToken } },
77
+ });
78
+ await client.connect(transport, { signal: options.signal, timeout: 20000 });
79
+ const advertised = (await client.listTools({}, { signal: options.signal, timeout: 20000 })).tools;
80
+ return {
81
+ nativeIncoming: false,
82
+ listTools: () => advertised.filter(tool => checkers.has(tool.name)),
83
+ async call(name, args, { signal } = {}) {
84
+ if (!checkers.has(name)) throw fail("MCP_TOOL_UNAVAILABLE");
85
+ const current = await inspect();
86
+ if (signal?.aborted) throw fail("RUN_CANCELLED");
87
+ // requestInit binds this client to one native token. A rotated cache or
88
+ // expiry must retire that channel before its next request; reconnect
89
+ // uses the native client's refresh flow and preserves the receiver lease.
90
+ if (current.entry.tokens.accessToken !== connectedToken || connectedUntil <= now() + 15000) throw refreshRequired();
91
+ try { return await client.callTool({ name, arguments: args }, undefined, { signal, timeout: 20000 }); }
92
+ catch (error) {
93
+ if (signal?.aborted) throw fail("RUN_CANCELLED");
94
+ if (error.status === 401) {
95
+ const latest = await inspect();
96
+ if (latest.entry.tokens.accessToken !== connectedToken || connectedUntil <= now() + 15000) throw refreshRequired();
97
+ }
98
+ if (error.name === "TimeoutError" || error.code === -32001) throw fail("TIMEOUT");
99
+ const failure = fail(prefix + "_MCP_CALL_FAILED");
100
+ if (Number.isInteger(error.status) && error.status >= 400 && error.status <= 599) failure.status = error.status;
101
+ throw failure;
102
+ }
103
+ }, close,
104
+ };
105
+ } catch (error) {
106
+ await close().catch(() => {});
107
+ if (error.status === 401 && connectedUntil <= now() + 15000) throw refreshRequired();
108
+ const failure = fail(/^[A-Z0-9_]{1,100}$/.test(error.code ?? "") ? error.code : options.signal?.aborted ? "RUN_CANCELLED" : prefix + "_MCP_UNAVAILABLE");
109
+ if (Number.isInteger(error.status) && error.status >= 400 && error.status <= 599) failure.status = error.status;
110
+ throw failure;
111
+ }
112
+ }
@@ -0,0 +1,22 @@
1
+ import { resolve } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { privateRead } from "./runtime-installation.mjs";
4
+ import { createOpenCodeNativeBridge } from "./native-opencode-bridge.mjs";
5
+ import { createOpenCodeMcpSurface } from "./native-opencode-mcp.mjs";
6
+ import { runNativeInboxReceiver } from "./native-inbox-receiver.mjs";
7
+
8
+ export function runOpenCodeNativeReceiver(config, options = {}) {
9
+ const kind = config?.native?.kind;
10
+ if (!["opencode", "kilo"].includes(kind) || config.mcp?.kind !== kind || config.mcp.profile !== config.native.profile || config.mcp.configPath !== config.native.configPath)
11
+ throw Object.assign(new Error("Native profile binding changed"), { code: "NATIVE_OPENCODE_RECEIVER_CONFIG_INVALID" });
12
+ return runNativeInboxReceiver(config, { kind, createBridge: createOpenCodeNativeBridge, createSurface: createOpenCodeMcpSurface }, options);
13
+ }
14
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
15
+ const controller = new AbortController(), stop = () => controller.abort();
16
+ process.once("SIGTERM", stop); process.once("SIGINT", stop);
17
+ try {
18
+ const result = await runOpenCodeNativeReceiver(await privateRead(resolve(process.argv[2])), { signal: controller.signal });
19
+ if (result.state === "paused") await new Promise(resolve => { if (controller.signal.aborted) resolve(); else controller.signal.addEventListener("abort", resolve, { once: true }); });
20
+ } catch (error) { process.stderr.write(/^[A-Z0-9_]{1,100}$/.test(error.code ?? "") ? error.code + "\n" : "NATIVE_OPENCODE_RECEIVER_FAILED\n"); process.exitCode = 1; }
21
+ finally { process.off("SIGTERM", stop); process.off("SIGINT", stop); }
22
+ }