totemlayer 0.1.92 → 0.1.96
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/bin/agent-runner.mjs +50 -17
- package/bin/agent-setup.mjs +11 -1
- package/bin/automatic-runtime.mjs +8 -4
- package/bin/connected-agent-service.mjs +8 -1
- package/bin/installation.mjs +9 -0
- package/bin/native/darwin-lifetime +0 -0
- package/bin/native/darwin-lifetime.json +1 -1
- package/bin/network-runtime.mjs +1 -0
- package/bin/setup-assets/api-manifest.json +1 -1
- package/bin/setup-assets/manifest.json +1 -1
- package/bin/startup-diagnostics.mjs +18 -1
- package/package.json +1 -1
package/bin/agent-runner.mjs
CHANGED
|
@@ -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(
|
|
@@ -232,7 +232,8 @@ export async function readAgentRuntimeStatus(profile, { isAlive = alive, now = D
|
|
|
232
232
|
const fresh = now() - Date.parse(record.updatedAt) <= 30_000;
|
|
233
233
|
const receiving =
|
|
234
234
|
record.listenerHealthRequired !== true ||
|
|
235
|
-
(
|
|
235
|
+
(record.listenerHeartbeatReady !== false &&
|
|
236
|
+
typeof record.listenerHeartbeatAt === "string" &&
|
|
236
237
|
Number.isFinite(Date.parse(record.listenerHeartbeatAt)) &&
|
|
237
238
|
now() - Date.parse(record.listenerHeartbeatAt) >= -5000 &&
|
|
238
239
|
now() - Date.parse(record.listenerHeartbeatAt) <= 30_000);
|
|
@@ -263,7 +264,9 @@ export async function readAgentRuntimeStatus(profile, { isAlive = alive, now = D
|
|
|
263
264
|
),
|
|
264
265
|
phase: running
|
|
265
266
|
? fresh
|
|
266
|
-
? record.phase
|
|
267
|
+
? record.ready && !receiving && !["blocked", "working", "starting"].includes(record.phase)
|
|
268
|
+
? "reconnecting"
|
|
269
|
+
: record.phase
|
|
267
270
|
: "unresponsive"
|
|
268
271
|
: ["stopped", "failed"].includes(record.phase)
|
|
269
272
|
? record.phase
|
|
@@ -338,8 +341,15 @@ export async function stopStalledAgent(profile, runId, options = {}) {
|
|
|
338
341
|
|
|
339
342
|
function eventState(event) {
|
|
340
343
|
if (event?.type === "runtime_ready")
|
|
341
|
-
return {
|
|
342
|
-
|
|
344
|
+
return {
|
|
345
|
+
ready: true,
|
|
346
|
+
listenerHeartbeatReady: true,
|
|
347
|
+
listenerHeartbeatAt: new Date().toISOString(),
|
|
348
|
+
};
|
|
349
|
+
if (event?.type === "runtime_heartbeat")
|
|
350
|
+
return { listenerHeartbeatReady: true, listenerHeartbeatAt: new Date().toISOString() };
|
|
351
|
+
if (event?.type === "runtime_heartbeat_check")
|
|
352
|
+
return { listenerCheckAt: new Date().toISOString() };
|
|
343
353
|
const candidate = event?.state ?? event?.status ?? event?.type;
|
|
344
354
|
const phase =
|
|
345
355
|
typeof candidate === "string" ? candidate.replace(/^mission_agent_|^agent_/, "") : "";
|
|
@@ -472,14 +482,19 @@ export async function runAgentSession(profile, options = {}) {
|
|
|
472
482
|
throw new Error("Invalid listener watchdog deadline.");
|
|
473
483
|
const poll = async () => {
|
|
474
484
|
if (status.ready && Date.now() - Date.parse(status.listenerHeartbeatAt) > listenerWatchdogMs) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
485
|
+
// Bounded network retries keep the verified chat, but never restore readiness.
|
|
486
|
+
const retrying = Date.now() - Date.parse(status.listenerCheckAt ?? "") <= listenerWatchdogMs;
|
|
487
|
+
status = { ...status, listenerHeartbeatReady: false };
|
|
488
|
+
if (!retrying) {
|
|
489
|
+
listenerExpired = true;
|
|
490
|
+
status = {
|
|
491
|
+
...status,
|
|
492
|
+
phase: "reconnecting",
|
|
493
|
+
ready: false,
|
|
494
|
+
errorCode: "LISTENER_HEARTBEAT_TIMEOUT",
|
|
495
|
+
};
|
|
496
|
+
controller.abort();
|
|
497
|
+
}
|
|
483
498
|
}
|
|
484
499
|
try {
|
|
485
500
|
const request = await readPrivateJson(join(directory, `${runId}.stop`));
|
|
@@ -585,12 +600,12 @@ export async function runAgentSession(profile, options = {}) {
|
|
|
585
600
|
} catch (error) {
|
|
586
601
|
result = 1;
|
|
587
602
|
const diagnostic = startupDiagnostic(error);
|
|
588
|
-
if (!diagnostic.retryable)
|
|
603
|
+
if (!diagnostic.retryable || diagnostic.retryAt)
|
|
589
604
|
await privateWrite(join(profile.stateDirectory, "automatic-health.json"), {
|
|
590
|
-
state: "needs_attention",
|
|
605
|
+
state: diagnostic.retryAt ? "waiting" : "needs_attention",
|
|
591
606
|
code: "LISTENER_START_FAILED",
|
|
592
607
|
...diagnostic,
|
|
593
|
-
retryAt: null,
|
|
608
|
+
retryAt: diagnostic.retryAt ?? null,
|
|
594
609
|
updatedAt: new Date().toISOString(),
|
|
595
610
|
});
|
|
596
611
|
emit({
|
|
@@ -680,12 +695,30 @@ export async function agentStartMain(args, options = {}) {
|
|
|
680
695
|
}
|
|
681
696
|
let profile = await readAgentProfile(parsed.profile, { environment });
|
|
682
697
|
assertExecutorConfigured(profile);
|
|
698
|
+
if (automatic) {
|
|
699
|
+
const health = await privateRead(join(profile.stateDirectory, "automatic-health.json")).catch(
|
|
700
|
+
(error) => {
|
|
701
|
+
if (error.code !== "ENOENT") throw error;
|
|
702
|
+
return null;
|
|
703
|
+
},
|
|
704
|
+
);
|
|
705
|
+
const cooldown = startupDiagnostic(health);
|
|
706
|
+
if (
|
|
707
|
+
cooldown.causeCode === "CHAT_PROMPT_RATE_LIMITED" &&
|
|
708
|
+
cooldown.retryAt &&
|
|
709
|
+
!(await readAgentRuntimeStatus(profile)).running
|
|
710
|
+
)
|
|
711
|
+
throw Object.assign(new Error(cooldown.cause), {
|
|
712
|
+
code: cooldown.causeCode,
|
|
713
|
+
retryAt: cooldown.retryAt,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
683
716
|
if (
|
|
684
717
|
profile.interactiveRequired &&
|
|
685
718
|
(!profile.codexThreadId ||
|
|
686
719
|
!(await (await import("./harness-bootstrap.mjs")).harnessStartupReady(profile)))
|
|
687
720
|
) {
|
|
688
|
-
if (!automatic && profile.codexThreadId) {
|
|
721
|
+
if (!automatic && (profile.codexThreadId || profile.codexNativeCallbacks)) {
|
|
689
722
|
// Explicit start can finish this selected binding; passive recovery cannot unpause it.
|
|
690
723
|
const { setAutomaticEnabled } = await import("./automatic-runtime.mjs");
|
|
691
724
|
const { ensureInteractiveHarness } = await import("./harness-bootstrap.mjs");
|
package/bin/agent-setup.mjs
CHANGED
|
@@ -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
|
-
...(
|
|
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 (
|
|
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:
|
|
209
|
-
|
|
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. */
|
|
@@ -77,7 +77,14 @@ export async function runConnectedAgentService(settings, dependencies = {}) {
|
|
|
77
77
|
replyTimeoutMs: 90000,
|
|
78
78
|
pollMs: settings.pollMs ?? DEFAULT_LISTENER_POLL_MS,
|
|
79
79
|
emit(event) {
|
|
80
|
-
if (
|
|
80
|
+
if (
|
|
81
|
+
[
|
|
82
|
+
"runtime_ready",
|
|
83
|
+
"runtime_heartbeat",
|
|
84
|
+
"runtime_heartbeat_check",
|
|
85
|
+
"receive_check",
|
|
86
|
+
].includes(event.type)
|
|
87
|
+
)
|
|
81
88
|
emit(event);
|
|
82
89
|
else if (event.type === "runtime_reconnecting")
|
|
83
90
|
report("messages", { type: "reconnecting" });
|
package/bin/installation.mjs
CHANGED
|
@@ -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":"
|
|
1
|
+
{"sourceSha256":"e15dc6cfd6f828dbcb829fc6fa3f5f6ef51062f45462ec6532b8c52f532c7951","binarySha256":"39c3e7ba072d6fe97d7a5bdec0effce7ba0fa035c5c0376d1295557e1272c352","architectures":["arm64","x86_64"],"minimumMacOS":"13.0"}
|
package/bin/network-runtime.mjs
CHANGED
|
@@ -840,6 +840,7 @@ export async function runRuntime(settings) {
|
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
842
|
const beat = async () => {
|
|
843
|
+
emit({ type: "runtime_heartbeat_check" });
|
|
843
844
|
const unavailable = ["blocked", "reconnecting"].includes(diagnostics.phase);
|
|
844
845
|
const result = await client.runtime.heartbeat(
|
|
845
846
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":1,"packageVersion":"0.1.
|
|
1
|
+
{"version":1,"packageVersion":"0.1.96","bytes":27189,"sha256":"c62a0116c76177d905051b40ddcbb0503e042b1fc8f5d024c1ebf41c7f173983"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":1,"packageVersion":"0.1.
|
|
1
|
+
{"version":1,"packageVersion":"0.1.96","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
|
-
|
|
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
|
}
|