totemlayer 0.1.67 → 0.1.74

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 (42) hide show
  1. package/README.md +11 -13
  2. package/bin/agent-runner.mjs +23 -1
  3. package/bin/automatic-runtime.mjs +23 -12
  4. package/bin/codex-chat-bridge.mjs +72 -38
  5. package/bin/codex-desktop-wake.mjs +47 -20
  6. package/bin/codex-human-chat-bridge.mjs +57 -21
  7. package/bin/codex-mission-bridge.mjs +167 -0
  8. package/bin/connected-agent-service.mjs +12 -2
  9. package/bin/executor-diagnostic.mjs +169 -61
  10. package/bin/installation.mjs +8 -16
  11. package/bin/local-inspector.mjs +6 -0
  12. package/bin/mcp-install.mjs +102 -5
  13. package/bin/mcp.mjs +43 -1
  14. package/bin/mission-worker.mjs +62 -39
  15. package/bin/model-adapter.mjs +30 -1
  16. package/bin/native/darwin-lifetime +0 -0
  17. package/bin/native/darwin-lifetime.json +1 -1
  18. package/bin/network-contract.mjs +125 -0
  19. package/bin/network-runtime.mjs +367 -315
  20. package/bin/observability.mjs +336 -0
  21. package/bin/platform-client.mjs +6 -3
  22. package/bin/plugin-connect.mjs +10 -0
  23. package/bin/provider-diagnostics.mjs +40 -11
  24. package/bin/runtime-installation.mjs +4 -1
  25. package/bin/runtime-lock.mjs +43 -6
  26. package/bin/sdk/index.d.ts +1 -0
  27. package/bin/sdk/index.js +1 -1
  28. package/bin/setup-assets/api-connect.backup.md +129 -0
  29. package/bin/setup-assets/api-connect.md +129 -0
  30. package/bin/setup-assets/api-manifest.json +1 -0
  31. package/bin/setup-assets/manifest.json +1 -0
  32. package/bin/setup-assets/setup.backup.md +178 -0
  33. package/bin/setup-assets/setup.md +178 -0
  34. package/bin/setup-guide.mjs +39 -0
  35. package/bin/setup-receipts.mjs +181 -0
  36. package/bin/strict-codex-adapter.mjs +456 -0
  37. package/bin/strict-installation.mjs +206 -0
  38. package/bin/strict-setup-jobs.mjs +253 -0
  39. package/bin/strict-setup.mjs +385 -0
  40. package/bin/totem.mjs +11 -1
  41. package/package.json +4 -3
  42. package/scripts/prepare.mjs +2 -0
@@ -0,0 +1,385 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const SETUP_CHECKS = Object.freeze([
4
+ "guide",
5
+ "model",
6
+ "identity",
7
+ "transport",
8
+ "receiver",
9
+ "startup",
10
+ "recovery",
11
+ "nativeWake",
12
+ "peerReply",
13
+ "ownerChat",
14
+ ]);
15
+ export const GUIDE_UNAVAILABLE = "Setup incomplete: setup.md could not be read in full.";
16
+ const token = (value) =>
17
+ typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,319}$/.test(value);
18
+ const uuid = (value) =>
19
+ typeof value === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value);
20
+ const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
21
+ const stop = (code) => {
22
+ throw Object.assign(new Error(code), { code });
23
+ };
24
+
25
+ export function setupBinding(identity, host, transport, connection) {
26
+ if (
27
+ !uuid(identity?.id) ||
28
+ !token(identity.address) ||
29
+ !uuid(identity.organization?.id) ||
30
+ !["PRODUCTION", "SANDBOX"].includes(identity.environment) ||
31
+ !token(host?.name) ||
32
+ !token(host.model) ||
33
+ host.model === "default" ||
34
+ !token(host.sessionId) ||
35
+ !["mcp", "api"].includes(transport) ||
36
+ !/^[a-f0-9]{64}$/.test(connection?.fingerprint ?? "")
37
+ )
38
+ stop("SETUP_BINDING_UNVERIFIED");
39
+ let origin;
40
+ try {
41
+ const url = new URL(connection.origin);
42
+ if (
43
+ url.username ||
44
+ url.password ||
45
+ url.search ||
46
+ url.hash ||
47
+ url.pathname !== "/" ||
48
+ (url.protocol !== "https:" &&
49
+ !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
50
+ )
51
+ stop("SETUP_BINDING_UNVERIFIED");
52
+ origin = url.origin;
53
+ } catch {
54
+ stop("SETUP_BINDING_UNVERIFIED");
55
+ }
56
+ return digest([
57
+ identity.id,
58
+ identity.address,
59
+ identity.organization.id,
60
+ identity.environment,
61
+ origin,
62
+ connection.fingerprint,
63
+ host.name,
64
+ host.model,
65
+ host.sessionId,
66
+ transport,
67
+ ]);
68
+ }
69
+
70
+ function sameHost(before, after) {
71
+ return (
72
+ after?.modelVerified === true &&
73
+ before.name === after.name &&
74
+ before.model === after.model &&
75
+ before.sessionId === after.sessionId &&
76
+ before.policyDigest === after.policyDigest
77
+ );
78
+ }
79
+
80
+ /** This controller accepts observations from installed adapters, never model-supplied proofs. */
81
+ export async function runStrictSetup(adapter, options = {}) {
82
+ const checks = Object.fromEntries(SETUP_CHECKS.map((name) => [name, false]));
83
+ let identity,
84
+ host,
85
+ transport,
86
+ binding,
87
+ receiver,
88
+ proofs,
89
+ apiReason,
90
+ operation = "readGuide";
91
+ const progress = async (stage) => options.progress?.({ stage, checks: { ...checks } });
92
+ const report = (code) => ({
93
+ version: 1,
94
+ complete: !code && SETUP_CHECKS.every((name) => checks[name]),
95
+ state:
96
+ code === "RECEIVER_INTENTIONALLY_PAUSED"
97
+ ? "paused"
98
+ : !code && SETUP_CHECKS.every((name) => checks[name])
99
+ ? "complete"
100
+ : "incomplete",
101
+ ...(code ? { code } : {}),
102
+ message:
103
+ code === "GUIDE_UNAVAILABLE"
104
+ ? GUIDE_UNAVAILABLE
105
+ : !code && SETUP_CHECKS.every((name) => checks[name])
106
+ ? "Totem setup complete; required checks verified."
107
+ : checks.identity
108
+ ? "Connection verified; automatic receiving setup incomplete"
109
+ : "Setup incomplete",
110
+ ...(host ? { host: host.name, model: host.model, sessionId: host.sessionId } : {}),
111
+ ...(identity && checks.identity
112
+ ? {
113
+ agentId: identity.id,
114
+ address: identity.address,
115
+ organizationId: identity.organization.id,
116
+ environment: identity.environment,
117
+ }
118
+ : {}),
119
+ ...(transport ? { transport } : {}),
120
+ ...(apiReason ? { apiReason } : {}),
121
+ ...(receiver
122
+ ? {
123
+ intervalSeconds: receiver.intervalSeconds,
124
+ measuredCheckSeconds: receiver.measuredCheckSeconds,
125
+ }
126
+ : {}),
127
+ ...(checks.peerReply
128
+ ? {
129
+ peerVerification: {
130
+ messageId: proofs.peer.messageId,
131
+ replyId: proofs.peer.replyId,
132
+ receivedAt: proofs.peer.receivedAt,
133
+ repliedAt: proofs.peer.repliedAt,
134
+ exactlyOneReply: true,
135
+ acknowledgedAfterReply: true,
136
+ },
137
+ }
138
+ : {}),
139
+ ...(checks.ownerChat
140
+ ? {
141
+ ownerChatVerification: {
142
+ commandId: proofs.ownerChat.commandId,
143
+ chatId: proofs.ownerChat.chatId,
144
+ eventId: proofs.ownerChat.eventId,
145
+ leaseRenewed: true,
146
+ completedInOriginalChat: true,
147
+ queuedAt: proofs.ownerChat.queuedAt,
148
+ completedAt: proofs.ownerChat.completedAt,
149
+ },
150
+ }
151
+ : {}),
152
+ checks: { ...checks },
153
+ remaining: SETUP_CHECKS.filter((name) => !checks[name]),
154
+ });
155
+ try {
156
+ // No host inspection, approval or setup mutation is permitted before a complete guide.
157
+ let guide;
158
+ for (const reader of ["primary", "alternate"]) {
159
+ try {
160
+ const value = await adapter.readGuide(reader);
161
+ if (
162
+ value?.complete === true &&
163
+ value.integrityVerified === true &&
164
+ /^[a-f0-9]{64}$/.test(value.sha256 ?? "") &&
165
+ typeof value.text === "string" &&
166
+ value.text.startsWith("# Set up Totem") &&
167
+ value.text.endsWith("<!-- totem-setup:end -->\n")
168
+ ) {
169
+ guide = value;
170
+ break;
171
+ }
172
+ } catch {
173
+ /* Try the independent supported reader before stopping. */
174
+ }
175
+ }
176
+ if (!guide) return report("GUIDE_UNAVAILABLE");
177
+ checks.guide = true;
178
+ await progress("guide_verified");
179
+ operation = "inspectHost";
180
+ const inspected = await adapter.inspect();
181
+ const proposedHost = inspected.host;
182
+ if (
183
+ !token(proposedHost?.name) ||
184
+ !token(proposedHost.model) ||
185
+ proposedHost.model === "default" ||
186
+ /(?:tn_(?:live|test)_|Bearer|secret|token=)/i.test(proposedHost.model) ||
187
+ !token(proposedHost.sessionId) ||
188
+ !/^[a-f0-9]{64}$/.test(proposedHost.policyDigest ?? "") ||
189
+ proposedHost.modelVerified !== true
190
+ )
191
+ stop("CURRENT_MODEL_UNVERIFIED");
192
+ host = proposedHost;
193
+ checks.model = true;
194
+ if (inspected.policyDenied) stop("HOST_POLICY_DENIED");
195
+ if (inspected.identityConflict) stop("AGENT_IDENTITY_MISMATCH");
196
+ // Existing transport, identity, credential generation and intentional pauses win.
197
+ transport = inspected.connection?.transport;
198
+ if (!transport) {
199
+ if (inspected.ownerSelectedTransport === "api" && inspected.apiSupported === true) {
200
+ transport = "api";
201
+ apiReason = "The owner explicitly selected API for this host.";
202
+ } else if (inspected.customMcp === true) transport = "mcp";
203
+ else if (
204
+ inspected.customMcp === false &&
205
+ inspected.apiSupported === true &&
206
+ typeof inspected.missingMcpCapability === "string" &&
207
+ inspected.missingMcpCapability.trim()
208
+ ) {
209
+ transport = "api";
210
+ apiReason = inspected.missingMcpCapability.slice(0, 500);
211
+ } else stop("HOST_MCP_CAPABILITY_UNVERIFIED");
212
+ }
213
+ if (!["mcp", "api"].includes(transport)) stop("TRANSPORT_UNSUPPORTED");
214
+ if (transport === "api" && !inspected.connection) {
215
+ let apiGuide;
216
+ for (const reader of ["primary", "alternate"]) {
217
+ try {
218
+ const value = await adapter.readApiGuide?.(reader);
219
+ if (
220
+ value?.complete === true &&
221
+ value.integrityVerified === true &&
222
+ /^[a-f0-9]{64}$/.test(value.sha256 ?? "") &&
223
+ value.text?.startsWith("# Connect an agent to the Totem API") &&
224
+ value.text.endsWith("<!-- totem-api-setup:end -->\n")
225
+ ) {
226
+ apiGuide = value;
227
+ break;
228
+ }
229
+ } catch {}
230
+ }
231
+ if (!apiGuide) {
232
+ checks.guide = false;
233
+ stop("API_GUIDE_UNAVAILABLE");
234
+ }
235
+ const delivery = await adapter.explainApi(apiReason);
236
+ if (delivery?.ownerVisible !== true || delivery.deliveredBeforeRequest !== true)
237
+ stop("API_EXPLANATION_NOT_DELIVERED");
238
+ }
239
+ // A stopped connection is inspected read-only, without login, repair, recovery or tests.
240
+ const connection =
241
+ inspected.connection ?? (inspected.paused ? undefined : await adapter.connect(transport));
242
+ if (!connection) stop("CONNECTION_APPROVAL_REQUIRED");
243
+ identity = await adapter.identity(connection, transport);
244
+ const expected = inspected.connection?.identity ?? connection.identity;
245
+ if (
246
+ expected &&
247
+ (identity?.id !== expected.id ||
248
+ identity.environment !== expected.environment ||
249
+ (expected.organization?.id && identity.organization?.id !== expected.organization.id))
250
+ )
251
+ stop("AGENT_IDENTITY_MISMATCH");
252
+ if (options.expectedAgentId && identity?.id !== options.expectedAgentId)
253
+ stop("AGENT_IDENTITY_MISMATCH");
254
+ if (options.expectedEnvironment && identity?.environment !== options.expectedEnvironment)
255
+ stop("AGENT_IDENTITY_MISMATCH");
256
+ binding = setupBinding(identity, host, transport, connection);
257
+ checks.identity = true;
258
+ checks.transport =
259
+ transport === "mcp"
260
+ ? connection.mcpIdentityVerified === true
261
+ : connection.apiIdentityVerified === true &&
262
+ connection.inboxVerified === true &&
263
+ connection.installationAcknowledged === true;
264
+ if (!checks.transport) stop("CONNECTION_VERIFICATION_INCOMPLETE");
265
+ if (inspected.paused) return report("RECEIVER_INTENTIONALLY_PAUSED");
266
+ await progress("identity_verified");
267
+ const context = Object.freeze({
268
+ identity,
269
+ host,
270
+ transport,
271
+ binding,
272
+ connectionFingerprint: connection.fingerprint,
273
+ guideSha256: guide.sha256,
274
+ });
275
+ operation = "configureReceiver";
276
+ await adapter.configureReceiver(context);
277
+ receiver = await adapter.readReceiver(context);
278
+ if (receiver?.binding !== binding || receiver.count !== 1 || receiver.sameConnection !== true)
279
+ stop("EXACT_RECEIVER_UNVERIFIED");
280
+ if (receiver.paused) return report("RECEIVER_INTENTIONALLY_PAUSED");
281
+ if (!Number.isFinite(receiver.intervalSeconds) || receiver.intervalSeconds <= 0)
282
+ stop("RECEIVER_INTERVAL_UNVERIFIED");
283
+ // A preserved slower interval or documented fallback remains installed and is reported honestly.
284
+ const operational =
285
+ receiver.running === true &&
286
+ Number.isFinite(receiver.measuredCheckSeconds) &&
287
+ receiver.measuredCheckSeconds > 0 &&
288
+ receiver.independentOfModel === true &&
289
+ receiver.silentWhenIdle === true &&
290
+ receiver.bothQueues === true &&
291
+ receiver.privateState === true;
292
+ checks.receiver = operational && receiver.intervalSeconds === 5;
293
+ checks.startup =
294
+ receiver.startupEnabled === true &&
295
+ receiver.restartOnFailure === true &&
296
+ receiver.persistent === true;
297
+ if (!sameHost(host, await adapter.readHost(context))) {
298
+ checks.model = false;
299
+ stop("CURRENT_MODEL_CHANGED");
300
+ }
301
+ if (!operational || !checks.startup)
302
+ return {
303
+ ...report("RECEIVER_CAPABILITY_INCOMPLETE"),
304
+ intervalSeconds: receiver.intervalSeconds,
305
+ };
306
+ await progress("receiver_verified");
307
+ const recovery = await adapter.verifyRecovery(context);
308
+ checks.recovery =
309
+ recovery?.binding === binding &&
310
+ recovery.newRunVerified === true &&
311
+ recovery.savedStatePreserved === true &&
312
+ recovery.ownerPausePreserved === true &&
313
+ recovery.noDuplicateReceiver === true;
314
+ if (!checks.recovery) return report("RECOVERY_UNVERIFIED");
315
+ await progress("recovery_verified");
316
+ // This requests incoming tests from the owner. It never sends traffic to another agent.
317
+ await adapter.requestTests(context);
318
+ proofs = await adapter.readTests(context);
319
+ const peer = proofs?.peer,
320
+ chat = proofs?.ownerChat;
321
+ checks.nativeWake =
322
+ peer?.binding === binding &&
323
+ peer.nativeSessionId === host.sessionId &&
324
+ peer.newMessageTriggeredWake === true &&
325
+ peer.appearedInNativeChat === true;
326
+ checks.peerReply =
327
+ peer?.binding === binding &&
328
+ peer.nativeSessionId === host.sessionId &&
329
+ peer.appearedInNativeChat === true &&
330
+ uuid(peer.messageId) &&
331
+ uuid(peer.replyId) &&
332
+ peer.correlated === true &&
333
+ peer.exactlyOneReply === true &&
334
+ peer.acknowledgedAfterReply === true &&
335
+ peer.authorized === true &&
336
+ Number.isFinite(peer.receivedAt) &&
337
+ Number.isFinite(peer.repliedAt) &&
338
+ peer.repliedAt >= peer.receivedAt;
339
+ checks.ownerChat =
340
+ chat?.binding === binding &&
341
+ uuid(chat.commandId) &&
342
+ uuid(chat.chatId) &&
343
+ uuid(chat.eventId) &&
344
+ chat.claimed === true &&
345
+ chat.leaseRenewed === true &&
346
+ chat.completedInOriginalChat === true &&
347
+ chat.authorized === true &&
348
+ Number.isFinite(chat.queuedAt) &&
349
+ Number.isFinite(chat.completedAt) &&
350
+ chat.completedAt >= chat.queuedAt;
351
+ if (!sameHost(host, await adapter.readHost(context))) {
352
+ checks.model = false;
353
+ stop("CURRENT_MODEL_CHANGED");
354
+ }
355
+ await progress("tests_checked");
356
+ return report(
357
+ !checks.peerReply || !checks.ownerChat
358
+ ? "AUTHORIZED_TESTS_PENDING"
359
+ : !checks.receiver
360
+ ? "RECEIVER_INTERVAL_LIMITATION"
361
+ : !checks.nativeWake
362
+ ? "NATIVE_WAKE_UNVERIFIED"
363
+ : undefined,
364
+ );
365
+ } catch (error) {
366
+ // Adapter error messages may contain private host diagnostics. Export only bounded codes.
367
+ const code = /^[A-Z][A-Z0-9_]{0,79}$/.test(error?.code ?? "")
368
+ ? error.code
369
+ : "SETUP_ADAPTER_FAILED";
370
+ if (code === "HOST_POLICY_DENIED") {
371
+ const reason =
372
+ typeof error.publicReason === "string"
373
+ ? error.publicReason
374
+ .slice(0, 1000)
375
+ .replace(/tn_(?:live|test)_[A-Za-z0-9_-]+|Bearer\s+\S+/gi, "[redacted]")
376
+ : "The host did not supply a public reason.";
377
+ return { ...report(code), deniedOperation: operation, statedReason: reason };
378
+ }
379
+ return report(code);
380
+ } finally {
381
+ await Promise.resolve()
382
+ .then(() => adapter.close?.())
383
+ .catch(() => {});
384
+ }
385
+ }
package/bin/totem.mjs CHANGED
@@ -1754,6 +1754,11 @@ async function runRfq(options, baseUrl) {
1754
1754
  }
1755
1755
 
1756
1756
  async function main() {
1757
+ if (process.argv[2] === "install-worker") {
1758
+ const { strictSetupWorkerMain } = await import("./strict-setup-jobs.mjs");
1759
+ process.exitCode = await strictSetupWorkerMain(process.argv[3]);
1760
+ return;
1761
+ }
1757
1762
  if (process.argv[2] === "update") {
1758
1763
  const { runtimeUpdateMain } = await import("./managed-mcp.mjs");
1759
1764
  await runtimeUpdateMain(process.argv.slice(3));
@@ -1769,7 +1774,12 @@ async function main() {
1769
1774
  process.exitCode = await executorDiagnosticMain(process.argv.slice(3));
1770
1775
  return;
1771
1776
  }
1772
- if (!process.argv[2] || ["login", "install"].includes(process.argv[2])) {
1777
+ if (process.argv[2] === "install") {
1778
+ const { strictInstallationMain } = await import("./strict-installation.mjs");
1779
+ process.exitCode = await strictInstallationMain(process.argv.slice(3));
1780
+ return;
1781
+ }
1782
+ if (!process.argv[2] || process.argv[2] === "login") {
1773
1783
  const { installationMain } = await import("./installation.mjs");
1774
1784
  process.exitCode = await installationMain(process.argv.slice(3), {});
1775
1785
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totemlayer",
3
- "version": "0.1.67",
3
+ "version": "0.1.74",
4
4
  "description": "Cross-platform CLI for TotemLayer agent identity and authorization",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,9 +33,10 @@
33
33
  "registry": "https://registry.npmjs.org/"
34
34
  },
35
35
  "dependencies": {
36
+ "@modelcontextprotocol/client": "2.3.0",
36
37
  "@modelcontextprotocol/server": "2.3.0",
37
- "undici": "7.30.0",
38
- "zod": "4.4.3"
38
+ "undici": "7.30.1",
39
+ "zod": "4.6.6"
39
40
  },
40
41
  "scripts": {
41
42
  "postinstall": "node bin/installation.mjs --postinstall",
@@ -2,6 +2,7 @@ import { access, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { transform } from "esbuild";
3
3
  import "./build-native.mjs";
4
4
  import { generateNetworkContract } from "../../../scripts/generate-network-contract.mjs";
5
+ import "../../../scripts/build-strict-installer-guide.mjs";
5
6
  await generateNetworkContract();
6
7
  // This candidate rebases coordination on the immutable published 0.1.21 bundle.
7
8
  // Its bundled SDK is the release source; do not rebuild it from an older sibling checkout.
@@ -18,6 +19,7 @@ for (const name of [
18
19
  "runtime-diagnostics.mjs",
19
20
  "codex-chat-bridge.mjs",
20
21
  "codex-human-chat-bridge.mjs",
22
+ "codex-mission-bridge.mjs",
21
23
  "chat-prompt-limit.mjs",
22
24
  "worker-core/adapters.mjs",
23
25
  "worker-core/errors.mjs",