totemlayer 0.1.5 → 0.1.11

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.
@@ -36,7 +36,7 @@ export async function runDockerNodeTests(checkout, signal, runFile) {
36
36
  "--env",
37
37
  "TMPDIR=/tmp",
38
38
  "--mount",
39
- `type=bind,source=${root},target=/workspace`,
39
+ `type=bind,source=${root},target=/workspace,readonly`,
40
40
  "--workdir",
41
41
  "/workspace",
42
42
  NODE_TEST_IMAGE,
@@ -0,0 +1,160 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { privateRead, privateWrite } from "./runtime-installation.mjs";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { ensureInstalledRuntime, installRuntimeService } from "./runtime-installation.mjs";
7
+ import { beginDeviceLogin } from "./device-login.mjs";
8
+ import { detectInstalledProvider, providerArguments } from "./provider-detection.mjs";
9
+
10
+ export async function activateConnection(profile, env = process.env, options = {}) {
11
+ const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(env);
12
+ const { automaticEnabled, setAutomaticEnabled } = await import("./automatic-runtime.mjs");
13
+ const enabled = await automaticEnabled(profile);
14
+ if (enabled) await setAutomaticEnabled(profile, true);
15
+ const service = await (options.installService ?? installRuntimeService)(runtime, {
16
+ ...env,
17
+ ...(profile.agentTokenEnv ? { TOTEM_AGENT_TOKEN_ENV: profile.agentTokenEnv } : {}),
18
+ });
19
+ if (!enabled) return { state: "paused", ...service, entry: runtime.cli };
20
+ const { agentStartMain } = await import("./agent-runner.mjs");
21
+ await (options.start ?? agentStartMain)(
22
+ ["--profile", profile.profileName, "--background", "--json"],
23
+ { env, automatic: true, stdout: { write() {} } },
24
+ );
25
+ return { state: "running", ...service, entry: runtime.cli };
26
+ }
27
+
28
+ export async function installationMain(args = [], options = {}) {
29
+ const env = options.env ?? process.env;
30
+ const stdout = options.stdout ?? process.stdout;
31
+ const flags = parseArgs({
32
+ args,
33
+ strict: true,
34
+ options: {
35
+ provider: { type: "string" },
36
+ "agent-id": { type: "string" },
37
+ "base-url": { type: "string" },
38
+ "no-browser": { type: "boolean" },
39
+ help: { type: "boolean", short: "h" },
40
+ json: { type: "boolean" },
41
+ },
42
+ }).values;
43
+ if (flags.help) {
44
+ stdout.write(
45
+ "totem login [--provider codex|claude|qwen-local|ollama-local|nim-local|command|http] [--no-browser]\nConnect this installation once. Totem securely saves its connection and automatically runs and restarts the listener.\n",
46
+ );
47
+ return 0;
48
+ }
49
+ const environment = {
50
+ ...env,
51
+ ...(flags.provider ? { TOTEM_PROVIDER: flags.provider } : {}),
52
+ ...(flags["agent-id"] ? { TOTEM_AGENT_ID: flags["agent-id"] } : {}),
53
+ ...(flags["base-url"] ? { TOTEM_BASE_URL: flags["base-url"] } : {}),
54
+ };
55
+ const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(environment);
56
+ const service = await (options.installService ?? installRuntimeService)(runtime, environment);
57
+ if (environment.TOTEM_NETWORK_API_KEY || environment.TOTEM_NETWORK_API_KEY_FILE) {
58
+ const provider = await (options.detectProvider ?? detectInstalledProvider)(environment);
59
+ if (!provider) {
60
+ let key = environment.TOTEM_NETWORK_API_KEY;
61
+ if (!key && environment.TOTEM_NETWORK_API_KEY_FILE) {
62
+ const info = await stat(environment.TOTEM_NETWORK_API_KEY_FILE);
63
+ if (
64
+ !info.isFile() ||
65
+ info.size > 8192 ||
66
+ (process.platform !== "win32" && info.mode & 0o077)
67
+ )
68
+ throw new Error("Use a private connection key file.");
69
+ key = (await readFile(environment.TOTEM_NETWORK_API_KEY_FILE, "utf8")).trim();
70
+ }
71
+ if (!/^tn_(?:test|live)_[A-Za-z0-9_-]{43}$/.test(key ?? ""))
72
+ throw new Error("The connection key is invalid.");
73
+ await privateWrite(join(runtime.directory, "connection-ready.json"), {
74
+ key,
75
+ baseUrl: environment.TOTEM_BASE_URL ?? "https://totemlayer.com",
76
+ agentId: environment.TOTEM_AGENT_ID,
77
+ });
78
+ stdout.write(
79
+ "Totem's automatic runtime is installed. Install/sign in to your AI client, then run totem login again.\n",
80
+ );
81
+ return 1;
82
+ }
83
+ const { agentSetupMain } = await import("./agent-setup.mjs");
84
+ return agentSetupMain(
85
+ [
86
+ "--profile",
87
+ environment.TOTEM_AGENT_ID ?? "default",
88
+ ...(environment.TOTEM_AGENT_ID ? ["--agent-id", environment.TOTEM_AGENT_ID] : []),
89
+ ...(await providerArguments(provider, environment)),
90
+ ...(flags.json ? ["--json"] : []),
91
+ ],
92
+ {
93
+ ...options,
94
+ env: environment,
95
+ activateConnection: options.activateConnection ?? activateConnection,
96
+ },
97
+ );
98
+ }
99
+ try {
100
+ await privateRead(join(runtime.directory, "connection-ready.json"));
101
+ stdout.write(
102
+ "Your Totem connection is saved privately. Sign in to your AI client; the listener connects automatically.\n",
103
+ );
104
+ return 0;
105
+ } catch (error) {
106
+ if (error.code !== "ENOENT") throw error;
107
+ }
108
+ // An upgrade reuses private profiles and starts their existing connections.
109
+ const { opendir } = await import("node:fs/promises");
110
+ let saved = false;
111
+ try {
112
+ const profiles = await opendir(join(runtime.root, "connections"));
113
+ for await (const entry of profiles)
114
+ if (
115
+ entry.isDirectory() &&
116
+ (!environment.TOTEM_AGENT_ID || entry.name === environment.TOTEM_AGENT_ID)
117
+ )
118
+ saved = true;
119
+ } catch (error) {
120
+ if (error.code !== "ENOENT") throw error;
121
+ }
122
+ if (saved && options.forceLogin !== true) {
123
+ const report = { installed: true, connection: "saved", automatic: true, service };
124
+ stdout.write(
125
+ flags.json
126
+ ? JSON.stringify(report) + "\n"
127
+ : "Totem's automatic listener is installed and reuses your saved connections.\n",
128
+ );
129
+ return 0;
130
+ }
131
+ const connection = await (options.beginLogin ?? beginDeviceLogin)(runtime, environment, {
132
+ openBrowser: !flags["no-browser"] && environment.TOTEM_NO_BROWSER !== "1",
133
+ });
134
+ const report = {
135
+ installed: true,
136
+ connection: "sign_in_required",
137
+ automatic: true,
138
+ service,
139
+ ...connection,
140
+ };
141
+ stdout.write(
142
+ flags.json
143
+ ? JSON.stringify(report) + "\n"
144
+ : `Totem is installed. Connect your agent once at:\n${connection.url}\nConnection code: ${connection.code.match(/.{1,4}/g).join(" ")}\nThe listener starts automatically after you connect.\n`,
145
+ );
146
+ return 0;
147
+ }
148
+ export async function postinstallMain(env = process.env) {
149
+ if (env.TOTEM_AUTO_START === "0" || env.CI) return 0;
150
+ try {
151
+ return await installationMain([], { env });
152
+ } catch {
153
+ process.stdout.write(
154
+ "Totem is installed. Automatic connection setup needs attention; run totem login to finish it.\n",
155
+ );
156
+ return 0;
157
+ }
158
+ }
159
+ if (process.argv[1] === fileURLToPath(import.meta.url) && process.argv[2] === "--postinstall")
160
+ await postinstallMain();
@@ -1,10 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
- import { stat } from "node:fs/promises";
4
- import { resolve } from "node:path";
3
+ import { stat, lstat } from "node:fs/promises";
4
+ import { resolve, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  import { installedClientInvocation, localClientEnvironment } from "./platform-client.mjs";
8
+ import { runtimeRoot } from "./runtime-installation.mjs";
8
9
 
9
10
  const DEFAULT_ORIGIN = "https://totemlayer.com";
10
11
  const DEFAULT_NAME = "totem-local";
@@ -131,19 +132,28 @@ export async function installTotemMcp(args, environment = process.env, runtime =
131
132
  ];
132
133
  const addArgs =
133
134
  options.client === "codex"
134
- ? ["mcp", "add", options.name, ...envArgs, "--", process.execPath, SERVER_ENTRY, "mcp"]
135
+ ? [
136
+ "mcp",
137
+ "add",
138
+ options.name,
139
+ ...envArgs,
140
+ "--",
141
+ process.execPath,
142
+ runtime.serverEntry ?? SERVER_ENTRY,
143
+ "mcp",
144
+ ]
135
145
  : [
136
146
  "mcp",
137
147
  "add",
148
+ options.name,
138
149
  ...envArgs,
139
150
  "--scope",
140
151
  "user",
141
152
  "--transport",
142
153
  "stdio",
143
- options.name,
144
154
  "--",
145
155
  process.execPath,
146
- SERVER_ENTRY,
156
+ runtime.serverEntry ?? SERVER_ENTRY,
147
157
  "mcp",
148
158
  ];
149
159
  const added = runClient(command, addArgs, environment, runtime);
@@ -169,7 +179,26 @@ export function managedMcpName(profileName) {
169
179
  : `totem_${profileName.slice(0, 24)}_${createHash("sha256").update(profileName).digest("hex").slice(0, 32)}`;
170
180
  }
171
181
 
172
- function managedArgs(profile, name) {
182
+ async function installedServerEntry(profile, environment) {
183
+ if (!profile.stateDirectory) return SERVER_ENTRY;
184
+ const path = join(resolve(profile.stateDirectory, "../../../.."), "runtime", "totem.mjs");
185
+ try {
186
+ const info = await lstat(path);
187
+ if (
188
+ info.isFile() &&
189
+ !info.isSymbolicLink() &&
190
+ info.nlink === 1 &&
191
+ (!process.getuid || info.uid === process.getuid()) &&
192
+ (process.platform === "win32" || (info.mode & 0o777) === 0o600)
193
+ )
194
+ return path;
195
+ } catch {
196
+ /* Connections created without automatic installation retain their entry. */
197
+ }
198
+ return SERVER_ENTRY;
199
+ }
200
+
201
+ function managedArgs(profile, name, entry = SERVER_ENTRY) {
173
202
  const variables = [
174
203
  "--env",
175
204
  `TOTEM_BASE_URL=${profile.baseUrl}`,
@@ -177,24 +206,24 @@ function managedArgs(profile, name) {
177
206
  `TOTEM_NETWORK_API_KEY_FILE=${profile.credentialFile}`,
178
207
  ];
179
208
  return profile.client === "codex"
180
- ? ["mcp", "add", name, ...variables, "--", process.execPath, SERVER_ENTRY, "mcp"]
209
+ ? ["mcp", "add", name, ...variables, "--", process.execPath, entry, "mcp"]
181
210
  : [
182
211
  "mcp",
183
212
  "add",
213
+ name,
184
214
  ...variables,
185
215
  "--scope",
186
216
  "user",
187
217
  "--transport",
188
218
  "stdio",
189
- name,
190
219
  "--",
191
220
  process.execPath,
192
- SERVER_ENTRY,
221
+ entry,
193
222
  "mcp",
194
223
  ];
195
224
  }
196
225
 
197
- function matchesManagedEntry(entry, profile, name) {
226
+ function matchesManagedEntry(entry, profile, name, serverEntry = SERVER_ENTRY) {
198
227
  const transport = entry?.transport;
199
228
  const expectedEnvironment = {
200
229
  TOTEM_BASE_URL: profile.baseUrl,
@@ -209,7 +238,7 @@ function matchesManagedEntry(entry, profile, name) {
209
238
  ) &&
210
239
  transport?.type === "stdio" &&
211
240
  transport.command === process.execPath &&
212
- JSON.stringify(transport.args) === JSON.stringify([SERVER_ENTRY, "mcp"]) &&
241
+ JSON.stringify(transport.args) === JSON.stringify([serverEntry, "mcp"]) &&
213
242
  transport.cwd == null &&
214
243
  (transport.env_vars == null ||
215
244
  (Array.isArray(transport.env_vars) && transport.env_vars.length === 0)) &&
@@ -279,10 +308,11 @@ function reconnectAdvice(profile, name) {
279
308
  }
280
309
 
281
310
  export async function inspectManagedTotemMcp(profile, environment = process.env, runtime = {}) {
311
+ const serverEntry = await installedServerEntry(profile, environment);
282
312
  const inspected = await inspectEntry(profile, localClientEnvironment(environment), runtime);
283
313
  if (
284
314
  inspected.state === "present" &&
285
- matchesManagedEntry(inspected.entry, profile, inspected.name)
315
+ matchesManagedEntry(inspected.entry, profile, inspected.name, serverEntry)
286
316
  )
287
317
  return {
288
318
  name: inspected.name,
@@ -308,6 +338,7 @@ export async function installManagedTotemMcp(
308
338
  environment = process.env,
309
339
  runtime = {},
310
340
  ) {
341
+ const serverEntry = await installedServerEntry(profile, environment);
311
342
  if (!["codex", "claude"].includes(profile.client))
312
343
  fail("Choose an existing AI app for MCP setup.");
313
344
  try {
@@ -337,11 +368,14 @@ export async function installManagedTotemMcp(
337
368
  name,
338
369
  ],
339
370
  safeEnvironment,
340
- runtime,
371
+ { ...runtime, serverEntry },
341
372
  );
342
373
  if (profile.client === "codex") {
343
374
  const after = await inspectEntry(profile, safeEnvironment, runtime);
344
- if (after.state !== "present" || !matchesManagedEntry(after.entry, profile, name))
375
+ if (
376
+ after.state !== "present" ||
377
+ !matchesManagedEntry(after.entry, profile, name, serverEntry)
378
+ )
345
379
  return {
346
380
  name,
347
381
  state: "needs_attention",
@@ -361,7 +395,10 @@ export async function installManagedTotemMcp(
361
395
  };
362
396
  }
363
397
  }
364
- if (inspected.state === "present" && matchesManagedEntry(inspected.entry, profile, name))
398
+ if (
399
+ inspected.state === "present" &&
400
+ matchesManagedEntry(inspected.entry, profile, name, serverEntry)
401
+ )
365
402
  return {
366
403
  name,
367
404
  state: "configured_unverified",
@@ -371,7 +408,21 @@ export async function installManagedTotemMcp(
371
408
  previousProfile?.profileName === profile.profileName &&
372
409
  previousProfile.client === profile.client &&
373
410
  inspected.state === "present" &&
374
- matchesManagedEntry(inspected.entry, previousProfile, name);
411
+ matchesManagedEntry(
412
+ inspected.entry,
413
+ previousProfile,
414
+ name,
415
+ inspected.entry?.transport?.args?.[0],
416
+ ) &&
417
+ (inspected.entry.transport.args[0] === SERVER_ENTRY ||
418
+ new RegExp(
419
+ `^${runtimeRoot(environment)
420
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
421
+ .replaceAll(
422
+ "\\\\",
423
+ "[\\\\/]",
424
+ )}[\\\\/]runtime-packages[\\\\/]\\d+\\.\\d+\\.\\d+[\\\\/]node_modules[\\\\/]totemlayer[\\\\/]bin[\\\\/]totem\\.mjs$`,
425
+ ).test(inspected.entry.transport.args[0]));
375
426
  if (!ownsPrevious)
376
427
  return {
377
428
  name,
@@ -380,10 +431,15 @@ export async function installManagedTotemMcp(
380
431
  };
381
432
  // Codex add can update this exact named entry. No remove occurs: unsupported client
382
433
  // versions fail safely, and an exact read-back is required before reporting an update.
383
- const updated = await managedCall(profile, managedArgs(profile, name), safeEnvironment, runtime);
434
+ const updated = await managedCall(
435
+ profile,
436
+ managedArgs(profile, name, serverEntry),
437
+ safeEnvironment,
438
+ runtime,
439
+ );
384
440
  if (updated.status === 0) {
385
441
  const after = await inspectEntry(profile, safeEnvironment, runtime);
386
- if (after.state === "present" && matchesManagedEntry(after.entry, profile, name))
442
+ if (after.state === "present" && matchesManagedEntry(after.entry, profile, name, serverEntry))
387
443
  return {
388
444
  name,
389
445
  state: "updated_unverified",
package/bin/mcp.mjs CHANGED
@@ -8,7 +8,7 @@ import * as z from "zod/v4";
8
8
  import { Totem } from "./sdk/index.js";
9
9
  import { inspectLocalRepository, verifyLocalRepoClaim } from "./repository.mjs";
10
10
 
11
- const VERSION = "0.1.5";
11
+ const VERSION = "0.1.6";
12
12
  const MAX_KEY_BYTES = 8 * 1024;
13
13
  const MAX_ATTACHMENT_BYTES = 2 * 1024 * 1024;
14
14
  const collaborationSchema = z
@@ -148,6 +148,61 @@ export function createTotemMcpServer(config) {
148
148
  () => client.agent(),
149
149
  );
150
150
 
151
+ register(
152
+ server,
153
+ "totem_list_missions",
154
+ {
155
+ title: "List assigned missions",
156
+ description:
157
+ "Discover durable missions this authenticated agent can participate in. Offline agents must connect their own runtime to execute work.",
158
+ inputSchema: z.object({}),
159
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
160
+ },
161
+ () => client.missions.list(),
162
+ );
163
+
164
+ register(
165
+ server,
166
+ "totem_get_mission",
167
+ {
168
+ title: "Inspect a mission",
169
+ description:
170
+ "Read the mission team, dependencies, owner-approved infrastructure scope, results, and receipts before delegating work.",
171
+ inputSchema: z.object({ missionId: z.uuid() }),
172
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
173
+ },
174
+ ({ missionId }) => client.missions.get(missionId),
175
+ );
176
+
177
+ register(
178
+ server,
179
+ "totem_delegate_mission_task",
180
+ {
181
+ title: "Delegate a mission task",
182
+ description:
183
+ "The mission coordinator assigns a child task to a consented member. Infrastructure deployment requires the selected executor and an approved review dependency; verification depends on a completed deployment. This does not grant permissions or wake an offline agent.",
184
+ inputSchema: z.object({
185
+ missionId: z.uuid(),
186
+ ownerMemberId: z.uuid(),
187
+ parentWorkItemId: z.uuid(),
188
+ dependsOnIds: z.array(z.uuid()).min(1).max(40),
189
+ kind: z.enum([
190
+ "engineering",
191
+ "code",
192
+ "review",
193
+ "infrastructure_deploy",
194
+ "infrastructure_verify",
195
+ ]),
196
+ objective: z.string().min(1).max(10000),
197
+ requiredOutput: z.string().min(1).max(10000),
198
+ idempotencyKey: z.string().min(1).max(128),
199
+ }),
200
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
201
+ },
202
+ ({ missionId, kind, idempotencyKey, ...input }) =>
203
+ client.missions.assign(missionId, { ...input, metadata: { kind } }, { idempotencyKey }),
204
+ );
205
+
151
206
  if (localRepositoryTools)
152
207
  register(
153
208
  server,
@@ -1,3 +1,4 @@
1
+ import { createLocalStaticProvider } from "./mission-infrastructure.mjs";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import { readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
@@ -18,14 +19,34 @@ import {
18
19
  import { repositoryIdFromRemote } from "./repository.mjs";
19
20
 
20
21
  const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
21
- const STAGES = new Set(["proposal", "engineering", "decision", "code", "review", "handoff"]);
22
- const REPOSITORY_STAGES = new Set(["engineering", "code", "review"]);
22
+ const STAGES = new Set([
23
+ "proposal",
24
+ "engineering",
25
+ "decision",
26
+ "code",
27
+ "review",
28
+ "handoff",
29
+ "infrastructure_deploy",
30
+ "infrastructure_verify",
31
+ ]);
32
+ const REPOSITORY_STAGES = new Set([
33
+ "engineering",
34
+ "code",
35
+ "review",
36
+ "infrastructure_deploy",
37
+ "infrastructure_verify",
38
+ ]);
23
39
  const TERMINAL = new Set(["completed", "canceled", "failed"]);
24
40
  const RECOVERABLE = new Set([
25
41
  "ready",
26
42
  "result_settling",
27
43
  "local_test_failure_settling",
28
44
  "claiming",
45
+ "infra_source",
46
+ "infra_source_dispatched",
47
+ "infra_source_settling",
48
+ "infra_reserved",
49
+ "infra_dispatched",
29
50
  ]);
30
51
  const CHECKPOINT_BYTES = 2 * 1024 * 1024;
31
52
 
@@ -129,6 +150,7 @@ export async function runMissionAgentService(
129
150
  nimUrl,
130
151
  ollamaUrl,
131
152
  reasoning = false,
153
+ adapterSettings = {},
132
154
  stateDirectory = join(homedir(), ".totem", "missions"),
133
155
  pollMs = 5000,
134
156
  signal,
@@ -210,11 +232,21 @@ export async function runMissionAgentService(
210
232
  };
211
233
  const workerStateDirectory = join(resolve(stateDirectory), "mission-agents", session.key);
212
234
  const directory = join(workerStateDirectory, agent.id);
235
+ let infrastructureProvider;
213
236
  try {
214
237
  await ensurePrivateRuntimeDirectory(resolve(stateDirectory));
215
238
  await ensurePrivateRuntimeDirectory(join(resolve(stateDirectory), "mission-agents"));
216
239
  await ensurePrivateRuntimeDirectory(workerStateDirectory);
217
240
  await ensurePrivateRuntimeDirectory(directory);
241
+ const getInfrastructureProvider = async () => {
242
+ infrastructureProvider ??= await createLocalStaticProvider({
243
+ directory: join(workerStateDirectory, "local-static"),
244
+ });
245
+ return infrastructureProvider;
246
+ };
247
+ // Restore persisted releases on startup without repeating their deployment operations.
248
+ if ((await readdir(workerStateDirectory)).includes("local-static"))
249
+ await getInfrastructureProvider();
218
250
  const checkpointPath = join(directory, `discovery-${session.key}.json`);
219
251
  const state = await loadCheckpoint(checkpointPath, origin, agent.id);
220
252
  const persist = () => saveCheckpoint(checkpointPath, state);
@@ -276,9 +308,14 @@ export async function runMissionAgentService(
276
308
  nimUrl,
277
309
  ollamaUrl,
278
310
  reasoning,
311
+ adapterSettings,
279
312
  signal,
280
313
  candidateWorkItem: workItem,
281
314
  recoveryOnly,
315
+ infrastructureProvider:
316
+ recoveryOnly || workItem?.metadata?.kind?.startsWith("infrastructure_")
317
+ ? await getInfrastructureProvider()
318
+ : undefined,
282
319
  emit(event) {
283
320
  const type =
284
321
  event.type === "work_started"
@@ -435,6 +472,7 @@ export async function runMissionAgentService(
435
472
  }
436
473
  }
437
474
  } finally {
475
+ await infrastructureProvider?.close();
438
476
  await session.release();
439
477
  report({ type: "stopped" });
440
478
  }