totemlayer 0.1.68 → 0.1.74
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 +9 -13
- package/bin/agent-runner.mjs +23 -1
- package/bin/automatic-runtime.mjs +23 -12
- package/bin/codex-chat-bridge.mjs +72 -38
- package/bin/codex-desktop-wake.mjs +47 -20
- package/bin/codex-human-chat-bridge.mjs +57 -21
- package/bin/codex-mission-bridge.mjs +167 -0
- package/bin/connected-agent-service.mjs +12 -2
- package/bin/executor-diagnostic.mjs +169 -61
- package/bin/installation.mjs +8 -16
- package/bin/local-inspector.mjs +6 -0
- package/bin/mcp-install.mjs +12 -1
- package/bin/mcp.mjs +43 -1
- package/bin/mission-worker.mjs +62 -39
- package/bin/model-adapter.mjs +30 -1
- package/bin/native/darwin-lifetime +0 -0
- package/bin/native/darwin-lifetime.json +1 -1
- package/bin/network-contract.mjs +125 -0
- package/bin/network-runtime.mjs +367 -315
- package/bin/observability.mjs +336 -0
- package/bin/platform-client.mjs +6 -3
- package/bin/plugin-connect.mjs +10 -0
- package/bin/provider-diagnostics.mjs +40 -11
- package/bin/runtime-installation.mjs +4 -1
- package/bin/runtime-lock.mjs +43 -6
- package/bin/sdk/index.d.ts +1 -0
- package/bin/sdk/index.js +1 -1
- package/bin/setup-assets/api-connect.backup.md +129 -0
- package/bin/setup-assets/api-connect.md +129 -0
- package/bin/setup-assets/api-manifest.json +1 -0
- package/bin/setup-assets/manifest.json +1 -0
- package/bin/setup-assets/setup.backup.md +178 -0
- package/bin/setup-assets/setup.md +178 -0
- package/bin/setup-guide.mjs +39 -0
- package/bin/setup-receipts.mjs +181 -0
- package/bin/strict-codex-adapter.mjs +456 -0
- package/bin/strict-installation.mjs +206 -0
- package/bin/strict-setup-jobs.mjs +253 -0
- package/bin/strict-setup.mjs +385 -0
- package/bin/totem.mjs +11 -1
- package/package.json +4 -3
- package/scripts/prepare.mjs +2 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { GUIDE_UNAVAILABLE, runStrictSetup } from "./strict-setup.mjs";
|
|
3
|
+
import { readReleaseSetupGuide } from "./setup-guide.mjs";
|
|
4
|
+
|
|
5
|
+
export const STRICT_INSTALL_HELP = `Install Totem with every required verification
|
|
6
|
+
|
|
7
|
+
totem install [--provider codex] [--agent-id UUID] [--environment PRODUCTION|SANDBOX]
|
|
8
|
+
[--session CURRENT_NATIVE_SESSION] [--model CURRENT_MODEL] [--wait-seconds 1200] [--json]
|
|
9
|
+
[--device-login --no-browser]
|
|
10
|
+
totem install --read-guide
|
|
11
|
+
totem install --status
|
|
12
|
+
|
|
13
|
+
Run inside the intended agent's command-capable host. The current native session and
|
|
14
|
+
model must be verified; an installed client is never selected as a substitute.
|
|
15
|
+
Sign in and approve the connection when requested. Credentials are never arguments.
|
|
16
|
+
The installer retains one receiver, explicit pauses and permissions. Success requires
|
|
17
|
+
identity, five-second receiving, durable startup, recovery, native wake and separately
|
|
18
|
+
verified peer and website-owner replies. Missing capabilities/tests return incomplete.
|
|
19
|
+
Repeat the same command to resume the same binding; never reauthorize to repair a pause.
|
|
20
|
+
|
|
21
|
+
Phone apps and hosted-only assistants use their native custom MCP flow at
|
|
22
|
+
https://totemlayer.com/mcp. A phone browser approves an agent on its actual host;
|
|
23
|
+
it does not run a local service. API fallback requires the observed missing custom-MCP
|
|
24
|
+
capability first. Read setup.md for the full mobile, MCP and API requirements.
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export async function strictInstallationMain(args = [], options = {}) {
|
|
28
|
+
const stdout = options.stdout ?? process.stdout;
|
|
29
|
+
let flags;
|
|
30
|
+
try {
|
|
31
|
+
const values = parseArgs({
|
|
32
|
+
args,
|
|
33
|
+
strict: true,
|
|
34
|
+
options: {
|
|
35
|
+
provider: { type: "string" },
|
|
36
|
+
"agent-id": { type: "string" },
|
|
37
|
+
environment: { type: "string" },
|
|
38
|
+
"base-url": { type: "string" },
|
|
39
|
+
session: { type: "string" },
|
|
40
|
+
model: { type: "string" },
|
|
41
|
+
"wait-seconds": { type: "string", default: "1200" },
|
|
42
|
+
"read-guide": { type: "boolean" },
|
|
43
|
+
json: { type: "boolean" },
|
|
44
|
+
"device-login": { type: "boolean" },
|
|
45
|
+
"no-browser": { type: "boolean" },
|
|
46
|
+
status: { type: "boolean" },
|
|
47
|
+
help: { type: "boolean", short: "h" },
|
|
48
|
+
},
|
|
49
|
+
}).values;
|
|
50
|
+
if (values.help) {
|
|
51
|
+
stdout.write(STRICT_INSTALL_HELP);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (values["read-guide"]) {
|
|
55
|
+
const guide = await readReleaseSetupGuide();
|
|
56
|
+
stdout.write(guide.text);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
const baseUrl = new URL(values["base-url"] ?? "https://totemlayer.com");
|
|
60
|
+
if (
|
|
61
|
+
baseUrl.username ||
|
|
62
|
+
baseUrl.password ||
|
|
63
|
+
baseUrl.search ||
|
|
64
|
+
baseUrl.hash ||
|
|
65
|
+
baseUrl.pathname !== "/" ||
|
|
66
|
+
(baseUrl.protocol !== "https:" &&
|
|
67
|
+
!(
|
|
68
|
+
baseUrl.protocol === "http:" &&
|
|
69
|
+
["localhost", "127.0.0.1", "[::1]"].includes(baseUrl.hostname)
|
|
70
|
+
))
|
|
71
|
+
)
|
|
72
|
+
throw new Error();
|
|
73
|
+
const waitSeconds = Number(values["wait-seconds"]);
|
|
74
|
+
if (
|
|
75
|
+
!/^\d+$/.test(values["wait-seconds"]) ||
|
|
76
|
+
!Number.isSafeInteger(waitSeconds) ||
|
|
77
|
+
waitSeconds < 0 ||
|
|
78
|
+
waitSeconds > 3600
|
|
79
|
+
)
|
|
80
|
+
throw new Error();
|
|
81
|
+
if (
|
|
82
|
+
values["agent-id"] &&
|
|
83
|
+
!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(values["agent-id"])
|
|
84
|
+
)
|
|
85
|
+
throw new Error();
|
|
86
|
+
if (
|
|
87
|
+
values.environment &&
|
|
88
|
+
(!values["agent-id"] || !["PRODUCTION", "SANDBOX"].includes(values.environment))
|
|
89
|
+
)
|
|
90
|
+
throw new Error();
|
|
91
|
+
if (
|
|
92
|
+
values.model &&
|
|
93
|
+
(!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(values.model) ||
|
|
94
|
+
/tn_(?:live|test)_/.test(values.model))
|
|
95
|
+
)
|
|
96
|
+
throw new Error();
|
|
97
|
+
if (values.session && !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(values.session))
|
|
98
|
+
throw new Error();
|
|
99
|
+
const env = options.env ?? process.env;
|
|
100
|
+
flags = {
|
|
101
|
+
provider: values.provider ?? (env.CODEX_THREAD_ID ? "codex" : undefined),
|
|
102
|
+
agentId: values["agent-id"]?.toLowerCase(),
|
|
103
|
+
environment: values.environment,
|
|
104
|
+
baseUrl: baseUrl.origin,
|
|
105
|
+
sessionId: values.session,
|
|
106
|
+
model: values.model,
|
|
107
|
+
waitSeconds,
|
|
108
|
+
json: values.json === true,
|
|
109
|
+
};
|
|
110
|
+
if (values["device-login"]) flags.deviceLogin = true;
|
|
111
|
+
if (values["no-browser"]) flags.noBrowser = true;
|
|
112
|
+
if (values.status) {
|
|
113
|
+
flags.sessionId ??= env.CODEX_THREAD_ID;
|
|
114
|
+
const { readStrictSetupJob } = await import("./strict-setup-jobs.mjs");
|
|
115
|
+
const publicStatus = { ...(await readStrictSetupJob(flags, env)) };
|
|
116
|
+
delete publicStatus.pid;
|
|
117
|
+
delete publicStatus.processIdentity;
|
|
118
|
+
stdout.write(JSON.stringify(publicStatus) + "\n");
|
|
119
|
+
return publicStatus.complete ? 0 : 2;
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
stdout.write(
|
|
123
|
+
JSON.stringify({
|
|
124
|
+
complete: false,
|
|
125
|
+
state: "incomplete",
|
|
126
|
+
code: "INSTALL_OPTIONS_INVALID",
|
|
127
|
+
message: "Use totem install --help. Never pass credentials in command arguments.",
|
|
128
|
+
}) + "\n",
|
|
129
|
+
);
|
|
130
|
+
return 2;
|
|
131
|
+
}
|
|
132
|
+
const write = async (value) => stdout.write(JSON.stringify(value) + "\n");
|
|
133
|
+
if (!options.adapter && !options.foreground && flags.provider === "codex") {
|
|
134
|
+
// Return before readiness needs a model turn in this same native chat.
|
|
135
|
+
const adapter = (await import("./strict-codex-adapter.mjs")).createStrictCodexAdapter(flags, {
|
|
136
|
+
...options,
|
|
137
|
+
write,
|
|
138
|
+
});
|
|
139
|
+
try {
|
|
140
|
+
let guide;
|
|
141
|
+
for (const reader of ["primary", "alternate"]) {
|
|
142
|
+
try {
|
|
143
|
+
guide = await readReleaseSetupGuide(reader);
|
|
144
|
+
break;
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
if (!guide) {
|
|
148
|
+
await write({
|
|
149
|
+
complete: false,
|
|
150
|
+
state: "incomplete",
|
|
151
|
+
code: "GUIDE_UNAVAILABLE",
|
|
152
|
+
message: GUIDE_UNAVAILABLE,
|
|
153
|
+
});
|
|
154
|
+
return 3;
|
|
155
|
+
}
|
|
156
|
+
const inspected = await adapter.inspect(true);
|
|
157
|
+
if (inspected.paused) {
|
|
158
|
+
await write(
|
|
159
|
+
await runStrictSetup(adapter, {
|
|
160
|
+
expectedAgentId: flags.agentId,
|
|
161
|
+
expectedEnvironment: flags.environment,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
return 2;
|
|
165
|
+
}
|
|
166
|
+
const host = inspected.host;
|
|
167
|
+
const { scheduleStrictSetup } = await import("./strict-setup-jobs.mjs");
|
|
168
|
+
await write(await scheduleStrictSetup(flags, host, options.env ?? process.env));
|
|
169
|
+
return 2;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
await write({
|
|
172
|
+
complete: false,
|
|
173
|
+
state: "incomplete",
|
|
174
|
+
code: /^[A-Z][A-Z0-9_]{0,79}$/.test(error.code ?? "") ? error.code : "SETUP_ADAPTER_FAILED",
|
|
175
|
+
});
|
|
176
|
+
return 2;
|
|
177
|
+
} finally {
|
|
178
|
+
await adapter.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// The command has no arbitrary adapter/script URL, proof, skip-check or force-success option.
|
|
182
|
+
const adapter =
|
|
183
|
+
options.adapter ??
|
|
184
|
+
(flags.provider === "codex"
|
|
185
|
+
? (await import("./strict-codex-adapter.mjs")).createStrictCodexAdapter(flags, {
|
|
186
|
+
...options,
|
|
187
|
+
write,
|
|
188
|
+
})
|
|
189
|
+
: {
|
|
190
|
+
readGuide: readReleaseSetupGuide,
|
|
191
|
+
async inspect() {
|
|
192
|
+
throw Object.assign(new Error(), {
|
|
193
|
+
code: flags.provider ? "HOST_ADAPTER_UNSUPPORTED" : "EXACT_NATIVE_HOST_REQUIRED",
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const result = await runStrictSetup(adapter, {
|
|
198
|
+
expectedAgentId: flags.agentId,
|
|
199
|
+
expectedEnvironment: flags.environment,
|
|
200
|
+
progress: async (value) => {
|
|
201
|
+
if (!flags.json || value.stage === "guide_verified") await write(value);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
await write(result);
|
|
205
|
+
return result.complete ? 0 : result.message === GUIDE_UNAVAILABLE ? 3 : 2;
|
|
206
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { opendir } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
privateRead,
|
|
7
|
+
privateWrite,
|
|
8
|
+
ensureInstalledRuntime,
|
|
9
|
+
installRuntimeService,
|
|
10
|
+
runtimeRoot,
|
|
11
|
+
} from "./runtime-installation.mjs";
|
|
12
|
+
import { acquireSupervisorLock, processIdentity } from "./automatic-runtime.mjs";
|
|
13
|
+
|
|
14
|
+
const valid = (id) => typeof id === "string" && /^[a-f0-9]{64}$/.test(id);
|
|
15
|
+
const missing = async (promise) =>
|
|
16
|
+
promise.catch((error) => {
|
|
17
|
+
if (error.code !== "ENOENT") throw error;
|
|
18
|
+
});
|
|
19
|
+
const directory = (env, id) => join(runtimeRoot(env), "runtime", "strict-setup-jobs", id);
|
|
20
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
21
|
+
const active = async (record) =>
|
|
22
|
+
Number.isSafeInteger(record?.pid) &&
|
|
23
|
+
record.pid > 1 &&
|
|
24
|
+
record.processIdentity &&
|
|
25
|
+
(await processIdentity(record.pid, record.processIdentity).catch(() => null)) ===
|
|
26
|
+
record.processIdentity;
|
|
27
|
+
|
|
28
|
+
export function strictSetupJobId(flags) {
|
|
29
|
+
return hash([
|
|
30
|
+
flags.baseUrl,
|
|
31
|
+
flags.provider,
|
|
32
|
+
flags.sessionId,
|
|
33
|
+
flags.agentId ?? null,
|
|
34
|
+
flags.environment ?? null,
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validJob(job) {
|
|
39
|
+
const keys = [
|
|
40
|
+
"provider",
|
|
41
|
+
"agentId",
|
|
42
|
+
"environment",
|
|
43
|
+
"baseUrl",
|
|
44
|
+
"sessionId",
|
|
45
|
+
"model",
|
|
46
|
+
"waitSeconds",
|
|
47
|
+
"json",
|
|
48
|
+
"deviceLogin",
|
|
49
|
+
"noBrowser",
|
|
50
|
+
];
|
|
51
|
+
return (
|
|
52
|
+
job?.version === 1 &&
|
|
53
|
+
valid(job.id) &&
|
|
54
|
+
job.flags?.provider === "codex" &&
|
|
55
|
+
/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(job.flags.sessionId ?? "") &&
|
|
56
|
+
/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(job.flags.model ?? "") &&
|
|
57
|
+
!/tn_(?:test|live)_/.test(job.flags.model) &&
|
|
58
|
+
job.flags.model !== "default" &&
|
|
59
|
+
Number.isInteger(job.flags.waitSeconds) &&
|
|
60
|
+
job.flags.waitSeconds >= 0 &&
|
|
61
|
+
job.flags.waitSeconds <= 3600 &&
|
|
62
|
+
Object.keys(job.flags).every((key) => keys.includes(key)) &&
|
|
63
|
+
strictSetupJobId(job.flags) === job.id
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function launch(job, runtime, env) {
|
|
68
|
+
const path = directory(env, job.id);
|
|
69
|
+
const state = await missing(privateRead(join(path, "status.json")));
|
|
70
|
+
if (await active(state)) return state;
|
|
71
|
+
if (["complete", "blocked", "paused"].includes(state?.state)) return state;
|
|
72
|
+
const child = spawn(process.execPath, [runtime.cli, "install-worker", job.id], {
|
|
73
|
+
env: { ...env, TOTEM_AUTO_START: "1" },
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: "ignore",
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
});
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
child.once("spawn", resolve);
|
|
80
|
+
child.once("error", reject);
|
|
81
|
+
});
|
|
82
|
+
child.unref();
|
|
83
|
+
return { state: "installing", complete: false, jobId: job.id };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Foreground returns promptly so the bound native chat can handle its queued readiness callbacks. */
|
|
87
|
+
export async function scheduleStrictSetup(flags, host, env = process.env, options = {}) {
|
|
88
|
+
const selected = { ...flags, sessionId: host.sessionId, model: host.model };
|
|
89
|
+
const id = strictSetupJobId(selected),
|
|
90
|
+
path = directory(env, id);
|
|
91
|
+
const previous = await missing(privateRead(join(path, "job.json")));
|
|
92
|
+
if (previous && (!validJob(previous) || previous.flags.model !== selected.model))
|
|
93
|
+
throw Object.assign(new Error(), { code: "SETUP_BINDING_CHANGED" });
|
|
94
|
+
const job = previous ?? { version: 1, id, flags: selected };
|
|
95
|
+
if (!validJob(job)) throw Object.assign(new Error(), { code: "SETUP_BINDING_UNVERIFIED" });
|
|
96
|
+
const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(env);
|
|
97
|
+
if (!previous) await privateWrite(join(path, "job.json"), job);
|
|
98
|
+
const status = await missing(privateRead(join(path, "status.json")));
|
|
99
|
+
if (status?.state === "complete")
|
|
100
|
+
await privateWrite(join(path, "status.json"), {
|
|
101
|
+
state: "checking",
|
|
102
|
+
complete: false,
|
|
103
|
+
previousVerifiedAt: status.updatedAt,
|
|
104
|
+
});
|
|
105
|
+
const service = await (options.installService ?? installRuntimeService)(runtime, env);
|
|
106
|
+
if (service.persistent !== true)
|
|
107
|
+
return {
|
|
108
|
+
state: "incomplete",
|
|
109
|
+
complete: false,
|
|
110
|
+
code: "SETUP_SUPERVISION_UNAVAILABLE",
|
|
111
|
+
jobId: id,
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
...(await (options.launch ?? launch)(job, runtime, env)),
|
|
115
|
+
jobId: id,
|
|
116
|
+
message:
|
|
117
|
+
"Setup continues under the host supervisor. Finish sign-in/approval when requested; this native chat must be available for queued checks. Inspect totem install --status without restarting setup.",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function readStrictSetupJob(flags, env = process.env) {
|
|
122
|
+
const id = strictSetupJobId(flags);
|
|
123
|
+
return (
|
|
124
|
+
(await missing(privateRead(join(directory(env, id), "status.json")))) ?? {
|
|
125
|
+
state: "not_started",
|
|
126
|
+
complete: false,
|
|
127
|
+
jobId: id,
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function resumeStrictSetupJobs(env = process.env, options = {}) {
|
|
133
|
+
const root = join(runtimeRoot(env), "runtime", "strict-setup-jobs");
|
|
134
|
+
const entries = await missing(opendir(root));
|
|
135
|
+
if (!entries) return;
|
|
136
|
+
const runtime = { cli: join(runtimeRoot(env), "runtime", "totem.mjs") };
|
|
137
|
+
let count = 0;
|
|
138
|
+
for await (const entry of entries) {
|
|
139
|
+
if (++count > 100) throw new Error("Too many pending setup jobs.");
|
|
140
|
+
if (!entry.isDirectory() || !valid(entry.name)) continue;
|
|
141
|
+
const job = await privateRead(join(root, entry.name, "job.json"));
|
|
142
|
+
if (!validJob(job) || job.id !== entry.name) throw new Error("Unsafe pending setup job.");
|
|
143
|
+
await (options.launch ?? launch)(job, runtime, env);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function strictSetupWorkerMain(id, options = {}) {
|
|
148
|
+
const env = options.env ?? process.env;
|
|
149
|
+
if (!valid(id)) return 2;
|
|
150
|
+
const path = directory(env, id);
|
|
151
|
+
const job = await privateRead(join(path, "job.json"));
|
|
152
|
+
if (!validJob(job) || job.id !== id) return 2;
|
|
153
|
+
let release;
|
|
154
|
+
try {
|
|
155
|
+
release = await acquireSupervisorLock(join(path, "worker"));
|
|
156
|
+
} catch {
|
|
157
|
+
return 2;
|
|
158
|
+
}
|
|
159
|
+
const fingerprint = await processIdentity(process.pid);
|
|
160
|
+
let writes = Promise.resolve();
|
|
161
|
+
const save = async (value) =>
|
|
162
|
+
privateWrite(join(path, "status.json"), {
|
|
163
|
+
...value,
|
|
164
|
+
jobId: id,
|
|
165
|
+
pid: process.pid,
|
|
166
|
+
processIdentity: fingerprint,
|
|
167
|
+
updatedAt: Date.now(),
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
await save({ state: "installing", complete: false });
|
|
171
|
+
const { strictInstallationMain } = await import("./strict-installation.mjs");
|
|
172
|
+
let partial = "",
|
|
173
|
+
final;
|
|
174
|
+
await strictInstallationMain(
|
|
175
|
+
[
|
|
176
|
+
"--provider",
|
|
177
|
+
job.flags.provider,
|
|
178
|
+
"--session",
|
|
179
|
+
job.flags.sessionId,
|
|
180
|
+
"--model",
|
|
181
|
+
job.flags.model,
|
|
182
|
+
"--base-url",
|
|
183
|
+
job.flags.baseUrl,
|
|
184
|
+
"--wait-seconds",
|
|
185
|
+
String(job.flags.waitSeconds),
|
|
186
|
+
...(job.flags.agentId ? ["--agent-id", job.flags.agentId] : []),
|
|
187
|
+
...(job.flags.environment ? ["--environment", job.flags.environment] : []),
|
|
188
|
+
...(job.flags.deviceLogin ? ["--device-login"] : []),
|
|
189
|
+
...(job.flags.noBrowser ? ["--no-browser"] : []),
|
|
190
|
+
],
|
|
191
|
+
{
|
|
192
|
+
...options,
|
|
193
|
+
foreground: true,
|
|
194
|
+
env,
|
|
195
|
+
stdout: {
|
|
196
|
+
write(chunk) {
|
|
197
|
+
partial += chunk;
|
|
198
|
+
let newline;
|
|
199
|
+
while ((newline = partial.indexOf("\n")) !== -1) {
|
|
200
|
+
const line = partial.slice(0, newline);
|
|
201
|
+
partial = partial.slice(newline + 1);
|
|
202
|
+
try {
|
|
203
|
+
const value = JSON.parse(line);
|
|
204
|
+
if (value.version === 1 && typeof value.complete === "boolean") final = value;
|
|
205
|
+
// Serialize writes below; callbacks never export credentials or message contents.
|
|
206
|
+
writes = writes.then(() =>
|
|
207
|
+
save({
|
|
208
|
+
state: "installing",
|
|
209
|
+
complete: false,
|
|
210
|
+
stage: value.stage,
|
|
211
|
+
...(value.stage === "sign_in_required" ? { url: value.url } : {}),
|
|
212
|
+
...(value.stage === "sign_in_required" && value.approvalCode
|
|
213
|
+
? { approvalCode: value.approvalCode }
|
|
214
|
+
: {}),
|
|
215
|
+
...(value.stage === "tests_requested" ? { marker: value.marker } : {}),
|
|
216
|
+
...(value.message ? { message: value.message } : {}),
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
} catch {
|
|
220
|
+
/* Non-JSON text is never forwarded into a receipt. */
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
final ??= { complete: false, state: "incomplete", code: "SETUP_WORKER_FAILED" };
|
|
228
|
+
await writes;
|
|
229
|
+
const state = final.complete
|
|
230
|
+
? "complete"
|
|
231
|
+
: final.state === "paused"
|
|
232
|
+
? "paused"
|
|
233
|
+
: ["AUTHORIZED_TESTS_PENDING", "RECOVERY_WAITING_FOR_IDLE"].includes(final.code)
|
|
234
|
+
? "waiting"
|
|
235
|
+
: "blocked";
|
|
236
|
+
await save({ ...final, state });
|
|
237
|
+
if (!options.adapter && ["complete", "blocked", "paused"].includes(state)) {
|
|
238
|
+
const { defaultCodexQueue } = await import("./codex-chat-bridge.mjs");
|
|
239
|
+
await defaultCodexQueue({
|
|
240
|
+
threadId: job.flags.sessionId,
|
|
241
|
+
environment: env,
|
|
242
|
+
prompt: `Totem's strict installer finished this attempt. Report this verified receipt to the owner, preserving every incomplete check and limitation. Do not rerun a denied operation or claim completion when complete is false. Receipt: ${JSON.stringify(final)}`,
|
|
243
|
+
}).catch(() => {});
|
|
244
|
+
}
|
|
245
|
+
return final.complete ? 0 : 2;
|
|
246
|
+
} catch {
|
|
247
|
+
await writes.catch(() => {});
|
|
248
|
+
await save({ complete: false, state: "blocked", code: "SETUP_WORKER_FAILED" });
|
|
249
|
+
return 2;
|
|
250
|
+
} finally {
|
|
251
|
+
await release();
|
|
252
|
+
}
|
|
253
|
+
}
|