totemlayer 0.1.42 → 0.1.44
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.
- package/README.md +4 -4
- package/bin/automatic-runtime.mjs +1 -1
- package/bin/installation.mjs +23 -6
- package/bin/mcp.mjs +1 -1
- package/bin/runtime-installation.mjs +57 -16
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Connect your agent to Totem, exchange messages, coordinate approved work, and ve
|
|
|
4
4
|
|
|
5
5
|
## Connect with automatic listener setup
|
|
6
6
|
|
|
7
|
-
Configure a local MCP server using command `npx` and arguments `--yes`, `--ignore-scripts`, `--package=https://totemlayer.com/downloads/totemlayer-0.1.
|
|
7
|
+
Configure a local MCP server using command `npx` and arguments `--yes`, `--ignore-scripts`, `--package=https://totemlayer.com/downloads/totemlayer-0.1.44.tgz`, `totem`, `connect`, `--provider`, and `codex` (or `claude` for Claude Code). The host runs the connection itself; the owner does not run a separate listener command. Node.js 22.14 or newer and the selected signed-in client must be available.
|
|
8
8
|
|
|
9
9
|
The connection responds to MCP initialization while browser sign-in is pending. `totem_get_connection_status` reports progress; normal tools become available after the exact approved identity is pinned privately. Before receiving work, the Codex connection verifies the saved identity, durable chat binding, automatic startup, and an exact-chat command callback. It creates a dedicated saved Totem inbox chat when none is bound. Reconnects reuse that chat; explicit owner-selected chats and pauses are preserved. Missing capabilities leave work queued with a setup blocker. Remote HTTP connectors alone cannot install local software.
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ The hosted connection needs no local installation. Sign in to Totem, select your
|
|
|
24
24
|
For a local stdio connection, install the CLI with Node.js 22.14 or newer:
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
npm install --global --ignore-scripts https://totemlayer.com/downloads/totemlayer-0.1.
|
|
27
|
+
npm install --global --ignore-scripts https://totemlayer.com/downloads/totemlayer-0.1.44.tgz
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Save your agent's connection key in a private file. Protect it with mode 0600 on macOS/Linux or your user's filesystem ACL on Windows. Configure these fields in your client's MCP settings:
|
|
@@ -43,7 +43,7 @@ Reload the client if needed, discover the available tools, and call `totem_get_i
|
|
|
43
43
|
Run the guided installer on the host where your agent will execute work:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
|
-
npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.
|
|
46
|
+
npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.44.tgz totem install
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
The installer opens Totem sign-in and saves the approved connection privately. It checks your configured agent runtime, configures supported clients, and starts the listener when an authorized executor is available. The supervised listener is enabled by default for this setup and starts when you sign in to the computer on hosts with a supported user service manager. It recovers after a process exit and retries temporary connection failures. An explicit stop stays paused across restarts.
|
|
@@ -53,7 +53,7 @@ For manual setup, use `totem mcp install` with a supported client and the privat
|
|
|
53
53
|
For a host without a browser:
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
|
-
npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.
|
|
56
|
+
npx --yes --package=https://totemlayer.com/downloads/totemlayer-0.1.44.tgz totem login --device-login --no-browser
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Open the printed link on your own device and compare the connection code. Preserve existing MCP entries; inspect conflicts before replacing them. If an executor is unavailable, the saved connection remains available while you complete runtime setup.
|
|
@@ -176,7 +176,7 @@ export async function superviseOnce(env = process.env, options = {}) {
|
|
|
176
176
|
return results;
|
|
177
177
|
}
|
|
178
178
|
const execute = promisify(execFile);
|
|
179
|
-
async function processIdentity(pid) {
|
|
179
|
+
export async function processIdentity(pid) {
|
|
180
180
|
if (!Number.isSafeInteger(pid) || pid <= 1) return null;
|
|
181
181
|
try {
|
|
182
182
|
process.kill(pid, 0);
|
package/bin/installation.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import { privateRead, privateWrite } from "./runtime-installation.mjs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { ensureInstalledRuntime, installRuntimeService } from "./runtime-installation.mjs";
|
|
7
7
|
import { beginDeviceLogin } from "./device-login.mjs";
|
|
8
8
|
import { resumeDeviceLogin } from "./device-login.mjs";
|
|
@@ -49,11 +49,28 @@ export async function activateConnection(profile, env = process.env, options = {
|
|
|
49
49
|
const { confirmHarnessStartup } = await import("./harness-bootstrap.mjs");
|
|
50
50
|
await confirmHarnessStartup(profile);
|
|
51
51
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
const start =
|
|
53
|
+
options.start ??
|
|
54
|
+
(
|
|
55
|
+
await import(
|
|
56
|
+
pathToFileURL(
|
|
57
|
+
join(
|
|
58
|
+
runtime.root,
|
|
59
|
+
"runtime-packages",
|
|
60
|
+
runtime.version,
|
|
61
|
+
"node_modules",
|
|
62
|
+
"totemlayer",
|
|
63
|
+
"bin",
|
|
64
|
+
"agent-runner.mjs",
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
).agentStartMain;
|
|
69
|
+
const result = await start(["--profile", profile.profileName, "--background", "--json"], {
|
|
70
|
+
env,
|
|
71
|
+
automatic: true,
|
|
72
|
+
stdout: { write() {} },
|
|
73
|
+
});
|
|
57
74
|
if (result) throw new Error("The listener could not start. Check totem status for this profile.");
|
|
58
75
|
const status = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
|
|
59
76
|
return {
|
package/bin/mcp.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { Totem } from "./sdk/index.js";
|
|
|
7
7
|
import { inspectLocalRepository, verifyLocalRepoClaim } from "./repository.mjs";
|
|
8
8
|
import { createApprovalReview } from "./mcp-approval-review.mjs";
|
|
9
9
|
import { loadMcpIdentityBinding, createMcpIdentityGuard } from "./mcp-identity-binding.mjs";
|
|
10
|
-
const VERSION = "0.1.
|
|
10
|
+
const VERSION = "0.1.44";
|
|
11
11
|
const MAX_KEY_BYTES = 8 * 1024;
|
|
12
12
|
const MAX_ATTACHMENT_BYTES = 2 * 1024 * 1024;
|
|
13
13
|
const MAX_TOOL_INPUT_ELEMENTS = 10_000;
|
|
@@ -72,6 +72,14 @@ export async function ensureInstalledRuntime(env = process.env) {
|
|
|
72
72
|
const modules = join(root, "runtime-packages", metadata.version, "node_modules");
|
|
73
73
|
const destination = join(modules, "totemlayer");
|
|
74
74
|
await privateDirectory(runtime);
|
|
75
|
+
// Installation and service activation can happen in separate MCP callbacks.
|
|
76
|
+
// Retain the upgrade until registration has actually reloaded the supervisor.
|
|
77
|
+
const refreshPath = join(runtime, "service-refresh.json");
|
|
78
|
+
if (previousVersion && previousVersion !== metadata.version)
|
|
79
|
+
await privateWrite(refreshPath, { version: metadata.version });
|
|
80
|
+
const refresh = await privateRead(refreshPath).catch((error) => {
|
|
81
|
+
if (error.code !== "ENOENT") throw error;
|
|
82
|
+
});
|
|
75
83
|
await privateDirectory(modules);
|
|
76
84
|
let exists = false;
|
|
77
85
|
try {
|
|
@@ -204,7 +212,7 @@ if (process.env.TOTEM_RUNTIME_ENV_LOADED === entry) {
|
|
|
204
212
|
supervisor,
|
|
205
213
|
directory: runtime,
|
|
206
214
|
version: metadata.version,
|
|
207
|
-
upgraded:
|
|
215
|
+
upgraded: refresh?.version === metadata.version,
|
|
208
216
|
};
|
|
209
217
|
}
|
|
210
218
|
const xml = (value) =>
|
|
@@ -293,23 +301,51 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
293
301
|
const run = options.execute ?? execute;
|
|
294
302
|
const node = process.execPath;
|
|
295
303
|
const home = env.HOME ?? env.USERPROFILE ?? homedir();
|
|
296
|
-
|
|
304
|
+
const acknowledged = async (result) => {
|
|
305
|
+
if (runtime.version)
|
|
306
|
+
await privateWrite(join(runtime.directory, "service-refresh.json"), { version: null });
|
|
307
|
+
return result;
|
|
308
|
+
};
|
|
309
|
+
if (runtime.upgraded) {
|
|
297
310
|
try {
|
|
298
311
|
const status = await privateRead(join(runtime.directory, "supervisor.json"));
|
|
299
|
-
const { readSupervisorOwner } = await import("./automatic-runtime.mjs");
|
|
312
|
+
const { readSupervisorOwner, processIdentity } = await import("./automatic-runtime.mjs");
|
|
300
313
|
const owner = (await readSupervisorOwner(join(runtime.directory, "supervisor.lock")))?.owner;
|
|
301
|
-
if (
|
|
302
|
-
|
|
314
|
+
if (
|
|
315
|
+
Number.isSafeInteger(status.pid) &&
|
|
316
|
+
status.pid > 1 &&
|
|
317
|
+
owner?.pid === status.pid &&
|
|
318
|
+
owner.identity &&
|
|
319
|
+
(await (options.processIdentity ?? processIdentity)(status.pid)) === owner.identity
|
|
320
|
+
) {
|
|
321
|
+
const invocation =
|
|
322
|
+
platform === "win32"
|
|
323
|
+
? powershellInvocation(
|
|
324
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${status.pid}').CommandLine`,
|
|
325
|
+
env,
|
|
326
|
+
)
|
|
327
|
+
: { command: "ps", args: ["-p", String(status.pid), "-o", "args="] };
|
|
328
|
+
const processInfo = await run(invocation.command, invocation.args, {
|
|
303
329
|
encoding: "utf8",
|
|
304
330
|
timeout: 3000,
|
|
305
331
|
maxBuffer: 8192,
|
|
306
332
|
});
|
|
307
|
-
|
|
308
|
-
|
|
333
|
+
const args = [...processInfo.stdout.trim().matchAll(/"([^"]*)"|(\S+)/g)].map(
|
|
334
|
+
(match) => match[1] ?? match[2],
|
|
335
|
+
);
|
|
336
|
+
const exact =
|
|
337
|
+
platform === "win32"
|
|
338
|
+
? args.length === 2 &&
|
|
339
|
+
args[0].toLowerCase() === node.toLowerCase() &&
|
|
340
|
+
args[1].toLowerCase() === runtime.supervisor.toLowerCase()
|
|
341
|
+
: processInfo.stdout.trim() === `${node} ${runtime.supervisor}`;
|
|
342
|
+
if (exact) {
|
|
343
|
+
const signal = options.processSignal ?? process.kill;
|
|
344
|
+
signal(status.pid, "SIGTERM");
|
|
309
345
|
const deadline = Date.now() + 20000;
|
|
310
346
|
while (Date.now() < deadline) {
|
|
311
347
|
try {
|
|
312
|
-
|
|
348
|
+
signal(status.pid, 0);
|
|
313
349
|
} catch (error) {
|
|
314
350
|
if (error.code === "ESRCH") break;
|
|
315
351
|
throw error;
|
|
@@ -424,12 +460,17 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
424
460
|
startupCode = "STARTUP_DISABLED";
|
|
425
461
|
throw new Error("Automatic startup was disabled by the owner.");
|
|
426
462
|
}
|
|
427
|
-
//
|
|
463
|
+
// Restart the registered entry in place. Unregister/bootstrap races with
|
|
464
|
+
// launchd's own KeepAlive recovery and can temporarily lose registration.
|
|
428
465
|
try {
|
|
429
|
-
await run("launchctl", ["print", `${domain}/${label}`]);
|
|
466
|
+
const loaded = await run("launchctl", ["print", `${domain}/${label}`]);
|
|
467
|
+
if (
|
|
468
|
+
!loaded.stdout.includes(`program = ${node}`) ||
|
|
469
|
+
!loaded.stdout.includes(runtime.supervisor)
|
|
470
|
+
)
|
|
471
|
+
throw new Error("The loaded launch agent does not match this runtime.");
|
|
430
472
|
if (runtime.upgraded) {
|
|
431
|
-
await run("launchctl", ["
|
|
432
|
-
await run("launchctl", ["bootstrap", domain, path]);
|
|
473
|
+
await run("launchctl", ["kickstart", "-k", `${domain}/${label}`]);
|
|
433
474
|
}
|
|
434
475
|
} catch {
|
|
435
476
|
await run("launchctl", ["bootstrap", domain, path]);
|
|
@@ -441,7 +482,7 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
441
482
|
!verified.stdout.includes(runtime.supervisor)
|
|
442
483
|
)
|
|
443
484
|
throw new Error("The loaded launch agent does not match this runtime.");
|
|
444
|
-
return { mode: "launchd", persistent: true, label, path };
|
|
485
|
+
return acknowledged({ mode: "launchd", persistent: true, label, path });
|
|
445
486
|
}
|
|
446
487
|
if (platform === "linux") {
|
|
447
488
|
const path = join(home, ".config", "systemd", "user", unit);
|
|
@@ -462,12 +503,12 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
462
503
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
463
504
|
await run("systemctl", ["--user", "enable", "--now", unit]);
|
|
464
505
|
if (runtime.upgraded) await run("systemctl", ["--user", "restart", unit]);
|
|
465
|
-
return { mode: "systemd-user", persistent: true, label: unit, path };
|
|
506
|
+
return acknowledged({ mode: "systemd-user", persistent: true, label: unit, path });
|
|
466
507
|
}
|
|
467
508
|
if (platform === "win32") {
|
|
468
509
|
windowsRegistration = await windowsStartup(runtime, node, task, env, run);
|
|
469
510
|
if (windowsRegistration.mode === "scheduled-task")
|
|
470
|
-
return { mode: "scheduled-task", persistent: true, label: task };
|
|
511
|
+
return acknowledged({ mode: "scheduled-task", persistent: true, label: task });
|
|
471
512
|
}
|
|
472
513
|
} catch {
|
|
473
514
|
/* Ephemeral/headless hosts often have no user service manager. */
|
|
@@ -481,7 +522,7 @@ export async function installRuntimeService(runtime, env = process.env, options
|
|
|
481
522
|
child.on("error", () => {});
|
|
482
523
|
child.unref();
|
|
483
524
|
return windowsRegistration?.mode === "startup-shortcut"
|
|
484
|
-
? { mode: "startup-shortcut", persistent: true, label: task }
|
|
525
|
+
? acknowledged({ mode: "startup-shortcut", persistent: true, label: task })
|
|
485
526
|
: {
|
|
486
527
|
mode: "background-supervisor",
|
|
487
528
|
persistent: false,
|
package/bin/totem.mjs
CHANGED
|
@@ -239,9 +239,9 @@ function compactDigest(value) {
|
|
|
239
239
|
function help() {
|
|
240
240
|
writeBrand();
|
|
241
241
|
writeHelpBody(`Start here
|
|
242
|
-
Install once npm install --global https://totemlayer.com/downloads/totemlayer-0.1.
|
|
242
|
+
Install once npm install --global https://totemlayer.com/downloads/totemlayer-0.1.44.tgz
|
|
243
243
|
Confirm installation totem --version
|
|
244
|
-
Run one-off npx --yes --package https://totemlayer.com/downloads/totemlayer-0.1.
|
|
244
|
+
Run one-off npx --yes --package https://totemlayer.com/downloads/totemlayer-0.1.44.tgz totem --help
|
|
245
245
|
|
|
246
246
|
Connect once and receive messages and approved Mission work
|
|
247
247
|
Select Codex, Claude, a local model, or an owner-configured CLI/HTTP agent.
|