totemlayer 0.1.63 → 0.1.66

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.
@@ -63,6 +63,9 @@ export function createCommandAdapter(config) {
63
63
  const maxOutputBytes = integer(config.maxOutputBytes, 1024 * 1024, 16 * 1024 * 1024);
64
64
  const parseResult = config.parseResult ?? ((output) => ({ status: "completed", output }));
65
65
  if (typeof parseResult !== "function") throw new WorkerError("INVALID_ADAPTER_CONFIG");
66
+ const supervised = config.managedLifecycle === "darwin-coalition";
67
+ if (config.managedLifecycle !== undefined && (!supervised || process.platform !== "darwin"))
68
+ throw new WorkerError("INVALID_ADAPTER_CONFIG");
66
69
 
67
70
  return {
68
71
  async run({ prompt, signal }) {
@@ -97,7 +100,8 @@ export function createCommandAdapter(config) {
97
100
  // Also remove surviving descendants if the parent exits before escalation.
98
101
  const kill = (kind) => {
99
102
  try {
100
- if (process.platform !== "win32" && child?.pid) process.kill(-child.pid, kind);
103
+ if (supervised) child?.kill("SIGTERM");
104
+ else if (process.platform !== "win32" && child?.pid) process.kill(-child.pid, kind);
101
105
  else if (child?.pid) {
102
106
  // A Windows shim can outlive its parent and keep stdio open.
103
107
  const killer = spawn("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], {
@@ -119,8 +123,10 @@ export function createCommandAdapter(config) {
119
123
  failure = new WorkerError(code);
120
124
  // Kill the complete child process group on POSIX, including spawned tools.
121
125
  kill("SIGTERM");
122
- killTimer = setTimeout(() => kill("SIGKILL"), 1000);
123
- killTimer.unref();
126
+ if (!supervised) {
127
+ killTimer = setTimeout(() => kill("SIGKILL"), 1000);
128
+ killTimer.unref();
129
+ }
124
130
  };
125
131
  const cancel = () => stop("RUN_CANCELLED");
126
132
  try {
@@ -130,7 +136,7 @@ export function createCommandAdapter(config) {
130
136
  shell: false,
131
137
  detached: process.platform !== "win32",
132
138
  windowsHide: true,
133
- stdio: ["pipe", "pipe", "pipe"],
139
+ stdio: supervised ? ["pipe", "pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],
134
140
  });
135
141
  } catch (error) {
136
142
  reject(Object.assign(new WorkerError("AGENT_START_FAILED"), { cause: error }));
@@ -155,6 +161,21 @@ export function createCommandAdapter(config) {
155
161
  child.stdin.on("error", () => {
156
162
  /* A failed child can close stdin early. */
157
163
  });
164
+ if (supervised) {
165
+ let reported = false;
166
+ child.stdio[3].on("data", (status) => {
167
+ if (!reported && status.includes(83)) {
168
+ reported = true;
169
+ process.emitWarning(
170
+ "Native process cleanup is delayed; Totem is retrying and has not completed the turn.",
171
+ { code: "AGENT_CLEANUP_STALLED" },
172
+ );
173
+ }
174
+ });
175
+ child.stdio[3].on("error", () => {
176
+ /* Supervisor exit closes the private channel. */
177
+ });
178
+ }
158
179
  child.on("error", (error) => {
159
180
  failure ??= Object.assign(new WorkerError("AGENT_START_FAILED"), { cause: error });
160
181
  });
@@ -163,9 +184,9 @@ export function createCommandAdapter(config) {
163
184
  clearTimeout(killTimer);
164
185
  signal?.removeEventListener("abort", cancel);
165
186
  // A successful foreground turn cannot leave background tools behind.
166
- if (config.inheritSystemEnvironment === false) kill("SIGKILL");
187
+ if (!supervised && config.inheritSystemEnvironment === false) kill("SIGKILL");
167
188
  if (failure) {
168
- kill("SIGKILL");
189
+ if (!supervised) kill("SIGKILL");
169
190
  return reject(diagnosticFailure(failure, code));
170
191
  }
171
192
  if (code !== 0) {
@@ -1,4 +1,8 @@
1
1
  const SAFE_CODES = new Set([
2
+ "INSPECTION_BLOCKED",
3
+ "INSPECTION_UNAVAILABLE",
4
+ "INSPECTION_LIMIT",
5
+ "INSPECTION_UNSUPPORTED_FILE",
2
6
  "INTERNAL_ERROR",
3
7
  "PRIVACY_REVIEW_REQUIRED",
4
8
  "ISOLATION_UNAVAILABLE",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totemlayer",
3
- "version": "0.1.63",
3
+ "version": "0.1.66",
4
4
  "description": "Cross-platform CLI for TotemLayer agent identity and authorization",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  "bin",
13
13
  "LICENSE.md",
14
14
  "README.md",
15
- "scripts/prepare.mjs"
15
+ "scripts/prepare.mjs",
16
+ "scripts/build-native.mjs"
16
17
  ],
17
18
  "engines": {
18
19
  "node": ">=22.14.0"
@@ -0,0 +1,60 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const source = new URL("../native/darwin-lifetime.c", import.meta.url);
7
+ const binary = new URL("../bin/native/darwin-lifetime", import.meta.url);
8
+ const manifest = new URL("../bin/native/darwin-lifetime.json", import.meta.url);
9
+ const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
10
+ const sourceSha256 = digest(await readFile(source));
11
+
12
+ if (process.platform === "win32")
13
+ throw new Error(
14
+ "Build the universal CLI tarball on macOS or Linux to preserve the native executable mode; Windows can install that tarball.",
15
+ );
16
+
17
+ if (process.platform === "darwin") {
18
+ await mkdir(new URL("../bin/native/", import.meta.url), { recursive: true });
19
+ const temporary = `${fileURLToPath(binary)}.${process.pid}.tmp`;
20
+ try {
21
+ execFileSync(
22
+ "/usr/bin/clang",
23
+ [
24
+ "-std=c11",
25
+ "-D_DARWIN_C_SOURCE",
26
+ "-Wall",
27
+ "-Wextra",
28
+ "-Werror",
29
+ "-O2",
30
+ "-arch",
31
+ "arm64",
32
+ "-arch",
33
+ "x86_64",
34
+ "-mmacosx-version-min=13.0",
35
+ fileURLToPath(source),
36
+ "-o",
37
+ temporary,
38
+ ],
39
+ { stdio: "inherit", timeout: 60_000 },
40
+ );
41
+ await rename(temporary, binary);
42
+ await writeFile(
43
+ manifest,
44
+ JSON.stringify({
45
+ sourceSha256,
46
+ binarySha256: digest(await readFile(binary)),
47
+ architectures: ["arm64", "x86_64"],
48
+ minimumMacOS: "13.0",
49
+ }) + "\n",
50
+ );
51
+ } finally {
52
+ await rm(temporary, { force: true });
53
+ }
54
+ } else {
55
+ // Every published tarball supports both Mac architectures. Build this small
56
+ // artifact on macOS before packing elsewhere; never ship a stale or missing one.
57
+ const built = JSON.parse(await readFile(manifest, "utf8"));
58
+ if (built.sourceSha256 !== sourceSha256 || built.binarySha256 !== digest(await readFile(binary)))
59
+ throw new Error("Build the current macOS lifetime supervisor on macOS before packing.");
60
+ }
@@ -1,5 +1,6 @@
1
1
  import { access, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { transform } from "esbuild";
3
+ import "./build-native.mjs";
3
4
  import { generateNetworkContract } from "../../../scripts/generate-network-contract.mjs";
4
5
  await generateNetworkContract();
5
6
  // This candidate rebases coordination on the immutable published 0.1.21 bundle.