totemlayer 0.1.4 → 0.1.5
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 +24 -3
- package/bin/agent-profile.mjs +466 -0
- package/bin/agent-runner.mjs +609 -0
- package/bin/agent-setup.mjs +659 -0
- package/bin/docker-test-sandbox.mjs +61 -0
- package/bin/local-model-provider.mjs +29 -0
- package/bin/mcp-install.mjs +240 -1
- package/bin/mcp.mjs +1 -1
- package/bin/mission-agent-service.mjs +441 -0
- package/bin/mission-agent-session.mjs +188 -0
- package/bin/mission-supervisor.mjs +166 -0
- package/bin/mission-worker.mjs +3383 -0
- package/bin/network-runtime.mjs +86 -27
- package/bin/network.mjs +96 -5
- package/bin/platform-client.mjs +1 -0
- package/bin/sdk/action-types.js +1 -0
- package/bin/sdk/actions.js +84 -0
- package/bin/sdk/index.js +75 -0
- package/bin/sdk/request-options.js +36 -0
- package/bin/sdk/transport.js +8 -3
- package/bin/totem.mjs +23 -2
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { localClientEnvironment } from "./platform-client.mjs";
|
|
7
|
+
import { missionModelEndpoint } from "./mission-worker.mjs";
|
|
8
|
+
|
|
9
|
+
const CLI_PATH = fileURLToPath(new URL("./totem.mjs", import.meta.url));
|
|
10
|
+
|
|
11
|
+
export async function runMissionSupervisor({
|
|
12
|
+
keyFiles,
|
|
13
|
+
missionId,
|
|
14
|
+
baseUrl,
|
|
15
|
+
provider = "qwen-local",
|
|
16
|
+
model,
|
|
17
|
+
ollamaUrl,
|
|
18
|
+
qwenUrl,
|
|
19
|
+
nimUrl,
|
|
20
|
+
reasoning = false,
|
|
21
|
+
repositoryRoot,
|
|
22
|
+
stateDirectory,
|
|
23
|
+
pollMs,
|
|
24
|
+
once = false,
|
|
25
|
+
signal,
|
|
26
|
+
emit = () => {},
|
|
27
|
+
}) {
|
|
28
|
+
if (!Array.isArray(keyFiles) || keyFiles.length < 1)
|
|
29
|
+
throw new Error("mission-start needs at least one --agent-key-file path.");
|
|
30
|
+
if (keyFiles.length > 100)
|
|
31
|
+
throw new Error("mission-start accepts at most 100 local agent key files.");
|
|
32
|
+
model ??= provider === "qwen-local" ? "qwen3.5-9b" : undefined;
|
|
33
|
+
if (!missionId || !["qwen-local", "ollama-local", "nim-local"].includes(provider) || !model)
|
|
34
|
+
throw new Error(
|
|
35
|
+
"mission-start requires --mission-id, --provider qwen-local|ollama-local|nim-local, and --model.",
|
|
36
|
+
);
|
|
37
|
+
missionModelEndpoint(provider, { ollamaUrl, qwenUrl, nimUrl });
|
|
38
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(missionId))
|
|
39
|
+
throw new Error("Provide a valid mission ID.");
|
|
40
|
+
const uniqueFiles = new Set();
|
|
41
|
+
const uniqueCredentials = new Set();
|
|
42
|
+
for (const path of keyFiles) {
|
|
43
|
+
const resolved = await realpath(path);
|
|
44
|
+
const info = await stat(resolved);
|
|
45
|
+
if (
|
|
46
|
+
!info.isFile() ||
|
|
47
|
+
info.size > 8192 ||
|
|
48
|
+
(process.platform !== "win32" && (info.mode & 0o077) !== 0)
|
|
49
|
+
)
|
|
50
|
+
throw new Error("Every agent key must be a private file (chmod 600), at most 8 KiB.");
|
|
51
|
+
if (uniqueFiles.has(resolved)) throw new Error("Use a different key file for each agent.");
|
|
52
|
+
const credential = (await readFile(resolved, "utf8")).trim();
|
|
53
|
+
if (!credential) throw new Error("Every agent key file must contain a credential.");
|
|
54
|
+
const digest = createHash("sha256").update(credential).digest("hex");
|
|
55
|
+
if (uniqueCredentials.has(digest))
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Each worker needs a different agent credential; two key files contain the same key.",
|
|
58
|
+
);
|
|
59
|
+
uniqueCredentials.add(digest);
|
|
60
|
+
uniqueFiles.add(resolved);
|
|
61
|
+
}
|
|
62
|
+
if (signal?.aborted) return;
|
|
63
|
+
const children = new Set();
|
|
64
|
+
const completions = [];
|
|
65
|
+
let failure = null;
|
|
66
|
+
let stopping = false;
|
|
67
|
+
let forceStop;
|
|
68
|
+
const stop = () => {
|
|
69
|
+
if (stopping) return;
|
|
70
|
+
stopping = true;
|
|
71
|
+
for (const child of children) child.kill("SIGTERM");
|
|
72
|
+
if (children.size) {
|
|
73
|
+
forceStop = setTimeout(() => {
|
|
74
|
+
for (const child of children) child.kill("SIGKILL");
|
|
75
|
+
}, 5000);
|
|
76
|
+
forceStop.unref();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const fail = (index, reason) => {
|
|
80
|
+
if (stopping || signal?.aborted) return;
|
|
81
|
+
failure = new Error(
|
|
82
|
+
`Mission worker ${index + 1} ${reason}. The other workers were stopped; correct the error and restart with the same state directory.`,
|
|
83
|
+
);
|
|
84
|
+
emit({ type: "mission_worker_stopped", agentIndex: index + 1, message: failure.message });
|
|
85
|
+
stop();
|
|
86
|
+
};
|
|
87
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
88
|
+
try {
|
|
89
|
+
for (const [index, keyFile] of [...uniqueFiles].entries()) {
|
|
90
|
+
if (stopping) break;
|
|
91
|
+
const args = [
|
|
92
|
+
CLI_PATH,
|
|
93
|
+
"network",
|
|
94
|
+
"mission-work",
|
|
95
|
+
"--mission-id",
|
|
96
|
+
missionId,
|
|
97
|
+
"--provider",
|
|
98
|
+
provider,
|
|
99
|
+
"--model",
|
|
100
|
+
model,
|
|
101
|
+
...(baseUrl ? ["--base-url", baseUrl] : []),
|
|
102
|
+
...(ollamaUrl ? ["--ollama-url", ollamaUrl] : []),
|
|
103
|
+
...(qwenUrl ? ["--qwen-url", qwenUrl] : []),
|
|
104
|
+
...(nimUrl ? ["--nim-url", nimUrl] : []),
|
|
105
|
+
...(reasoning ? ["--reasoning"] : []),
|
|
106
|
+
...(repositoryRoot ? ["--repo-root", repositoryRoot] : []),
|
|
107
|
+
...(stateDirectory ? ["--state-dir", stateDirectory] : []),
|
|
108
|
+
...(pollMs ? ["--poll-ms", String(pollMs)] : []),
|
|
109
|
+
...(once ? ["--once"] : []),
|
|
110
|
+
"--json",
|
|
111
|
+
];
|
|
112
|
+
const env = localClientEnvironment(process.env, {
|
|
113
|
+
TOTEM_NETWORK_API_KEY_FILE: keyFile,
|
|
114
|
+
...(baseUrl ? { TOTEM_BASE_URL: baseUrl } : {}),
|
|
115
|
+
});
|
|
116
|
+
delete env.TOTEM_NETWORK_API_KEY;
|
|
117
|
+
const child = spawn(process.execPath, args, { env, stdio: ["ignore", "pipe", "pipe"] });
|
|
118
|
+
children.add(child);
|
|
119
|
+
completions.push(
|
|
120
|
+
new Promise((resolveRun) => {
|
|
121
|
+
child.once("error", () => {
|
|
122
|
+
fail(index, "could not start");
|
|
123
|
+
children.delete(child);
|
|
124
|
+
resolveRun();
|
|
125
|
+
});
|
|
126
|
+
child.once("close", (code, childSignal) => {
|
|
127
|
+
children.delete(child);
|
|
128
|
+
if (code !== 0)
|
|
129
|
+
fail(index, childSignal ? `exited on ${childSignal}` : `exited with code ${code}`);
|
|
130
|
+
resolveRun();
|
|
131
|
+
});
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
for (const [stream, source] of [
|
|
135
|
+
[child.stdout, "stdout"],
|
|
136
|
+
[child.stderr, "stderr"],
|
|
137
|
+
]) {
|
|
138
|
+
let buffer = "";
|
|
139
|
+
stream.setEncoding("utf8");
|
|
140
|
+
stream.on("data", (chunk) => {
|
|
141
|
+
buffer += chunk;
|
|
142
|
+
for (;;) {
|
|
143
|
+
const newline = buffer.indexOf("\n");
|
|
144
|
+
if (newline < 0) break;
|
|
145
|
+
const line = buffer.slice(0, newline).trim();
|
|
146
|
+
buffer = buffer.slice(newline + 1);
|
|
147
|
+
if (line) emit({ type: "agent_worker_output", agentIndex: index + 1, source, line });
|
|
148
|
+
}
|
|
149
|
+
if (buffer.length > 8192) buffer = buffer.slice(-8192);
|
|
150
|
+
});
|
|
151
|
+
stream.on("end", () => {
|
|
152
|
+
const line = buffer.trim();
|
|
153
|
+
if (line) emit({ type: "agent_worker_output", agentIndex: index + 1, source, line });
|
|
154
|
+
buffer = "";
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
await Promise.all(completions);
|
|
159
|
+
if (failure && !signal?.aborted) throw failure;
|
|
160
|
+
} finally {
|
|
161
|
+
signal?.removeEventListener("abort", stop);
|
|
162
|
+
stop();
|
|
163
|
+
await Promise.all(completions);
|
|
164
|
+
clearTimeout(forceStop);
|
|
165
|
+
}
|
|
166
|
+
}
|