totemlayer 0.1.92 → 0.1.94

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.
@@ -12,7 +12,7 @@ import { parseArgs } from "node:util";
12
12
  import { readAgentCredential, readAgentProfile, withAgentProfileLock } from "./agent-profile.mjs";
13
13
  import { localClientEnvironment } from "./platform-client.mjs";
14
14
  import { Totem } from "./sdk/index.js";
15
- import { runtimeRoot, privateWrite } from "./runtime-installation.mjs";
15
+ import { runtimeRoot, privateRead, privateWrite } from "./runtime-installation.mjs";
16
16
  import { RUNTIME_ERROR_CODES, runtimeErrorCode } from "./runtime-diagnostics.mjs";
17
17
 
18
18
  const PACKAGE_VERSION = JSON.parse(
@@ -585,12 +585,12 @@ export async function runAgentSession(profile, options = {}) {
585
585
  } catch (error) {
586
586
  result = 1;
587
587
  const diagnostic = startupDiagnostic(error);
588
- if (!diagnostic.retryable)
588
+ if (!diagnostic.retryable || diagnostic.retryAt)
589
589
  await privateWrite(join(profile.stateDirectory, "automatic-health.json"), {
590
- state: "needs_attention",
590
+ state: diagnostic.retryAt ? "waiting" : "needs_attention",
591
591
  code: "LISTENER_START_FAILED",
592
592
  ...diagnostic,
593
- retryAt: null,
593
+ retryAt: diagnostic.retryAt ?? null,
594
594
  updatedAt: new Date().toISOString(),
595
595
  });
596
596
  emit({
@@ -680,12 +680,30 @@ export async function agentStartMain(args, options = {}) {
680
680
  }
681
681
  let profile = await readAgentProfile(parsed.profile, { environment });
682
682
  assertExecutorConfigured(profile);
683
+ if (automatic) {
684
+ const health = await privateRead(join(profile.stateDirectory, "automatic-health.json")).catch(
685
+ (error) => {
686
+ if (error.code !== "ENOENT") throw error;
687
+ return null;
688
+ },
689
+ );
690
+ const cooldown = startupDiagnostic(health);
691
+ if (
692
+ cooldown.causeCode === "CHAT_PROMPT_RATE_LIMITED" &&
693
+ cooldown.retryAt &&
694
+ !(await readAgentRuntimeStatus(profile)).running
695
+ )
696
+ throw Object.assign(new Error(cooldown.cause), {
697
+ code: cooldown.causeCode,
698
+ retryAt: cooldown.retryAt,
699
+ });
700
+ }
683
701
  if (
684
702
  profile.interactiveRequired &&
685
703
  (!profile.codexThreadId ||
686
704
  !(await (await import("./harness-bootstrap.mjs")).harnessStartupReady(profile)))
687
705
  ) {
688
- if (!automatic && profile.codexThreadId) {
706
+ if (!automatic && (profile.codexThreadId || profile.codexNativeCallbacks)) {
689
707
  // Explicit start can finish this selected binding; passive recovery cannot unpause it.
690
708
  const { setAutomaticEnabled } = await import("./automatic-runtime.mjs");
691
709
  const { ensureInteractiveHarness } = await import("./harness-bootstrap.mjs");
@@ -480,6 +480,11 @@ export async function agentSetupMain(args, options = {}) {
480
480
  "This profile is bound to another agent or origin. Choose a new profile or explicitly use --replace.",
481
481
  );
482
482
  const repositoryRoots = await selectedRepositories(selectedRoots, previous, io);
483
+ const nativeCallbacks = Boolean(
484
+ mode === "codex" &&
485
+ identity.listenerConnectionId &&
486
+ (previous?.codexNativeCallbacks || (!previous && !codexThreadId)),
487
+ );
483
488
  let profile;
484
489
  try {
485
490
  profile = await saveAgentProfile(
@@ -496,7 +501,8 @@ export async function agentSetupMain(args, options = {}) {
496
501
  repositoryRoots,
497
502
  ...(["codex", "claude"].includes(mode) ? { client: mode } : {}),
498
503
  ...(codexThreadId === undefined ? {} : { codexThreadId }),
499
- ...(!connectionOnly && previous?.interactiveRequired
504
+ ...(nativeCallbacks ? { codexNativeCallbacks: true } : {}),
505
+ ...(!connectionOnly && (previous?.interactiveRequired || nativeCallbacks)
500
506
  ? { interactiveRequired: true }
501
507
  : {}),
502
508
  },
@@ -525,6 +531,10 @@ export async function agentSetupMain(args, options = {}) {
525
531
  // connection into a persistent opt-out. An existing pause stays untouched.
526
532
  if (!connectionOnly && !flags["no-start"] && !io.deferActivation) {
527
533
  try {
534
+ if (nativeCallbacks)
535
+ profile = await (
536
+ io.ensureHarness ?? (await import("./harness-bootstrap.mjs")).ensureInteractiveHarness
537
+ )(profile, io.env);
528
538
  const activate =
529
539
  io.activateConnection ?? (await import("./installation.mjs")).activateConnection;
530
540
  automatic = await activate(profile, io.env);
@@ -151,7 +151,11 @@ export async function superviseOnce(env = process.env, options = {}) {
151
151
  continue;
152
152
  }
153
153
  const newAcceptance = activationIds.some((id) => !health?.activationIds?.includes(id));
154
- if (!newAcceptance && Number.isFinite(health?.retryAt) && health.retryAt > Date.now())
154
+ if (
155
+ (!newAcceptance || health?.causeCode === "CHAT_PROMPT_RATE_LIMITED") &&
156
+ Number.isFinite(health?.retryAt) &&
157
+ health.retryAt > Date.now()
158
+ )
155
159
  continue;
156
160
  // The run-local journal decides whether any interrupted work can safely resume.
157
161
  const result = await (options.start ?? agentStartMain)(
@@ -205,9 +209,9 @@ export async function superviseOnce(env = process.env, options = {}) {
205
209
  code: "LISTENER_START_FAILED",
206
210
  ...diagnostic,
207
211
  attempts: attempts + 1,
208
- retryAt: diagnostic.retryable
209
- ? Date.now() + Math.min(300000, 5000 * 2 ** attempts)
210
- : null,
212
+ retryAt:
213
+ diagnostic.retryAt ??
214
+ (diagnostic.retryable ? Date.now() + Math.min(300000, 5000 * 2 ** attempts) : null),
211
215
  updatedAt: new Date().toISOString(),
212
216
  }).catch(() => {});
213
217
  /* One offline/revoked connection cannot stop other profiles. */
@@ -20,6 +20,15 @@ export async function activateConnection(profile, env = process.env, options = {
20
20
  if (error.code !== "ENOENT") throw error;
21
21
  },
22
22
  );
23
+ const cooldown = startupDiagnostic(health);
24
+ if (cooldown.causeCode === "CHAT_PROMPT_RATE_LIMITED" && cooldown.retryAt)
25
+ return {
26
+ state: "needs_attention",
27
+ ready: false,
28
+ ...cooldown,
29
+ message:
30
+ "Automatic receiving is waiting for its existing chat prompt cooldown. Saved messages remain queued.",
31
+ };
23
32
  if (health?.retryable === false)
24
33
  return { state: "needs_attention", ready: false, ...startupDiagnostic(health) };
25
34
  try {
Binary file
@@ -1 +1 @@
1
- {"sourceSha256":"e15dc6cfd6f828dbcb829fc6fa3f5f6ef51062f45462ec6532b8c52f532c7951","binarySha256":"61283c8fce55617bbdf5930ee4c4289ae0976a198d1ea885bfbdcd3b1cb7cb8d","architectures":["arm64","x86_64"],"minimumMacOS":"13.0"}
1
+ {"sourceSha256":"e15dc6cfd6f828dbcb829fc6fa3f5f6ef51062f45462ec6532b8c52f532c7951","binarySha256":"f516045a8e5ecb13e997c44785abad1e340e77cc4d7782e6bdbc3972ec33ceb7","architectures":["arm64","x86_64"],"minimumMacOS":"13.0"}
@@ -1 +1 @@
1
- {"version":1,"packageVersion":"0.1.92","bytes":27189,"sha256":"c62a0116c76177d905051b40ddcbb0503e042b1fc8f5d024c1ebf41c7f173983"}
1
+ {"version":1,"packageVersion":"0.1.94","bytes":27189,"sha256":"c62a0116c76177d905051b40ddcbb0503e042b1fc8f5d024c1ebf41c7f173983"}
@@ -1 +1 @@
1
- {"version":1,"packageVersion":"0.1.92","bytes":36144,"sha256":"66535754c594ad559c91b68c93348ec5579f10a0d791c462cecf98cb618ea2ae"}
1
+ {"version":1,"packageVersion":"0.1.94","bytes":36144,"sha256":"66535754c594ad559c91b68c93348ec5579f10a0d791c462cecf98cb618ea2ae"}
@@ -4,6 +4,10 @@ export const HOST_APPROVAL_RECOVERY =
4
4
  "Stop retrying. Ask the owner to inspect this command in the host app's approval or automatic review controls. If no approval path is available, contact the host's support with the denial. Resume once after the owner resolves it; do not bypass the policy or change agents.";
5
5
 
6
6
  const causes = {
7
+ CHAT_PROMPT_RATE_LIMITED: [
8
+ "Automatic chat prompts are waiting for their existing cooldown.",
9
+ "Keep saved messages queued. Automatic receiving will retry at the reported time without changing the chat prompt limit.",
10
+ ],
7
11
  STARTUP_CONFLICT: [
8
12
  "The existing startup task or shortcut uses a different command.",
9
13
  "Inspect the current-user Windows startup task with totem repair-startup. Apply only its reviewed task digest; preserve disabled startup and verify receiving separately.",
@@ -191,5 +195,18 @@ export function startupDiagnostic(error) {
191
195
  : "RUNTIME_AUTHORIZATION_REJECTED"
192
196
  : "STARTUP_CAUSE_UNAVAILABLE";
193
197
  const [cause, recovery, retryable = true] = causes[causeCode];
194
- return { causeCode, cause, recovery, retryable };
198
+ const retryAt = match?.retryAt;
199
+ const now = Date.now();
200
+ return {
201
+ causeCode,
202
+ cause,
203
+ recovery,
204
+ retryable,
205
+ ...(causeCode === "CHAT_PROMPT_RATE_LIMITED" &&
206
+ Number.isFinite(retryAt) &&
207
+ retryAt > now &&
208
+ retryAt <= now + 60 * 60 * 1000
209
+ ? { retryAt }
210
+ : {}),
211
+ };
195
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totemlayer",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "description": "Connect AI agents to Totem Network for discovery, messaging, and shared work.",
5
5
  "type": "module",
6
6
  "bin": {