totemlayer 0.1.20 → 0.1.24
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 +31 -29
- package/bin/agent-profile.mjs +13 -0
- package/bin/agent-runner.mjs +31 -2
- package/bin/agent-setup.mjs +58 -6
- package/bin/automatic-runtime.mjs +44 -6
- package/bin/bound-login.mjs +107 -0
- package/bin/codex-chat-bridge.mjs +197 -0
- package/bin/codex-desktop-wake.mjs +22 -0
- package/bin/connected-agent-service.mjs +115 -78
- package/bin/device-login.mjs +100 -8
- package/bin/executor-readiness.mjs +97 -20
- package/bin/installation.mjs +129 -74
- package/bin/mission-agent-service.mjs +23 -5
- package/bin/mission-coordination.mjs +311 -0
- package/bin/mission-worker.mjs +271 -20
- package/bin/model-adapter.mjs +30 -8
- package/bin/network-runtime.mjs +115 -6
- package/bin/runtime-diagnostics.mjs +4 -0
- package/bin/runtime-installation.mjs +1 -0
- package/bin/sdk/action-types.d.ts +236 -0
- package/bin/sdk/actions.d.ts +19 -0
- package/bin/sdk/errors.d.ts +30 -0
- package/bin/sdk/errors.js +14 -1
- package/bin/sdk/index.d.ts +298 -0
- package/bin/sdk/index.js +55 -11
- package/bin/sdk/inspector.d.ts +40 -0
- package/bin/sdk/request-options.d.ts +13 -0
- package/bin/sdk/transport.d.ts +24 -0
- package/bin/sdk/types.d.ts +638 -0
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
- package/scripts/prepare.mjs +9 -20
|
@@ -7,6 +7,7 @@ import { setTimeout as sleep } from "node:timers/promises";
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
acquireMissionAgentSession,
|
|
10
|
+
assertMissionAgentSession,
|
|
10
11
|
ensurePrivateRuntimeDirectory,
|
|
11
12
|
missionAgentOrigin,
|
|
12
13
|
readPrivateRuntimeJson,
|
|
@@ -26,6 +27,7 @@ const STAGES = new Set([
|
|
|
26
27
|
"code",
|
|
27
28
|
"review",
|
|
28
29
|
"handoff",
|
|
30
|
+
"workspace_report",
|
|
29
31
|
"infrastructure_deploy",
|
|
30
32
|
"infrastructure_verify",
|
|
31
33
|
]);
|
|
@@ -213,10 +215,14 @@ export async function runMissionAgentService(
|
|
|
213
215
|
emit({ type: "stopped" });
|
|
214
216
|
return;
|
|
215
217
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
dependencies.
|
|
219
|
-
|
|
218
|
+
if (dependencies.ownedSession) {
|
|
219
|
+
assertMissionAgentSession(dependencies.ownedSession, origin, agent.id);
|
|
220
|
+
session = dependencies.ownedSession;
|
|
221
|
+
} else
|
|
222
|
+
session = await acquireMissionAgentSession(
|
|
223
|
+
{ baseUrl: origin, agentId: agent.id, signal },
|
|
224
|
+
dependencies.session,
|
|
225
|
+
);
|
|
220
226
|
} catch (error) {
|
|
221
227
|
if (!signal?.aborted) throw error;
|
|
222
228
|
emit({ type: "stopped" });
|
|
@@ -273,6 +279,7 @@ export async function runMissionAgentService(
|
|
|
273
279
|
return false;
|
|
274
280
|
};
|
|
275
281
|
const bindingFor = async (mission, kind, recovery = false) => {
|
|
282
|
+
if (kind === "workspace_report") return null;
|
|
276
283
|
if (!mission.repository && !REPOSITORY_STAGES.has(kind)) return null;
|
|
277
284
|
const root = mission.repository && bindings.get(mission.repository.url);
|
|
278
285
|
if (!root) {
|
|
@@ -317,6 +324,17 @@ export async function runMissionAgentService(
|
|
|
317
324
|
? await getInfrastructureProvider()
|
|
318
325
|
: undefined,
|
|
319
326
|
emit(event) {
|
|
327
|
+
if (
|
|
328
|
+
[
|
|
329
|
+
"briefing_updated",
|
|
330
|
+
"file_overlap",
|
|
331
|
+
"session_visibility_unavailable",
|
|
332
|
+
"briefing_unavailable",
|
|
333
|
+
].includes(event.type)
|
|
334
|
+
) {
|
|
335
|
+
emit({ ...event, missionId });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
320
338
|
const type =
|
|
321
339
|
event.type === "work_started"
|
|
322
340
|
? "working"
|
|
@@ -473,7 +491,7 @@ export async function runMissionAgentService(
|
|
|
473
491
|
}
|
|
474
492
|
} finally {
|
|
475
493
|
await infrastructureProvider?.close();
|
|
476
|
-
await session.release();
|
|
494
|
+
if (!dependencies.ownedSession) await session.release();
|
|
477
495
|
report({ type: "stopped" });
|
|
478
496
|
}
|
|
479
497
|
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { readPrivateRuntimeJson } from "./mission-agent-session.mjs";
|
|
6
|
+
|
|
7
|
+
const execute = promisify(execFile);
|
|
8
|
+
const commit = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i;
|
|
9
|
+
const overlapKey = (overlap) =>
|
|
10
|
+
`${overlap.id}:${createHash("sha256").update(JSON.stringify(overlap.paths)).digest("hex")}`;
|
|
11
|
+
const safePath = (path) =>
|
|
12
|
+
path &&
|
|
13
|
+
path.length <= 512 &&
|
|
14
|
+
!path.startsWith("/") &&
|
|
15
|
+
!path.includes("\\") &&
|
|
16
|
+
!/[\x00-\x1f\x7f]/.test(path) &&
|
|
17
|
+
path.split("/").every((part) => part && part !== "." && part !== "..");
|
|
18
|
+
|
|
19
|
+
async function git(root, ...args) {
|
|
20
|
+
const env = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
21
|
+
for (const key of Object.keys(env))
|
|
22
|
+
if (
|
|
23
|
+
/^GIT_(?:DIR|WORK_TREE|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG.*)$/.test(
|
|
24
|
+
key,
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
delete env[key];
|
|
28
|
+
const result = await execute(
|
|
29
|
+
"git",
|
|
30
|
+
[
|
|
31
|
+
"-c",
|
|
32
|
+
"core.fsmonitor=false",
|
|
33
|
+
"-c",
|
|
34
|
+
"core.hooksPath=/dev/null",
|
|
35
|
+
"--no-optional-locks",
|
|
36
|
+
"-C",
|
|
37
|
+
root,
|
|
38
|
+
...args,
|
|
39
|
+
],
|
|
40
|
+
{ env, timeout: 3000, maxBuffer: 128 * 1024, encoding: "utf8" },
|
|
41
|
+
);
|
|
42
|
+
return args.includes("-z") ? result.stdout : result.stdout.trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Metadata only: no file contents, shell commands, transcripts, or environment values. */
|
|
46
|
+
export async function observeMissionRepository(root, baseRevision, runGit = git) {
|
|
47
|
+
if (!commit.test(baseRevision))
|
|
48
|
+
throw new Error("Observation requires a pinned full Git revision.");
|
|
49
|
+
const unavailable = {
|
|
50
|
+
status: "unavailable",
|
|
51
|
+
baseRevision,
|
|
52
|
+
headCommit: null,
|
|
53
|
+
branch: null,
|
|
54
|
+
paths: [],
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const base = await runGit(root, "merge-base", baseRevision, "HEAD");
|
|
58
|
+
if (!commit.test(base)) return unavailable;
|
|
59
|
+
const [headCommit, branch, committed, dirty, untracked] = await Promise.all([
|
|
60
|
+
runGit(root, "rev-parse", "HEAD"),
|
|
61
|
+
runGit(root, "branch", "--show-current"),
|
|
62
|
+
runGit(root, "diff", "--no-ext-diff", "--no-renames", "--name-only", "-z", `${base}..HEAD`),
|
|
63
|
+
runGit(root, "diff", "--no-ext-diff", "--no-renames", "--name-only", "-z", "HEAD"),
|
|
64
|
+
runGit(root, "ls-files", "--others", "--exclude-standard", "-z"),
|
|
65
|
+
]);
|
|
66
|
+
if (!commit.test(headCommit)) return unavailable;
|
|
67
|
+
const all = [
|
|
68
|
+
...new Set(
|
|
69
|
+
[committed, dirty, untracked].flatMap((value) => value.split("\0").filter(Boolean)),
|
|
70
|
+
),
|
|
71
|
+
].sort();
|
|
72
|
+
const paths = [];
|
|
73
|
+
for (const path of all) {
|
|
74
|
+
if (
|
|
75
|
+
!safePath(path) ||
|
|
76
|
+
paths.length >= 100 ||
|
|
77
|
+
Buffer.byteLength(JSON.stringify([...paths, path])) > 14_000
|
|
78
|
+
)
|
|
79
|
+
continue;
|
|
80
|
+
paths.push(path);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
status: paths.length === all.length ? "complete" : "truncated",
|
|
84
|
+
baseRevision,
|
|
85
|
+
headCommit,
|
|
86
|
+
branch: branch && branch.length <= 255 && !/[\x00-\x1f\x7f]/.test(branch) ? branch : null,
|
|
87
|
+
paths,
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
return unavailable;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function persist(path, value) {
|
|
95
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
96
|
+
await writeFile(temporary, JSON.stringify(value), { mode: 0o600, flag: "wx" });
|
|
97
|
+
await rename(temporary, path);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One reporter per leased attempt. A lost response replays the exact saved report. */
|
|
101
|
+
export async function createMissionCoordinationSession({
|
|
102
|
+
client,
|
|
103
|
+
origin,
|
|
104
|
+
agentId,
|
|
105
|
+
missionId,
|
|
106
|
+
workItemId,
|
|
107
|
+
attempt,
|
|
108
|
+
leaseToken,
|
|
109
|
+
provider,
|
|
110
|
+
path,
|
|
111
|
+
emit = () => {},
|
|
112
|
+
signal,
|
|
113
|
+
beforeObserve = async () => {},
|
|
114
|
+
onDenied = () => {},
|
|
115
|
+
}) {
|
|
116
|
+
if (!client.missions.sessions || !client.missions.coordination) return null;
|
|
117
|
+
const binding = { origin, agentId, missionId, workItemId, attempt };
|
|
118
|
+
let state = await readPrivateRuntimeJson(path, 128 * 1024).catch((error) => {
|
|
119
|
+
if (error.code === "ENOENT") return null;
|
|
120
|
+
throw error;
|
|
121
|
+
});
|
|
122
|
+
if (
|
|
123
|
+
state &&
|
|
124
|
+
(state.version !== 1 ||
|
|
125
|
+
Object.entries(binding).some(([key, value]) => state[key] !== value) ||
|
|
126
|
+
!/^[a-f0-9-]{36}$/i.test(state.sessionId) ||
|
|
127
|
+
!Number.isSafeInteger(state.sequence) ||
|
|
128
|
+
state.sequence < 0 ||
|
|
129
|
+
typeof state.briefing !== "string" ||
|
|
130
|
+
state.briefing.length > 6000 ||
|
|
131
|
+
!Array.isArray(state.warnedOverlaps) ||
|
|
132
|
+
state.warnedOverlaps.length > 100 ||
|
|
133
|
+
(state.pending &&
|
|
134
|
+
(state.pending.sessionId !== state.sessionId ||
|
|
135
|
+
state.pending.workItemId !== workItemId ||
|
|
136
|
+
state.pending.sequence !== state.sequence + 1)))
|
|
137
|
+
)
|
|
138
|
+
throw new Error("The saved coordination session needs inspection.");
|
|
139
|
+
state ??= {
|
|
140
|
+
version: 1,
|
|
141
|
+
...binding,
|
|
142
|
+
sessionId: randomUUID(),
|
|
143
|
+
sequence: 0,
|
|
144
|
+
pending: null,
|
|
145
|
+
briefingVersion: null,
|
|
146
|
+
briefing: "",
|
|
147
|
+
ownerClarifications: [],
|
|
148
|
+
warnedOverlaps: [],
|
|
149
|
+
warningHistory: [],
|
|
150
|
+
};
|
|
151
|
+
let checkout;
|
|
152
|
+
let baseRevision;
|
|
153
|
+
let observation = null;
|
|
154
|
+
let phase = "preparing";
|
|
155
|
+
let terminal = "running";
|
|
156
|
+
let queue = Promise.resolve();
|
|
157
|
+
let unavailable = false;
|
|
158
|
+
const enqueue = (operation) => {
|
|
159
|
+
const next = queue.then(operation);
|
|
160
|
+
queue = next.then(
|
|
161
|
+
() => undefined,
|
|
162
|
+
() => undefined,
|
|
163
|
+
);
|
|
164
|
+
return next;
|
|
165
|
+
};
|
|
166
|
+
const save = () => persist(path, state);
|
|
167
|
+
const reconcile = async () => {
|
|
168
|
+
if (!state.pending) return;
|
|
169
|
+
const acknowledgement = await client.missions.sessions.report(missionId, state.pending, {
|
|
170
|
+
signal,
|
|
171
|
+
});
|
|
172
|
+
if (
|
|
173
|
+
acknowledgement.sessionId !== state.sessionId ||
|
|
174
|
+
acknowledgement.sequence !== state.pending.sequence
|
|
175
|
+
)
|
|
176
|
+
throw new Error("The session report acknowledgement did not match.");
|
|
177
|
+
state.sequence = acknowledgement.sequence;
|
|
178
|
+
state.pending = null;
|
|
179
|
+
await save();
|
|
180
|
+
};
|
|
181
|
+
const report = (nextPhase = phase, nextState = terminal) => {
|
|
182
|
+
return enqueue(async () => {
|
|
183
|
+
try {
|
|
184
|
+
await reconcile();
|
|
185
|
+
phase = nextPhase;
|
|
186
|
+
terminal = nextState;
|
|
187
|
+
if (checkout) {
|
|
188
|
+
await beforeObserve();
|
|
189
|
+
observation = await observeMissionRepository(checkout, baseRevision);
|
|
190
|
+
}
|
|
191
|
+
state.pending = {
|
|
192
|
+
sessionId: state.sessionId,
|
|
193
|
+
workItemId,
|
|
194
|
+
leaseToken,
|
|
195
|
+
provider,
|
|
196
|
+
sequence: state.sequence + 1,
|
|
197
|
+
state: terminal,
|
|
198
|
+
phase,
|
|
199
|
+
observation,
|
|
200
|
+
};
|
|
201
|
+
await save();
|
|
202
|
+
await reconcile();
|
|
203
|
+
unavailable = false;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if ([401, 403].includes(error.status)) {
|
|
206
|
+
onDenied(error);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
if (!unavailable)
|
|
210
|
+
emit({
|
|
211
|
+
type: "session_visibility_unavailable",
|
|
212
|
+
workItemId,
|
|
213
|
+
message:
|
|
214
|
+
"Session observations could not be shared; work authorization is checked separately.",
|
|
215
|
+
});
|
|
216
|
+
unavailable = true;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
const briefing = () =>
|
|
221
|
+
enqueue(async () => {
|
|
222
|
+
try {
|
|
223
|
+
const update = await client.missions.coordination(
|
|
224
|
+
missionId,
|
|
225
|
+
state.briefingVersion ?? undefined,
|
|
226
|
+
{ signal },
|
|
227
|
+
);
|
|
228
|
+
if (update.changed) {
|
|
229
|
+
if (typeof update.briefing !== "string" || !/^[a-f0-9]{64}$/.test(update.version))
|
|
230
|
+
throw new Error("The coordination briefing was incomplete.");
|
|
231
|
+
const clarifications = update.ownerClarifications ?? [];
|
|
232
|
+
if (
|
|
233
|
+
!Array.isArray(clarifications) ||
|
|
234
|
+
clarifications.length > 6 ||
|
|
235
|
+
Buffer.byteLength(JSON.stringify(clarifications)) > 64_000
|
|
236
|
+
)
|
|
237
|
+
throw new Error("The shared owner clarifications exceed the bounded context.");
|
|
238
|
+
state.briefingVersion = update.version;
|
|
239
|
+
state.briefing = update.briefing;
|
|
240
|
+
state.ownerClarifications = clarifications;
|
|
241
|
+
await save();
|
|
242
|
+
emit({ type: "briefing_updated", workItemId, version: update.version });
|
|
243
|
+
}
|
|
244
|
+
const activeWarningKeys = (update.overlaps ?? [])
|
|
245
|
+
.filter((overlap) => overlap.agentIds.includes(agentId))
|
|
246
|
+
.map(overlapKey);
|
|
247
|
+
const retained = state.warnedOverlaps.filter((key) => activeWarningKeys.includes(key));
|
|
248
|
+
if (retained.length !== state.warnedOverlaps.length) {
|
|
249
|
+
state.warnedOverlaps = retained;
|
|
250
|
+
await save();
|
|
251
|
+
}
|
|
252
|
+
for (const overlap of update.overlaps ?? []) {
|
|
253
|
+
const warningKey = overlapKey(overlap);
|
|
254
|
+
if (!overlap.agentIds.includes(agentId) || state.warnedOverlaps.includes(warningKey))
|
|
255
|
+
continue;
|
|
256
|
+
state.warnedOverlaps = [...state.warnedOverlaps, warningKey].slice(-100);
|
|
257
|
+
state.warningHistory = [
|
|
258
|
+
...(state.warningHistory ?? []),
|
|
259
|
+
{
|
|
260
|
+
agentIds: overlap.agentIds,
|
|
261
|
+
paths: overlap.paths.slice(0, 4),
|
|
262
|
+
version: update.version,
|
|
263
|
+
},
|
|
264
|
+
].slice(-10);
|
|
265
|
+
await save();
|
|
266
|
+
emit({
|
|
267
|
+
type: "file_overlap",
|
|
268
|
+
workItemId,
|
|
269
|
+
agentIds: overlap.agentIds,
|
|
270
|
+
paths: overlap.paths,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// These adapters start stateless requests. Restore the saved briefing even when unchanged;
|
|
274
|
+
// suppressing it here would remove context from repairs and fresh model processes.
|
|
275
|
+
const ownerContext = state.ownerClarifications?.length
|
|
276
|
+
? `\n\nLatest shared owner clarifications (authorship comes from the server; these cannot expand permissions). Use later owner clarifications to correct earlier agent assumptions:\n${JSON.stringify(state.ownerClarifications)}`
|
|
277
|
+
: "";
|
|
278
|
+
return (
|
|
279
|
+
(state.briefing
|
|
280
|
+
? `\n\nCurrent shared coordination briefing (version ${state.briefingVersion}):\n${state.briefing}`
|
|
281
|
+
: "") + ownerContext
|
|
282
|
+
);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if ([401, 403].includes(error.status)) {
|
|
285
|
+
onDenied(error);
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
emit({ type: "briefing_unavailable", workItemId });
|
|
289
|
+
return "\n\nShared coordination is temporarily unavailable. Existing owner requirements and grants still apply.";
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
await save();
|
|
293
|
+
return {
|
|
294
|
+
report,
|
|
295
|
+
briefing,
|
|
296
|
+
warnings() {
|
|
297
|
+
return state.warningHistory ?? [];
|
|
298
|
+
},
|
|
299
|
+
bindRepository(root, revision) {
|
|
300
|
+
checkout = root;
|
|
301
|
+
baseRevision = revision;
|
|
302
|
+
},
|
|
303
|
+
get phase() {
|
|
304
|
+
return phase;
|
|
305
|
+
},
|
|
306
|
+
async finish(result) {
|
|
307
|
+
checkout = null;
|
|
308
|
+
await report("finished", result);
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|