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,181 @@
|
|
|
1
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { privateRead, privateWrite } from "./runtime-installation.mjs";
|
|
4
|
+
import { readAgentCredential } from "./agent-profile.mjs";
|
|
5
|
+
|
|
6
|
+
const path = (directory, name) => join(directory, "strict-setup", `${name}.json`);
|
|
7
|
+
const read = async (directory, name) =>
|
|
8
|
+
privateRead(path(directory, name)).catch((error) => {
|
|
9
|
+
if (error.code !== "ENOENT") throw error;
|
|
10
|
+
});
|
|
11
|
+
const uuid = (value) =>
|
|
12
|
+
typeof value === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value);
|
|
13
|
+
const matches = (request, identity, sessionId) =>
|
|
14
|
+
request?.version === 1 &&
|
|
15
|
+
request.agentId === identity.id &&
|
|
16
|
+
request.environment === identity.environment &&
|
|
17
|
+
request.sessionId === sessionId &&
|
|
18
|
+
/^[a-f0-9]{64}$/.test(request.binding ?? "") &&
|
|
19
|
+
/^[a-f0-9]{32}$/.test(request.nonce ?? "");
|
|
20
|
+
const credentialFingerprint = async (profile) =>
|
|
21
|
+
createHash("sha256")
|
|
22
|
+
.update(await readAgentCredential(profile))
|
|
23
|
+
.digest("hex");
|
|
24
|
+
|
|
25
|
+
export async function prepareSetupTests(profile, context) {
|
|
26
|
+
const previous = await read(profile.stateDirectory, "request");
|
|
27
|
+
const fingerprint = await credentialFingerprint(profile);
|
|
28
|
+
if (context.connectionFingerprint !== fingerprint)
|
|
29
|
+
throw Object.assign(new Error("Credential generation changed."), {
|
|
30
|
+
code: "SETUP_BINDING_CHANGED",
|
|
31
|
+
});
|
|
32
|
+
if (
|
|
33
|
+
previous &&
|
|
34
|
+
(previous.binding !== context.binding ||
|
|
35
|
+
previous.agentId !== profile.agentId ||
|
|
36
|
+
previous.sessionId !== context.host.sessionId)
|
|
37
|
+
)
|
|
38
|
+
throw Object.assign(new Error("Saved verification belongs to another host binding."), {
|
|
39
|
+
code: "SETUP_BINDING_CHANGED",
|
|
40
|
+
});
|
|
41
|
+
const request = previous ?? {
|
|
42
|
+
version: 1,
|
|
43
|
+
binding: context.binding,
|
|
44
|
+
agentId: profile.agentId,
|
|
45
|
+
environment: profile.environment,
|
|
46
|
+
sessionId: context.host.sessionId,
|
|
47
|
+
model: context.host.model,
|
|
48
|
+
nonce: randomBytes(16).toString("hex"),
|
|
49
|
+
issuedAt: Date.now(),
|
|
50
|
+
credentialFingerprint: fingerprint,
|
|
51
|
+
};
|
|
52
|
+
if (!previous) await privateWrite(path(profile.stateDirectory, "request"), request);
|
|
53
|
+
return { marker: `Totem setup test ${request.nonce}`, issuedAt: request.issuedAt };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Only the existing native queue adapter records dispatch; no public proof-submission tool exists. */
|
|
57
|
+
export async function observeSetupDispatch({ directory, identity, sessionId, kind, id, text }) {
|
|
58
|
+
if (typeof directory !== "string" || !["peer", "ownerChat"].includes(kind)) return;
|
|
59
|
+
const request = await read(directory, "request");
|
|
60
|
+
if (
|
|
61
|
+
!matches(request, identity, sessionId) ||
|
|
62
|
+
!uuid(id) ||
|
|
63
|
+
typeof text !== "string" ||
|
|
64
|
+
!text.includes(`Totem setup test ${request.nonce}`)
|
|
65
|
+
)
|
|
66
|
+
return;
|
|
67
|
+
const previous = await read(directory, `${kind}-dispatch`);
|
|
68
|
+
if (previous && previous.id !== id) return;
|
|
69
|
+
await privateWrite(path(directory, `${kind}-dispatch`), {
|
|
70
|
+
version: 1,
|
|
71
|
+
binding: request.binding,
|
|
72
|
+
id,
|
|
73
|
+
sessionId,
|
|
74
|
+
dispatchedAt: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function recordSetupPeerReply({
|
|
79
|
+
profile,
|
|
80
|
+
identity,
|
|
81
|
+
message,
|
|
82
|
+
reply,
|
|
83
|
+
client,
|
|
84
|
+
sessionId,
|
|
85
|
+
}) {
|
|
86
|
+
const request = await read(profile.stateDirectory, "request");
|
|
87
|
+
if (!request || request.credentialFingerprint !== (await credentialFingerprint(profile))) return;
|
|
88
|
+
if (
|
|
89
|
+
!matches(request, identity, sessionId) ||
|
|
90
|
+
message.to !== identity.address ||
|
|
91
|
+
message.from === identity.address ||
|
|
92
|
+
!uuid(reply?.id) ||
|
|
93
|
+
reply.inReplyToId !== message.id ||
|
|
94
|
+
reply.conversationId !== message.conversationId ||
|
|
95
|
+
reply.to !== message.from ||
|
|
96
|
+
reply.from !== identity.address ||
|
|
97
|
+
!message.content?.text?.includes(`Totem setup test ${request.nonce}`)
|
|
98
|
+
)
|
|
99
|
+
return;
|
|
100
|
+
const dispatch = await read(profile.stateDirectory, "peer-dispatch");
|
|
101
|
+
if (dispatch?.id !== message.id || dispatch.binding !== request.binding) return;
|
|
102
|
+
const receivedAt = Date.parse(message.createdAt);
|
|
103
|
+
if (!Number.isFinite(receivedAt) || receivedAt < request.issuedAt) return;
|
|
104
|
+
const replies = [];
|
|
105
|
+
let cursor;
|
|
106
|
+
for (let page = 0; page < 50; page++) {
|
|
107
|
+
const result = await client.conversations.messages(message.conversationId, {
|
|
108
|
+
limit: 100,
|
|
109
|
+
...(cursor ? { cursor } : {}),
|
|
110
|
+
});
|
|
111
|
+
replies.push(...result.data.filter((item) => item.inReplyToId === message.id));
|
|
112
|
+
if (!result.nextCursor) {
|
|
113
|
+
cursor = undefined;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
cursor = result.nextCursor;
|
|
117
|
+
}
|
|
118
|
+
if (cursor || replies.length !== 1 || replies[0].id !== reply.id) return;
|
|
119
|
+
const acknowledged = await client.messages.get(message.id);
|
|
120
|
+
if (acknowledged.state !== "acknowledged") return;
|
|
121
|
+
await privateWrite(path(profile.stateDirectory, "peer"), {
|
|
122
|
+
binding: request.binding,
|
|
123
|
+
nativeSessionId: sessionId,
|
|
124
|
+
messageId: message.id,
|
|
125
|
+
replyId: reply.id,
|
|
126
|
+
receivedAt,
|
|
127
|
+
repliedAt: Date.now(),
|
|
128
|
+
correlated: true,
|
|
129
|
+
exactlyOneReply: true,
|
|
130
|
+
acknowledgedAfterReply: true,
|
|
131
|
+
authorized: true,
|
|
132
|
+
newMessageTriggeredWake: true,
|
|
133
|
+
appearedInNativeChat: true,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function recordSetupOwnerChat({
|
|
138
|
+
profile,
|
|
139
|
+
identity,
|
|
140
|
+
commandId,
|
|
141
|
+
saved,
|
|
142
|
+
result,
|
|
143
|
+
text,
|
|
144
|
+
sessionId,
|
|
145
|
+
leaseRenewed,
|
|
146
|
+
}) {
|
|
147
|
+
const request = await read(profile.stateDirectory, "request");
|
|
148
|
+
if (!request || request.credentialFingerprint !== (await credentialFingerprint(profile))) return;
|
|
149
|
+
if (
|
|
150
|
+
!matches(request, identity, sessionId) ||
|
|
151
|
+
!text?.includes(`Totem setup test ${request.nonce}`) ||
|
|
152
|
+
!uuid(commandId) ||
|
|
153
|
+
!uuid(saved.chatId) ||
|
|
154
|
+
!uuid(result?.event?.id) ||
|
|
155
|
+
result.run?.state !== "completed" ||
|
|
156
|
+
result.event.chatId !== saved.chatId ||
|
|
157
|
+
leaseRenewed !== true
|
|
158
|
+
)
|
|
159
|
+
return;
|
|
160
|
+
const dispatch = await read(profile.stateDirectory, "ownerChat-dispatch");
|
|
161
|
+
if (dispatch?.id !== commandId || dispatch.binding !== request.binding) return;
|
|
162
|
+
await privateWrite(path(profile.stateDirectory, "ownerChat"), {
|
|
163
|
+
binding: request.binding,
|
|
164
|
+
commandId,
|
|
165
|
+
chatId: saved.chatId,
|
|
166
|
+
eventId: result.event.id,
|
|
167
|
+
queuedAt: dispatch.dispatchedAt,
|
|
168
|
+
completedAt: Date.now(),
|
|
169
|
+
claimed: true,
|
|
170
|
+
leaseRenewed: true,
|
|
171
|
+
completedInOriginalChat: true,
|
|
172
|
+
authorized: true,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function readSetupTests(profile) {
|
|
177
|
+
return {
|
|
178
|
+
peer: await read(profile.stateDirectory, "peer"),
|
|
179
|
+
ownerChat: await read(profile.stateDirectory, "ownerChat"),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { opendir, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
7
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
8
|
+
import { pluginConnectionOptions, createPluginConnector } from "./plugin-connect.mjs";
|
|
9
|
+
import { installTotemMcp } from "./mcp-install.mjs";
|
|
10
|
+
import { connectCodexHarness, ensureInteractiveHarness } from "./harness-bootstrap.mjs";
|
|
11
|
+
import { bindInteractiveProfile, readAgentProfile, readAgentCredential } from "./agent-profile.mjs";
|
|
12
|
+
import { readAgentRuntimeStatus } from "./agent-runner.mjs";
|
|
13
|
+
import { automaticEnabled } from "./automatic-runtime.mjs";
|
|
14
|
+
import { runtimeRoot, privateRead, privateWrite } from "./runtime-installation.mjs";
|
|
15
|
+
import { readReleaseSetupGuide } from "./setup-guide.mjs";
|
|
16
|
+
import { prepareSetupTests, readSetupTests } from "./setup-receipts.mjs";
|
|
17
|
+
import { defaultCodexQueue } from "./codex-chat-bridge.mjs";
|
|
18
|
+
|
|
19
|
+
const execute = promisify(execFile);
|
|
20
|
+
const fail = (code) => {
|
|
21
|
+
throw Object.assign(new Error(code), { code });
|
|
22
|
+
};
|
|
23
|
+
function mcpIdentity(result) {
|
|
24
|
+
if (result.isError) fail("MCP_IDENTITY_UNVERIFIED");
|
|
25
|
+
const payload =
|
|
26
|
+
result.structuredContent ??
|
|
27
|
+
JSON.parse(result.content.find((item) => item.type === "text").text);
|
|
28
|
+
return payload.data ?? payload;
|
|
29
|
+
}
|
|
30
|
+
const sha = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
31
|
+
const missing = async (promise) =>
|
|
32
|
+
promise.catch((error) => {
|
|
33
|
+
if (error.code !== "ENOENT") throw error;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function stateSnapshot(directory, prefix = "", result = {}) {
|
|
37
|
+
const entries = await missing(opendir(join(directory, prefix)));
|
|
38
|
+
if (!entries) return result;
|
|
39
|
+
for await (const entry of entries) {
|
|
40
|
+
if (Object.keys(result).length > 10000) fail("RECOVERY_STATE_TOO_LARGE");
|
|
41
|
+
const relative = join(prefix, entry.name);
|
|
42
|
+
if (entry.isDirectory() && /^[a-f0-9]{64}$/.test(entry.name))
|
|
43
|
+
await stateSnapshot(directory, relative, result);
|
|
44
|
+
else if (entry.isFile() && /^[a-f0-9]{64}\.json$/.test(entry.name)) {
|
|
45
|
+
const contents = await readFile(join(directory, relative));
|
|
46
|
+
if (contents.length > 1048576) fail("RECOVERY_STATE_TOO_LARGE");
|
|
47
|
+
result[relative] = createHash("sha256").update(contents).digest("hex");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Uses installed native metadata and Totem's existing bound MCP connector; never chooses a model. */
|
|
54
|
+
export function createStrictCodexAdapter(flags, options = {}) {
|
|
55
|
+
const inherited = options.env ?? process.env;
|
|
56
|
+
const write = options.write ?? ((value) => process.stdout.write(JSON.stringify(value) + "\n"));
|
|
57
|
+
const baseUrl = flags.baseUrl;
|
|
58
|
+
let selection = pluginConnectionOptions(
|
|
59
|
+
[
|
|
60
|
+
"--provider",
|
|
61
|
+
"codex",
|
|
62
|
+
"--base-url",
|
|
63
|
+
baseUrl,
|
|
64
|
+
...(flags.deviceLogin ? ["--device-login"] : []),
|
|
65
|
+
...(flags.noBrowser ? ["--no-browser"] : []),
|
|
66
|
+
...(flags.agentId ? ["--agent-id", flags.agentId] : []),
|
|
67
|
+
...(flags.environment ? ["--environment", flags.environment] : []),
|
|
68
|
+
],
|
|
69
|
+
inherited,
|
|
70
|
+
);
|
|
71
|
+
let env = { ...inherited, TOTEM_CONFIG_DIR: selection.configDirectory };
|
|
72
|
+
let rpc,
|
|
73
|
+
host,
|
|
74
|
+
connector,
|
|
75
|
+
client,
|
|
76
|
+
profile,
|
|
77
|
+
existing,
|
|
78
|
+
identity,
|
|
79
|
+
activation,
|
|
80
|
+
requested = false,
|
|
81
|
+
existingEntry;
|
|
82
|
+
const pause = options.sleep ?? sleep;
|
|
83
|
+
async function snapshot() {
|
|
84
|
+
const threadId = flags.sessionId ?? inherited.CODEX_THREAD_ID;
|
|
85
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(threadId ?? ""))
|
|
86
|
+
fail("EXACT_NATIVE_SESSION_REQUIRED");
|
|
87
|
+
if (!rpc) {
|
|
88
|
+
rpc = (options.connectHarness ?? connectCodexHarness)(inherited);
|
|
89
|
+
await rpc.initialize();
|
|
90
|
+
}
|
|
91
|
+
// Resuming with no overrides reads the persisted model and policy. No turn starts.
|
|
92
|
+
const current = await rpc.request("thread/resume", { threadId });
|
|
93
|
+
if (
|
|
94
|
+
current?.thread?.id !== threadId ||
|
|
95
|
+
current.thread.ephemeral !== false ||
|
|
96
|
+
current.thread.archived
|
|
97
|
+
)
|
|
98
|
+
fail("EXACT_NATIVE_SESSION_REQUIRED");
|
|
99
|
+
if (!current.model || (flags.model && current.model !== flags.model))
|
|
100
|
+
fail("CURRENT_MODEL_UNVERIFIED");
|
|
101
|
+
return {
|
|
102
|
+
name: "codex",
|
|
103
|
+
model: current.model,
|
|
104
|
+
sessionId: threadId,
|
|
105
|
+
modelVerified: true,
|
|
106
|
+
policyDigest: sha([
|
|
107
|
+
current.modelProvider,
|
|
108
|
+
current.approvalPolicy,
|
|
109
|
+
current.sandbox,
|
|
110
|
+
current.permissionProfile,
|
|
111
|
+
current.reasoningEffort,
|
|
112
|
+
current.serviceTier,
|
|
113
|
+
]),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function attach() {
|
|
117
|
+
connector ??= (options.createConnector ?? createPluginConnector)(selection, {
|
|
118
|
+
env: { ...inherited, TOTEM_MODEL: host.model },
|
|
119
|
+
// Authentication prepares tools only. The controller verifies MCP identity before receiving starts.
|
|
120
|
+
activate: async () => ({ state: "needs_attention", ready: false, persistent: false }),
|
|
121
|
+
ensureHarness: async (saved, environment) => {
|
|
122
|
+
const verified = await client.callTool({ name: "totem_get_identity", arguments: {} });
|
|
123
|
+
const own = mcpIdentity(verified);
|
|
124
|
+
if (
|
|
125
|
+
own.id !== saved.agentId ||
|
|
126
|
+
own.address !== saved.address ||
|
|
127
|
+
own.environment !== saved.environment ||
|
|
128
|
+
!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(own.organization?.id ?? "")
|
|
129
|
+
)
|
|
130
|
+
fail("AGENT_IDENTITY_MISMATCH");
|
|
131
|
+
const checked = await snapshot();
|
|
132
|
+
if (JSON.stringify(checked) !== JSON.stringify(host)) fail("CURRENT_MODEL_CHANGED");
|
|
133
|
+
if (saved.codexThreadId && saved.codexThreadId !== host.sessionId)
|
|
134
|
+
fail("EXISTING_RECEIVER_SESSION_CONFLICT");
|
|
135
|
+
if (saved.model !== "default" && saved.model !== host.model) fail("CURRENT_MODEL_CHANGED");
|
|
136
|
+
profile = saved.codexThreadId ? saved : await bindInteractiveProfile(saved, host.sessionId);
|
|
137
|
+
profile = await (options.ensureHarness ?? ensureInteractiveHarness)(profile, environment);
|
|
138
|
+
return profile;
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
if (!client) {
|
|
142
|
+
const [local, server] = InMemoryTransport.createLinkedPair();
|
|
143
|
+
client = new Client({ name: "totem_strict_installer", version: "1" });
|
|
144
|
+
await connector.server.connect(server);
|
|
145
|
+
await client.connect(local);
|
|
146
|
+
}
|
|
147
|
+
const pending = connector.start();
|
|
148
|
+
let done = false;
|
|
149
|
+
void pending.then(
|
|
150
|
+
() => {
|
|
151
|
+
done = true;
|
|
152
|
+
},
|
|
153
|
+
() => {
|
|
154
|
+
done = true;
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
while (!done) {
|
|
158
|
+
const status = await connector.status();
|
|
159
|
+
if (status.state === "sign_in_required" && !requested) {
|
|
160
|
+
requested = true;
|
|
161
|
+
await write({
|
|
162
|
+
stage: "sign_in_required",
|
|
163
|
+
url: status.signInUrl,
|
|
164
|
+
...(status.approvalCode ? { approvalCode: status.approvalCode } : {}),
|
|
165
|
+
message: "Sign in and approve this exact agent connection in Totem.",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
await pause(500);
|
|
169
|
+
}
|
|
170
|
+
await pending;
|
|
171
|
+
const status = await connector.status();
|
|
172
|
+
if (!status.toolsConnected) fail(status.code ?? "CONNECTION_APPROVAL_REQUIRED");
|
|
173
|
+
const result = await client.callTool({ name: "totem_get_identity", arguments: {} });
|
|
174
|
+
identity = mcpIdentity(result);
|
|
175
|
+
if (!profile?.codexThreadId || profile.codexThreadId !== host.sessionId)
|
|
176
|
+
fail("EXACT_RECEIVER_UNVERIFIED");
|
|
177
|
+
if (!profile && status.agentId)
|
|
178
|
+
profile = await readAgentProfile(status.agentId, { environment: env });
|
|
179
|
+
return {
|
|
180
|
+
identity,
|
|
181
|
+
mcpIdentityVerified: true,
|
|
182
|
+
transport: "mcp",
|
|
183
|
+
origin: baseUrl,
|
|
184
|
+
fingerprint: createHash("sha256")
|
|
185
|
+
.update(await readAgentCredential(profile))
|
|
186
|
+
.digest("hex"),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
readGuide: options.readGuide ?? readReleaseSetupGuide,
|
|
191
|
+
async inspect(readOnly = false) {
|
|
192
|
+
host = await snapshot();
|
|
193
|
+
const result = await (options.execute ?? execute)("codex", ["mcp", "list", "--json"], {
|
|
194
|
+
env: inherited,
|
|
195
|
+
encoding: "utf8",
|
|
196
|
+
timeout: 5000,
|
|
197
|
+
maxBuffer: 65536,
|
|
198
|
+
}).catch((error) => {
|
|
199
|
+
if (
|
|
200
|
+
/no mcp server|not found|does not exist/i.test(
|
|
201
|
+
`${error.stdout ?? ""}\n${error.stderr ?? ""}`,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
return null;
|
|
205
|
+
fail("MCP_SETTINGS_UNVERIFIED");
|
|
206
|
+
});
|
|
207
|
+
if (result) {
|
|
208
|
+
const entries = JSON.parse(result.stdout);
|
|
209
|
+
if (!Array.isArray(entries)) fail("MCP_SETTINGS_UNVERIFIED");
|
|
210
|
+
let candidates = entries.filter((entry) => {
|
|
211
|
+
const t = entry.transport;
|
|
212
|
+
if (t?.type === "streamable_http") {
|
|
213
|
+
try {
|
|
214
|
+
return new URL(t.url).origin === baseUrl && new URL(t.url).pathname === "/mcp";
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return (
|
|
220
|
+
t?.type === "stdio" &&
|
|
221
|
+
Array.isArray(t.args) &&
|
|
222
|
+
/totem/i.test(`${t.command} ${t.args.join(" ")}`) &&
|
|
223
|
+
(t.args.includes("connect") || t.args.includes("mcp"))
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
// Prefer a fully scoped match while retaining unknown OAuth/legacy entries
|
|
227
|
+
// as conflicts. Known other identities are not this agent's connection.
|
|
228
|
+
if (flags.agentId) {
|
|
229
|
+
const scoped = new Map();
|
|
230
|
+
for (const entry of candidates) {
|
|
231
|
+
const args = entry.transport?.args;
|
|
232
|
+
if (entry.transport?.type !== "stdio" || !args?.includes("connect")) continue;
|
|
233
|
+
try {
|
|
234
|
+
scoped.set(
|
|
235
|
+
entry,
|
|
236
|
+
pluginConnectionOptions(args.slice(args.indexOf("connect") + 1), inherited),
|
|
237
|
+
);
|
|
238
|
+
} catch {
|
|
239
|
+
/* An unknown existing binding must remain preserved. */
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const matches = (saved) =>
|
|
243
|
+
saved?.agentId === flags.agentId &&
|
|
244
|
+
saved.baseUrl === baseUrl &&
|
|
245
|
+
(!flags.environment || saved.environment === flags.environment);
|
|
246
|
+
if ([...scoped.values()].some(matches))
|
|
247
|
+
candidates = candidates.filter(
|
|
248
|
+
(entry) => !scoped.has(entry) || matches(scoped.get(entry)),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (candidates.length > 1) fail("MULTIPLE_MCP_CONNECTIONS_REQUIRE_SELECTION");
|
|
252
|
+
const entry = candidates[0];
|
|
253
|
+
if (entry) {
|
|
254
|
+
if (entry.enabled === false) fail("RECEIVER_INTENTIONALLY_PAUSED");
|
|
255
|
+
// A native OAuth connection must be reused through that host, never duplicated by this installer.
|
|
256
|
+
if (
|
|
257
|
+
entry.transport?.type !== "stdio" ||
|
|
258
|
+
!entry.transport.args?.includes("connect") ||
|
|
259
|
+
!entry.transport.args?.includes("codex")
|
|
260
|
+
)
|
|
261
|
+
fail("EXISTING_MCP_REQUIRES_NATIVE_RECEIVER_ADAPTER");
|
|
262
|
+
const args = entry.transport.args.slice(entry.transport.args.indexOf("connect") + 1);
|
|
263
|
+
const chosen = pluginConnectionOptions(args, inherited);
|
|
264
|
+
if (
|
|
265
|
+
chosen.baseUrl !== baseUrl ||
|
|
266
|
+
(flags.agentId && chosen.agentId !== flags.agentId) ||
|
|
267
|
+
(flags.environment && chosen.environment !== flags.environment)
|
|
268
|
+
)
|
|
269
|
+
fail("AGENT_IDENTITY_MISMATCH");
|
|
270
|
+
if (entry.transport.env?.TOTEM_AUTO_START === "0") fail("RECEIVER_INTENTIONALLY_PAUSED");
|
|
271
|
+
if (entry.transport.env?.TOTEM_CONFIG_DIR)
|
|
272
|
+
fail("EXISTING_MCP_REQUIRES_NATIVE_RECEIVER_ADAPTER");
|
|
273
|
+
selection = chosen;
|
|
274
|
+
env = { ...inherited, TOTEM_CONFIG_DIR: selection.configDirectory };
|
|
275
|
+
existingEntry = entry;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const binding = await missing(
|
|
279
|
+
privateRead(join(selection.configDirectory, "plugin-binding.json")),
|
|
280
|
+
);
|
|
281
|
+
let paused = false;
|
|
282
|
+
if (binding) {
|
|
283
|
+
profile = await readAgentProfile(binding.profile, { environment: env });
|
|
284
|
+
if (
|
|
285
|
+
profile.provider !== "codex" ||
|
|
286
|
+
profile.baseUrl !== baseUrl ||
|
|
287
|
+
(flags.agentId && profile.agentId !== flags.agentId)
|
|
288
|
+
)
|
|
289
|
+
fail("AGENT_IDENTITY_MISMATCH");
|
|
290
|
+
paused = !(await automaticEnabled(profile));
|
|
291
|
+
// A pause is an entirely read-only path, including native harness preparation.
|
|
292
|
+
if (paused) {
|
|
293
|
+
const { readAgentCredential } = await import("./agent-profile.mjs");
|
|
294
|
+
const { createTotemMcpServer } = await import("./mcp.mjs");
|
|
295
|
+
const server = createTotemMcpServer({
|
|
296
|
+
apiKey: await readAgentCredential(profile),
|
|
297
|
+
baseUrl,
|
|
298
|
+
expectedAgentId: profile.agentId,
|
|
299
|
+
expectedEnvironment: profile.environment,
|
|
300
|
+
});
|
|
301
|
+
const [a, b] = InMemoryTransport.createLinkedPair();
|
|
302
|
+
const reader = new Client({ name: "totem_paused_identity_check", version: "1" });
|
|
303
|
+
try {
|
|
304
|
+
await server.connect(b);
|
|
305
|
+
await reader.connect(a);
|
|
306
|
+
const result = await reader.callTool({ name: "totem_get_identity", arguments: {} });
|
|
307
|
+
identity = mcpIdentity(result);
|
|
308
|
+
} finally {
|
|
309
|
+
await reader.close();
|
|
310
|
+
await server.close();
|
|
311
|
+
}
|
|
312
|
+
existing = {
|
|
313
|
+
identity,
|
|
314
|
+
mcpIdentityVerified: true,
|
|
315
|
+
transport: "mcp",
|
|
316
|
+
origin: baseUrl,
|
|
317
|
+
fingerprint: createHash("sha256")
|
|
318
|
+
.update(await readAgentCredential(profile))
|
|
319
|
+
.digest("hex"),
|
|
320
|
+
};
|
|
321
|
+
} else if (!readOnly) existing = await attach();
|
|
322
|
+
}
|
|
323
|
+
return { host, customMcp: true, paused, ...(existing ? { connection: existing } : {}) };
|
|
324
|
+
},
|
|
325
|
+
async connect(transport) {
|
|
326
|
+
if (transport !== "mcp") fail("MCP_REQUIRED");
|
|
327
|
+
// Pin the exact current chat before exposing the self-starting entry to the native host.
|
|
328
|
+
const connected = await attach();
|
|
329
|
+
if (!existingEntry)
|
|
330
|
+
await (options.installMcp ?? installTotemMcp)(
|
|
331
|
+
[
|
|
332
|
+
"codex",
|
|
333
|
+
"--base-url",
|
|
334
|
+
baseUrl,
|
|
335
|
+
...(flags.deviceLogin ? ["--device-login"] : []),
|
|
336
|
+
...(flags.noBrowser ? ["--no-browser"] : []),
|
|
337
|
+
...(flags.agentId ? ["--agent-id", flags.agentId] : []),
|
|
338
|
+
...(flags.environment ? ["--environment", flags.environment] : []),
|
|
339
|
+
],
|
|
340
|
+
inherited,
|
|
341
|
+
);
|
|
342
|
+
return connected;
|
|
343
|
+
},
|
|
344
|
+
async identity(connection) {
|
|
345
|
+
return connection.identity;
|
|
346
|
+
},
|
|
347
|
+
async configureReceiver() {
|
|
348
|
+
if (!profile) fail("EXACT_RECEIVER_UNVERIFIED");
|
|
349
|
+
if (!(await automaticEnabled(profile))) fail("RECEIVER_INTENTIONALLY_PAUSED");
|
|
350
|
+
const { activateConnection } = await import("./installation.mjs");
|
|
351
|
+
activation = await (options.activateConnection ?? activateConnection)(profile, {
|
|
352
|
+
...env,
|
|
353
|
+
TOTEM_HOST_LOCK_ROOT: runtimeRoot(inherited),
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
async readReceiver(context) {
|
|
357
|
+
let current;
|
|
358
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
359
|
+
current = await readAgentRuntimeStatus(profile);
|
|
360
|
+
if (Number.isFinite(current.measuredCheckSeconds) || !current.running) break;
|
|
361
|
+
await pause(1000);
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
binding: context.binding,
|
|
365
|
+
count: current.running ? 1 : 0,
|
|
366
|
+
sameConnection: true,
|
|
367
|
+
running: current.listenerReady,
|
|
368
|
+
intervalSeconds: current.receiverIntervalSeconds,
|
|
369
|
+
measuredCheckSeconds: current.measuredCheckSeconds,
|
|
370
|
+
independentOfModel: true,
|
|
371
|
+
silentWhenIdle: true,
|
|
372
|
+
bothQueues: true,
|
|
373
|
+
privateState: true,
|
|
374
|
+
startupEnabled: activation?.persistent === true,
|
|
375
|
+
restartOnFailure: activation?.persistent === true,
|
|
376
|
+
persistent: activation?.persistent === true,
|
|
377
|
+
paused: !(await automaticEnabled(profile)),
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
readHost: snapshot,
|
|
381
|
+
async verifyRecovery(context) {
|
|
382
|
+
const current = await readAgentRuntimeStatus(profile);
|
|
383
|
+
const file = join(profile.stateDirectory, "strict-setup", "recovery.json");
|
|
384
|
+
const previous = await missing(privateRead(file));
|
|
385
|
+
if (
|
|
386
|
+
previous?.binding === context.binding &&
|
|
387
|
+
previous.guideSha256 === context.guideSha256 &&
|
|
388
|
+
previous.newRunVerified &&
|
|
389
|
+
previous.savedStatePreserved &&
|
|
390
|
+
current.ready &&
|
|
391
|
+
current.listenerReady
|
|
392
|
+
)
|
|
393
|
+
return previous;
|
|
394
|
+
if (current.phase !== "waiting" || !current.ready) fail("RECOVERY_WAITING_FOR_IDLE");
|
|
395
|
+
const before = await stateSnapshot(profile.stateDirectory);
|
|
396
|
+
// Request a normal exit of this exact run. The supervisor remains enabled and owns restart.
|
|
397
|
+
await privateWrite(join(profile.stateDirectory, "runner", `${current.runId}.stop`), {
|
|
398
|
+
runId: current.runId,
|
|
399
|
+
});
|
|
400
|
+
for (let attempt = 0; attempt < 120; attempt++) {
|
|
401
|
+
if (!(await automaticEnabled(profile))) fail("RECEIVER_INTENTIONALLY_PAUSED");
|
|
402
|
+
const after = await readAgentRuntimeStatus(profile);
|
|
403
|
+
if (after.runId !== current.runId && after.ready && after.listenerReady) {
|
|
404
|
+
const saved = await stateSnapshot(profile.stateDirectory);
|
|
405
|
+
const receipt = {
|
|
406
|
+
binding: context.binding,
|
|
407
|
+
guideSha256: context.guideSha256,
|
|
408
|
+
newRunVerified: true,
|
|
409
|
+
savedStatePreserved: Object.entries(before).every(
|
|
410
|
+
([name, hash]) => saved[name] === hash,
|
|
411
|
+
),
|
|
412
|
+
ownerPausePreserved: true,
|
|
413
|
+
noDuplicateReceiver: true,
|
|
414
|
+
};
|
|
415
|
+
await privateWrite(file, receipt);
|
|
416
|
+
return receipt;
|
|
417
|
+
}
|
|
418
|
+
await pause(1000);
|
|
419
|
+
}
|
|
420
|
+
return { binding: context.binding, newRunVerified: false };
|
|
421
|
+
},
|
|
422
|
+
async requestTests(context) {
|
|
423
|
+
const test = await prepareSetupTests(profile, context);
|
|
424
|
+
await write({
|
|
425
|
+
stage: "tests_requested",
|
|
426
|
+
marker: test.marker,
|
|
427
|
+
message:
|
|
428
|
+
"Send one permitted peer message containing this marker, and separately send an owner message containing it in this agent's Totem website chat. Use only an already approved peer. Receiving stays active; no further native prompt is needed.",
|
|
429
|
+
});
|
|
430
|
+
const noticePath = join(profile.stateDirectory, "strict-setup", "test-notice.json");
|
|
431
|
+
if (!(await missing(privateRead(noticePath)))) {
|
|
432
|
+
await defaultCodexQueue({
|
|
433
|
+
threadId: host.sessionId,
|
|
434
|
+
environment: inherited,
|
|
435
|
+
prompt: `Totem's strict installer is waiting for its required incoming tests. Ask this owner to send one permitted incoming peer message containing ${test.marker}, and separately send an owner website-chat message containing that marker in this identity's original Totem chat. Do not send unsolicited traffic or change permissions. The installed receiver and verifier remain active; no further native prompt is needed. Report the tests as pending until their independent receipts exist.`,
|
|
436
|
+
});
|
|
437
|
+
await privateWrite(noticePath, { markerIssuedAt: test.issuedAt });
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
async readTests() {
|
|
441
|
+
const deadline = Date.now() + flags.waitSeconds * 1000;
|
|
442
|
+
do {
|
|
443
|
+
const tests = await readSetupTests(profile);
|
|
444
|
+
if (tests.peer && tests.ownerChat) return tests;
|
|
445
|
+
if (!(await automaticEnabled(profile))) fail("RECEIVER_INTENTIONALLY_PAUSED");
|
|
446
|
+
if (Date.now() >= deadline) return tests;
|
|
447
|
+
await pause(1000);
|
|
448
|
+
} while (true);
|
|
449
|
+
},
|
|
450
|
+
async close() {
|
|
451
|
+
await client?.close();
|
|
452
|
+
await connector?.close();
|
|
453
|
+
await rpc?.close();
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|