totemlayer 0.1.21 → 0.1.25
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 +34 -32
- package/bin/agent-runner.mjs +27 -2
- package/bin/agent-setup.mjs +33 -6
- package/bin/automatic-runtime.mjs +105 -9
- package/bin/bound-login.mjs +107 -0
- package/bin/connected-agent-service.mjs +119 -78
- package/bin/device-login.mjs +108 -9
- package/bin/installation.mjs +175 -76
- 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 -15
- 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 +300 -0
- package/bin/sdk/index.js +57 -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 +640 -0
- package/bin/totem.mjs +2 -2
- package/package.json +1 -1
- package/scripts/prepare.mjs +9 -20
package/bin/installation.mjs
CHANGED
|
@@ -5,6 +5,11 @@ import { join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { ensureInstalledRuntime, installRuntimeService } from "./runtime-installation.mjs";
|
|
7
7
|
import { beginDeviceLogin } from "./device-login.mjs";
|
|
8
|
+
import { resumeDeviceLogin } from "./device-login.mjs";
|
|
9
|
+
import { beginBoundLogin } from "./bound-login.mjs";
|
|
10
|
+
import { readAgentProfile } from "./agent-profile.mjs";
|
|
11
|
+
import { readAgentRuntimeStatus } from "./agent-runner.mjs";
|
|
12
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
13
|
import { detectInstalledProvider, providerArguments } from "./provider-detection.mjs";
|
|
9
14
|
|
|
10
15
|
export async function activateConnection(profile, env = process.env, options = {}) {
|
|
@@ -19,7 +24,7 @@ export async function activateConnection(profile, env = process.env, options = {
|
|
|
19
24
|
if (!enabled) return { state: "paused", ...service, entry: runtime.cli };
|
|
20
25
|
const { verifyExecutorReady } = await import("./executor-readiness.mjs");
|
|
21
26
|
try {
|
|
22
|
-
await (options.verifyExecutor ?? verifyExecutorReady)(profile, env);
|
|
27
|
+
if (profile.codexThreadId) await (options.verifyExecutor ?? verifyExecutorReady)(profile, env);
|
|
23
28
|
} catch (error) {
|
|
24
29
|
return {
|
|
25
30
|
state: "needs_attention",
|
|
@@ -31,42 +36,18 @@ export async function activateConnection(profile, env = process.env, options = {
|
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
38
|
const { agentStartMain } = await import("./agent-runner.mjs");
|
|
34
|
-
const
|
|
39
|
+
const result = await (options.start ?? agentStartMain)(
|
|
35
40
|
["--profile", profile.profileName, "--background", "--json"],
|
|
36
41
|
{ env, automatic: true, stdout: { write() {} } },
|
|
37
42
|
);
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const state = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
|
|
47
|
-
if (
|
|
48
|
-
state.running &&
|
|
49
|
-
state.phase === "blocked" &&
|
|
50
|
-
["INSPECTION_BLOCKED", "INSPECTION_LIMIT", "INSPECTION_UNSUPPORTED_FILE"].includes(state.blockerCode)
|
|
51
|
-
)
|
|
52
|
-
return {
|
|
53
|
-
state: "running",
|
|
54
|
-
...service,
|
|
55
|
-
entry: runtime.cli,
|
|
56
|
-
phase: state.phase,
|
|
57
|
-
quarantinedMessage: true,
|
|
58
|
-
code: state.blockerCode,
|
|
59
|
-
message: "The listener is running. An unsafe message was quarantined; other messages can still be processed.",
|
|
60
|
-
};
|
|
61
|
-
if (!state.running || !["connected", "working", "waiting"].includes(state.phase))
|
|
62
|
-
return {
|
|
63
|
-
state: "needs_attention",
|
|
64
|
-
...service,
|
|
65
|
-
entry: runtime.cli,
|
|
66
|
-
code: state.blockerCode ?? "WORKER_NOT_READY",
|
|
67
|
-
phase: state.phase,
|
|
68
|
-
};
|
|
69
|
-
return { state: "running", ...service, entry: runtime.cli };
|
|
43
|
+
if (result) throw new Error("The listener could not start. Check totem status for this profile.");
|
|
44
|
+
const status = await (options.readStatus ?? readAgentRuntimeStatus)(profile);
|
|
45
|
+
return {
|
|
46
|
+
state: status.ready ? "ready" : "needs_attention",
|
|
47
|
+
ready: Boolean(status.ready),
|
|
48
|
+
...service,
|
|
49
|
+
entry: runtime.cli,
|
|
50
|
+
};
|
|
70
51
|
}
|
|
71
52
|
|
|
72
53
|
export async function installationMain(args = [], options = {}) {
|
|
@@ -78,15 +59,19 @@ export async function installationMain(args = [], options = {}) {
|
|
|
78
59
|
options: {
|
|
79
60
|
provider: { type: "string" },
|
|
80
61
|
"agent-id": { type: "string" },
|
|
62
|
+
"connection-id": { type: "string" },
|
|
81
63
|
"base-url": { type: "string" },
|
|
82
64
|
"no-browser": { type: "boolean" },
|
|
65
|
+
"device-login": { type: "boolean" },
|
|
66
|
+
reauthorize: { type: "boolean" },
|
|
67
|
+
"host-kind": { type: "string" },
|
|
83
68
|
help: { type: "boolean", short: "h" },
|
|
84
69
|
json: { type: "boolean" },
|
|
85
70
|
},
|
|
86
71
|
}).values;
|
|
87
72
|
if (flags.help) {
|
|
88
73
|
stdout.write(
|
|
89
|
-
"totem
|
|
74
|
+
"totem install [--agent-id UUID] [--connection-id UUID] [--host-kind local|hosted] [--provider codex|claude|qwen-local|ollama-local|nim-local|command|http] [--no-browser] [--device-login]\nWith --connection-id, prepare the host and use one informed Accept action for peer approval and listener startup. The browser must reach this host's loopback callback (remote hosts require a tunnel). Standalone login uses explicit owner authorization. Headless --device-login uses code comparison and cannot replace the connection-bound Accept flow. Hosted labels an already provisioned, authorized host; it does not provision hosting.\n",
|
|
90
75
|
);
|
|
91
76
|
return 0;
|
|
92
77
|
}
|
|
@@ -94,8 +79,32 @@ export async function installationMain(args = [], options = {}) {
|
|
|
94
79
|
...env,
|
|
95
80
|
...(flags.provider ? { TOTEM_PROVIDER: flags.provider } : {}),
|
|
96
81
|
...(flags["agent-id"] ? { TOTEM_AGENT_ID: flags["agent-id"] } : {}),
|
|
82
|
+
...(flags["connection-id"] ? { TOTEM_CONNECTION_ID: flags["connection-id"] } : {}),
|
|
97
83
|
...(flags["base-url"] ? { TOTEM_BASE_URL: flags["base-url"] } : {}),
|
|
84
|
+
...(flags["host-kind"] ? { TOTEM_RUNTIME_HOST_KIND: flags["host-kind"] } : {}),
|
|
98
85
|
};
|
|
86
|
+
if (
|
|
87
|
+
environment.TOTEM_RUNTIME_HOST_KIND &&
|
|
88
|
+
!["local", "hosted"].includes(environment.TOTEM_RUNTIME_HOST_KIND)
|
|
89
|
+
)
|
|
90
|
+
throw new Error(
|
|
91
|
+
"Choose --host-kind local or hosted. Hosted mode labels an already provisioned, explicitly authorized host; it does not provision one.",
|
|
92
|
+
);
|
|
93
|
+
if (
|
|
94
|
+
environment.TOTEM_CONNECTION_ID &&
|
|
95
|
+
!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(environment.TOTEM_CONNECTION_ID)
|
|
96
|
+
)
|
|
97
|
+
throw new Error("Choose a valid connection request ID.");
|
|
98
|
+
if (
|
|
99
|
+
environment.TOTEM_CONNECTION_ID &&
|
|
100
|
+
(flags["device-login"] ||
|
|
101
|
+
environment.TOTEM_NETWORK_API_KEY ||
|
|
102
|
+
environment.TOTEM_NETWORK_API_KEY_FILE ||
|
|
103
|
+
flags.reauthorize)
|
|
104
|
+
)
|
|
105
|
+
throw new Error(
|
|
106
|
+
"Prepare connection acceptance with the bound browser installer on this host. Headless and credential replacement use their explicit repair flows.",
|
|
107
|
+
);
|
|
99
108
|
const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(environment);
|
|
100
109
|
const service = await (options.installService ?? installRuntimeService)(runtime, environment);
|
|
101
110
|
if (environment.TOTEM_NETWORK_API_KEY || environment.TOTEM_NETWORK_API_KEY_FILE) {
|
|
@@ -129,6 +138,7 @@ export async function installationMain(args = [], options = {}) {
|
|
|
129
138
|
[
|
|
130
139
|
"--profile",
|
|
131
140
|
environment.TOTEM_AGENT_ID ?? "default",
|
|
141
|
+
...(flags.reauthorize ? ["--replace"] : []),
|
|
132
142
|
...(environment.TOTEM_AGENT_ID ? ["--agent-id", environment.TOTEM_AGENT_ID] : []),
|
|
133
143
|
...(await providerArguments(provider, environment)),
|
|
134
144
|
...(flags.json ? ["--json"] : []),
|
|
@@ -140,61 +150,94 @@ export async function installationMain(args = [], options = {}) {
|
|
|
140
150
|
},
|
|
141
151
|
);
|
|
142
152
|
}
|
|
143
|
-
|
|
144
|
-
await
|
|
153
|
+
const activate = async (name) => {
|
|
154
|
+
const profile = await (options.readProfile ?? readAgentProfile)(name, { environment });
|
|
155
|
+
return (options.activateConnection ?? activateConnection)(profile, environment, options);
|
|
156
|
+
};
|
|
157
|
+
const reportActivation = (status) => {
|
|
145
158
|
stdout.write(
|
|
146
|
-
|
|
159
|
+
flags.json
|
|
160
|
+
? JSON.stringify(status) + "\n"
|
|
161
|
+
: status.ready
|
|
162
|
+
? "Ready. The listener passed its controlled receive, wake and reply check. This host must remain awake and online.\n"
|
|
163
|
+
: `Runtime ${status.state}. Run totem status for diagnostics; an installed or paused listener is not ready.\n`,
|
|
147
164
|
);
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
165
|
+
return status.ready ? 0 : 1;
|
|
166
|
+
};
|
|
167
|
+
if (environment.TOTEM_CONNECTION_ID) {
|
|
168
|
+
for (const file of ["connection-ready.json", "connection-request.json"]) {
|
|
169
|
+
const pending = await privateRead(join(runtime.directory, file)).catch((error) => {
|
|
170
|
+
if (error.code !== "ENOENT") throw error;
|
|
171
|
+
});
|
|
172
|
+
if (
|
|
173
|
+
pending &&
|
|
174
|
+
pending.connectionId !== environment.TOTEM_CONNECTION_ID &&
|
|
175
|
+
(file === "connection-ready.json" || Date.parse(pending.expiresAt) > Date.now())
|
|
176
|
+
)
|
|
177
|
+
throw new Error(
|
|
178
|
+
"Another host authorization is pending. Finish or explicitly cancel that setup before preparing this connection; no saved identity was replaced.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const resume = options.resumeLogin ?? resumeDeviceLogin;
|
|
183
|
+
const loginOptions = { ...options, replace: Boolean(flags.reauthorize) };
|
|
184
|
+
if (flags.reauthorize) {
|
|
185
|
+
const reset = await resume(runtime, environment, { ...loginOptions, reset: true });
|
|
186
|
+
if (reset?.state !== "reset") {
|
|
187
|
+
stdout.write(
|
|
188
|
+
"Another installer is saving this connection. Wait for it to finish before reauthorizing.\n",
|
|
189
|
+
);
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const staged = flags.reauthorize
|
|
194
|
+
? undefined
|
|
195
|
+
: await resume(runtime, environment, loginOptions).catch((error) => {
|
|
196
|
+
if (error.message?.includes("authorization expired")) return undefined;
|
|
197
|
+
throw error;
|
|
198
|
+
});
|
|
199
|
+
if (staged?.state === "connected") return reportActivation(await activate(staged.profile));
|
|
200
|
+
if (staged?.state === "executor_needed") {
|
|
201
|
+
stdout.write(
|
|
202
|
+
"Authorization is saved privately. Sign in to an installed Codex or Claude client, or configure a supported local/custom adapter, then retry totem login. The listener is not ready.\n",
|
|
203
|
+
);
|
|
204
|
+
return 1;
|
|
151
205
|
}
|
|
152
206
|
// An upgrade reuses private profiles and starts their existing connections.
|
|
153
207
|
const { opendir } = await import("node:fs/promises");
|
|
154
|
-
|
|
155
|
-
const savedProfiles = [];
|
|
208
|
+
const saved = [];
|
|
156
209
|
try {
|
|
157
210
|
const profiles = await opendir(join(runtime.root, "connections"));
|
|
158
211
|
for await (const entry of profiles)
|
|
159
212
|
if (
|
|
160
213
|
entry.isDirectory() &&
|
|
161
214
|
(!environment.TOTEM_AGENT_ID || entry.name === environment.TOTEM_AGENT_ID)
|
|
162
|
-
)
|
|
163
|
-
saved
|
|
164
|
-
savedProfiles.push(entry.name);
|
|
165
|
-
}
|
|
215
|
+
)
|
|
216
|
+
saved.push(entry.name);
|
|
166
217
|
} catch (error) {
|
|
167
218
|
if (error.code !== "ENOENT") throw error;
|
|
168
219
|
}
|
|
169
|
-
if (
|
|
170
|
-
|
|
220
|
+
if (
|
|
221
|
+
saved.length &&
|
|
222
|
+
options.forceLogin !== true &&
|
|
223
|
+
!flags.reauthorize &&
|
|
224
|
+
!environment.TOTEM_CONNECTION_ID
|
|
225
|
+
) {
|
|
171
226
|
const results = [];
|
|
172
|
-
for (const name of
|
|
173
|
-
|
|
174
|
-
results.push({
|
|
175
|
-
profile: name,
|
|
176
|
-
...(await (options.activateConnection ?? activateConnection)(profile, environment)),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
const ready = results.every((result) => ["running", "paused"].includes(result.state));
|
|
180
|
-
const report = {
|
|
227
|
+
for (const name of saved) results.push({ profile: name, ...(await activate(name)) });
|
|
228
|
+
return reportActivation({
|
|
181
229
|
installed: true,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
ready,
|
|
230
|
+
ready: results.every((r) => r.ready),
|
|
231
|
+
state: results.every((r) => r.ready) ? "ready" : "needs_attention",
|
|
185
232
|
profiles: results,
|
|
186
233
|
service,
|
|
187
|
-
};
|
|
188
|
-
stdout.write(
|
|
189
|
-
flags.json
|
|
190
|
-
? JSON.stringify(report) + "\n"
|
|
191
|
-
: ready
|
|
192
|
-
? "Totem's automatic listener reuses your saved connections; startup checks passed.\n"
|
|
193
|
-
: "Totem is installed, but an agent failed its startup check. Run totem status to inspect it.\n",
|
|
194
|
-
);
|
|
195
|
-
return ready ? 0 : 1;
|
|
234
|
+
});
|
|
196
235
|
}
|
|
197
|
-
const connection = await (
|
|
236
|
+
const connection = await (
|
|
237
|
+
options.beginLogin ?? (flags["device-login"] ? beginDeviceLogin : beginBoundLogin)
|
|
238
|
+
)(runtime, environment, {
|
|
239
|
+
...options,
|
|
240
|
+
replace: Boolean(flags.reauthorize),
|
|
198
241
|
openBrowser: !flags["no-browser"] && environment.TOTEM_NO_BROWSER !== "1",
|
|
199
242
|
});
|
|
200
243
|
const report = {
|
|
@@ -202,19 +245,75 @@ export async function installationMain(args = [], options = {}) {
|
|
|
202
245
|
connection: "sign_in_required",
|
|
203
246
|
automatic: true,
|
|
204
247
|
service,
|
|
205
|
-
|
|
248
|
+
url: connection.url,
|
|
249
|
+
...(flags["device-login"] ? { code: connection.code } : {}),
|
|
250
|
+
ready: false,
|
|
206
251
|
};
|
|
207
252
|
stdout.write(
|
|
208
253
|
flags.json
|
|
209
254
|
? JSON.stringify(report) + "\n"
|
|
210
|
-
:
|
|
255
|
+
: `${environment.TOTEM_CONNECTION_ID ? "Accept the peer connection and connect this host in one action:" : "Authorize your agent in the installer browser:"}\n${connection.url}\n${flags["device-login"] ? `Compare this code before approving: ${connection.code.match(/.{1,4}/g).join(" ")}\n` : ""}Waiting for authorization and the controlled listener check.\n`,
|
|
211
256
|
);
|
|
212
|
-
|
|
257
|
+
try {
|
|
258
|
+
const deadline = Math.min(
|
|
259
|
+
Date.parse(connection.expiresAt),
|
|
260
|
+
Date.now() + (options.loginTimeoutMs ?? 600000),
|
|
261
|
+
);
|
|
262
|
+
while (Date.now() < deadline && !options.signal?.aborted) {
|
|
263
|
+
const result = await resume(runtime, environment, loginOptions);
|
|
264
|
+
if (result?.state === "connected") return reportActivation(await activate(result.profile));
|
|
265
|
+
const receipt = await privateRead(join(runtime.directory, "connection-last.json")).catch(
|
|
266
|
+
(error) => {
|
|
267
|
+
if (error.code !== "ENOENT") throw error;
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
if (receipt?.requestId === connection.id && receipt.profile)
|
|
271
|
+
return reportActivation(await activate(receipt.profile));
|
|
272
|
+
if (result?.state === "executor_needed") {
|
|
273
|
+
stdout.write(
|
|
274
|
+
"Authorization saved. Sign in to an installed AI client or configure a supported adapter, then run totem login again. Runtime is not ready.\n",
|
|
275
|
+
);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
// The supervisor may have saved the profile while this installer was polling.
|
|
279
|
+
const pending = await privateRead(join(runtime.directory, "connection-request.json")).catch(
|
|
280
|
+
(error) => {
|
|
281
|
+
if (error.code !== "ENOENT") throw error;
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
const name = environment.TOTEM_AGENT_ID ?? pending?.requestedAgentId;
|
|
285
|
+
if (name && !environment.TOTEM_CONNECTION_ID) {
|
|
286
|
+
const profile = await (options.readProfile ?? readAgentProfile)(name, {
|
|
287
|
+
environment,
|
|
288
|
+
}).catch((error) => {
|
|
289
|
+
if (error.code !== "AGENT_PROFILE_NOT_FOUND") throw error;
|
|
290
|
+
});
|
|
291
|
+
if (profile)
|
|
292
|
+
return reportActivation(
|
|
293
|
+
await (options.activateConnection ?? activateConnection)(profile, environment, options),
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
await (options.sleep ?? sleep)(3000, undefined, { signal: options.signal });
|
|
297
|
+
}
|
|
298
|
+
stdout.write(
|
|
299
|
+
"Setup is not ready yet. Run totem login again to resume securely; inspect totem status for any saved profile.\n",
|
|
300
|
+
);
|
|
301
|
+
return 1;
|
|
302
|
+
} finally {
|
|
303
|
+
connection.close?.();
|
|
304
|
+
}
|
|
213
305
|
}
|
|
214
|
-
export async function postinstallMain(env = process.env) {
|
|
306
|
+
export async function postinstallMain(env = process.env, options = {}) {
|
|
215
307
|
if (env.TOTEM_AUTO_START === "0" || env.CI) return 0;
|
|
216
308
|
try {
|
|
217
|
-
|
|
309
|
+
// npm may hide lifecycle output. Prepare reception without opening a separate
|
|
310
|
+
// credential-consent page before the user reaches informed peer acceptance.
|
|
311
|
+
const runtime = await (options.ensureRuntime ?? ensureInstalledRuntime)(env);
|
|
312
|
+
await (options.installService ?? installRuntimeService)(runtime, env);
|
|
313
|
+
(options.stdout ?? process.stdout).write(
|
|
314
|
+
"Totem runtime prepared. Run totem install, or prepare the host from a peer request and use its single Accept action. No new runtime credential has been granted.\n",
|
|
315
|
+
);
|
|
316
|
+
return 0;
|
|
218
317
|
} catch {
|
|
219
318
|
process.stdout.write(
|
|
220
319
|
"Totem is installed. Automatic connection setup needs attention; run totem login to finish it.\n",
|
|
@@ -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
|
}
|