totemlayer 0.1.17 → 0.1.18

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.
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { constants } from "node:fs";
4
- import { lstat, mkdir, open, opendir, rename, unlink } from "node:fs/promises";
4
+ import { lstat, mkdir, open, opendir, rename, unlink, readFile } from "node:fs/promises";
5
5
  import { dirname, isAbsolute, join, parse, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parseArgs } from "node:util";
@@ -11,6 +11,9 @@ import { localClientEnvironment } from "./platform-client.mjs";
11
11
  import { Totem } from "./sdk/index.js";
12
12
  import { RUNTIME_ERROR_CODES, runtimeErrorCode } from "./runtime-diagnostics.mjs";
13
13
 
14
+ const PACKAGE_VERSION = JSON.parse(
15
+ await readFile(new URL("../package.json", import.meta.url), "utf8"),
16
+ ).version;
14
17
  const CLI_PATH = fileURLToPath(new URL("./totem.mjs", import.meta.url));
15
18
  const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i;
16
19
  const PHASES = new Set([
@@ -230,6 +233,7 @@ export async function readAgentRuntimeStatus(profile, { isAlive = alive, now = D
230
233
  ? record.phase
231
234
  : "interrupted",
232
235
  runId: record.runId,
236
+ ...(typeof record.packageVersion === "string" ? { packageVersion: record.packageVersion } : {}),
233
237
  updatedAt: record.updatedAt,
234
238
  ...(RUNTIME_ERROR_CODES.has(record.blockerCode) ? { blockerCode: record.blockerCode } : {}),
235
239
  ...(UUID.test(record.missionId ?? "") ? { missionId: record.missionId } : {}),
@@ -287,6 +291,7 @@ export async function runAgentSession(profile, options = {}) {
287
291
  if (options.signal?.aborted) controller.abort();
288
292
  let status = {
289
293
  version: 1,
294
+ packageVersion: PACKAGE_VERSION,
290
295
  runId,
291
296
  pid: process.pid,
292
297
  agentId: profile.agentId,
@@ -442,7 +447,7 @@ function runnerOptions(args) {
442
447
  return parsed.values;
443
448
  }
444
449
 
445
- async function requestStop(profile, runId) {
450
+ export async function requestStop(profile, runId) {
446
451
  if (!UUID.test(runId)) throw new Error("Invalid agent run identifier.");
447
452
  const directory = await runtimeDirectory(profile, true);
448
453
  const path = join(directory, `${runId}.stop`);
@@ -1,9 +1,9 @@
1
- import { opendir, rm } from "node:fs/promises";
1
+ import { opendir, rm, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { setTimeout as sleep } from "node:timers/promises";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { readAgentProfile } from "./agent-profile.mjs";
6
- import { agentStartMain, readAgentRuntimeStatus } from "./agent-runner.mjs";
6
+ import { agentStartMain, readAgentRuntimeStatus, requestStop } from "./agent-runner.mjs";
7
7
  import {
8
8
  privateDirectory,
9
9
  privateRead,
@@ -12,6 +12,10 @@ import {
12
12
  } from "./runtime-installation.mjs";
13
13
  import { resumeDeviceLogin } from "./device-login.mjs";
14
14
 
15
+ const PACKAGE_VERSION = JSON.parse(
16
+ await readFile(new URL("../package.json", import.meta.url), "utf8"),
17
+ ).version;
18
+
15
19
  export async function setAutomaticEnabled(profile, enabled) {
16
20
  await privateWrite(join(profile.stateDirectory, "automatic.json"), { enabled });
17
21
  }
@@ -42,7 +46,22 @@ export async function superviseOnce(env = process.env, options = {}) {
42
46
  const profile = await readAgentProfile(entry.name, { environment: env });
43
47
  if (!(await automaticEnabled(profile))) continue;
44
48
  const state = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
45
- if (state.running) continue;
49
+ if (state.running) {
50
+ if (state.packageVersion === (options.packageVersion ?? PACKAGE_VERSION)) continue;
51
+ // Finish active work before upgrading. Never overlap two workers or clear journals.
52
+ if (!["connected", "waiting", "blocked", "reconnecting"].includes(state.phase)) continue;
53
+ await (options.stop ?? requestStop)(profile, state.runId);
54
+ const deadline = Date.now() + (options.stopTimeoutMs ?? 10000);
55
+ let stopped = false;
56
+ while (Date.now() < deadline) {
57
+ if (!(await (options.readStatus ?? readAgentRuntimeStatus)(profile)).running) {
58
+ stopped = true;
59
+ break;
60
+ }
61
+ await sleep(100);
62
+ }
63
+ if (!stopped || !(await automaticEnabled(profile))) continue;
64
+ }
46
65
  // The run-local journal decides whether any interrupted work can safely resume.
47
66
  await (options.start ?? agentStartMain)(["--profile", entry.name, "--background", "--json"], {
48
67
  env,
@@ -154,7 +154,9 @@ export function createCommandAdapter(config) {
154
154
  if (code !== 0) {
155
155
  try {
156
156
  return reject(
157
- new WorkerError(config.classifyFailure?.(diagnosticOutput) ?? "AGENT_EXIT_FAILED"),
157
+ new WorkerError(
158
+ config.classifyFailure?.(`${diagnosticOutput}\n${output}`) ?? "AGENT_EXIT_FAILED",
159
+ ),
158
160
  );
159
161
  } catch {
160
162
  return reject(new WorkerError("AGENT_EXIT_FAILED"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totemlayer",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Cross-platform CLI for TotemLayer agent identity and authorization",
5
5
  "type": "module",
6
6
  "bin": {