@parlor/agent-host 0.2.0 → 0.3.1
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/dist/acp.d.ts +21 -0
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +138 -12
- package/dist/acp.js.map +1 -1
- package/dist/agent-options.d.ts +22 -0
- package/dist/agent-options.d.ts.map +1 -0
- package/dist/agent-options.js +105 -0
- package/dist/agent-options.js.map +1 -0
- package/dist/agent-survey.d.ts +15 -0
- package/dist/agent-survey.d.ts.map +1 -0
- package/dist/agent-survey.js +51 -0
- package/dist/agent-survey.js.map +1 -0
- package/dist/agent-tools.d.ts +30 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/agent-tools.js +71 -0
- package/dist/agent-tools.js.map +1 -0
- package/dist/codex-app-server.d.ts +7 -0
- package/dist/codex-app-server.d.ts.map +1 -0
- package/dist/codex-app-server.js +46 -0
- package/dist/codex-app-server.js.map +1 -0
- package/dist/control.d.ts +15 -0
- package/dist/control.d.ts.map +1 -1
- package/dist/control.js.map +1 -1
- package/dist/daemon.d.ts +10 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +217 -16
- package/dist/daemon.js.map +1 -1
- package/dist/fold.d.ts.map +1 -1
- package/dist/fold.js +2 -0
- package/dist/fold.js.map +1 -1
- package/dist/grants.d.ts +9 -0
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js.map +1 -1
- package/dist/learned.d.ts +25 -0
- package/dist/learned.d.ts.map +1 -0
- package/dist/learned.js +37 -0
- package/dist/learned.js.map +1 -0
- package/dist/link.d.ts +3 -1
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +23 -1
- package/dist/link.js.map +1 -1
- package/dist/main.js +105 -9
- package/dist/main.js.map +1 -1
- package/dist/mcp-proxy.d.ts +26 -3
- package/dist/mcp-proxy.d.ts.map +1 -1
- package/dist/mcp-proxy.js +288 -25
- package/dist/mcp-proxy.js.map +1 -1
- package/dist/pairing.d.ts +2 -0
- package/dist/pairing.d.ts.map +1 -1
- package/dist/pairing.js +25 -3
- package/dist/pairing.js.map +1 -1
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +1 -0
- package/dist/paths.js.map +1 -1
- package/dist/policy.d.ts +12 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +13 -3
- package/dist/policy.js.map +1 -1
- package/dist/prompter.d.ts +5 -0
- package/dist/prompter.d.ts.map +1 -1
- package/dist/prompter.js +15 -0
- package/dist/prompter.js.map +1 -1
- package/dist/spec-kit.d.ts +4 -1
- package/dist/spec-kit.d.ts.map +1 -1
- package/dist/spec-kit.js +12 -2
- package/dist/spec-kit.js.map +1 -1
- package/dist/testing/fake-agent.d.ts +12 -0
- package/dist/testing/fake-agent.d.ts.map +1 -1
- package/dist/testing/fake-agent.js +11 -1
- package/dist/testing/fake-agent.js.map +1 -1
- package/dist/turn.d.ts +7 -1
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +16 -3
- package/dist/turn.js.map +1 -1
- package/package.json +9 -9
package/dist/daemon.js
CHANGED
|
@@ -3,10 +3,13 @@ import { createServer } from "node:net";
|
|
|
3
3
|
import { basename, delimiter, dirname, join } from "node:path";
|
|
4
4
|
import { HOST_PROTOCOL, } from "@parlor/chat";
|
|
5
5
|
import { ActionError, asId, DEV_USER_ID } from "@parlor/core";
|
|
6
|
+
import { readUsage, surveyOptions } from "./agent-survey.js";
|
|
7
|
+
import { agentNames, registerAgentTools, unregisterAgentTools, } from "./agent-tools.js";
|
|
6
8
|
import { detectAgents, loadProfiles } from "./agents.js";
|
|
7
9
|
import { connectControl } from "./control.js";
|
|
8
10
|
import { requestDevHostToken } from "./dev-host.js";
|
|
9
11
|
import { devHostKeys } from "./grants.js";
|
|
12
|
+
import { learnedStore } from "./learned.js";
|
|
10
13
|
import { onLines } from "./lines.js";
|
|
11
14
|
import { createAppLink } from "./link.js";
|
|
12
15
|
import { withInheritedLock } from "./lock.js";
|
|
@@ -19,8 +22,11 @@ import { compareVersions, RELEASE_KEY, signedRelease } from "./release.js";
|
|
|
19
22
|
import { revokeGrant } from "./revoke.js";
|
|
20
23
|
import { selfEntry } from "./self.js";
|
|
21
24
|
import { installLoginItem, installRelease } from "./service.js";
|
|
25
|
+
import { agentEnvironment } from "./shell-path.js";
|
|
22
26
|
import { runTurn } from "./turn.js";
|
|
23
27
|
const CONNECT_TIMEOUT_MS = 15_000;
|
|
28
|
+
/** An agent token is asked for again this long before it runs out. */
|
|
29
|
+
const AGENT_TOKEN_MARGIN_MS = 60_000;
|
|
24
30
|
const OFFLINE_REPORT_MS = 1_000;
|
|
25
31
|
function incompatibleMessage(grant, welcome) {
|
|
26
32
|
return welcome.reason === "host_too_old"
|
|
@@ -58,6 +64,8 @@ export class HostAlreadyRunning extends Error {
|
|
|
58
64
|
super("Parlor Host is already running on this computer.");
|
|
59
65
|
}
|
|
60
66
|
}
|
|
67
|
+
/** Codex's own CLI throttles its rate-limit reports on the same interval. */
|
|
68
|
+
const USAGE_READ_FLOOR_MS = 30_000;
|
|
61
69
|
export function createHostDaemon(deps) {
|
|
62
70
|
const { paths } = deps;
|
|
63
71
|
const links = new Map();
|
|
@@ -66,6 +74,7 @@ export function createHostDaemon(deps) {
|
|
|
66
74
|
const platform = platformName();
|
|
67
75
|
let profiles = [];
|
|
68
76
|
let agents = [];
|
|
77
|
+
const learned = learnedStore(paths.learned);
|
|
69
78
|
let control;
|
|
70
79
|
let startup = Promise.resolve();
|
|
71
80
|
const clients = new Set();
|
|
@@ -83,14 +92,75 @@ export function createHostDaemon(deps) {
|
|
|
83
92
|
return undefined;
|
|
84
93
|
}
|
|
85
94
|
};
|
|
95
|
+
/** What an agent said about itself, told to every linked app when it changes what they show. */
|
|
96
|
+
const learn = (agentId, news) => {
|
|
97
|
+
void learned
|
|
98
|
+
.learn(agentId, news)
|
|
99
|
+
.then((changed) => {
|
|
100
|
+
if (!changed)
|
|
101
|
+
return;
|
|
102
|
+
for (const linked of links.values())
|
|
103
|
+
void report(linked).catch(() => undefined);
|
|
104
|
+
})
|
|
105
|
+
.catch(() => undefined);
|
|
106
|
+
};
|
|
107
|
+
/** Ends the reads below on stop or handover, so nothing outlives the daemon. */
|
|
108
|
+
const stopping = new AbortController();
|
|
109
|
+
const usageReadAt = new Map();
|
|
110
|
+
/** The allowance from the agent's own CLI where ACP carries none, at most every 30s per agent. */
|
|
111
|
+
const refreshUsage = async (agentId) => {
|
|
112
|
+
const profile = profiles.find((candidate) => candidate.id === agentId);
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (!profile || stopping.signal.aborted)
|
|
115
|
+
return;
|
|
116
|
+
if (now - (usageReadAt.get(agentId) ?? 0) < USAGE_READ_FLOOR_MS)
|
|
117
|
+
return;
|
|
118
|
+
usageReadAt.set(agentId, now);
|
|
119
|
+
const usage = await readUsage(profile, await agentEnvironment(), now).catch(() => undefined);
|
|
120
|
+
if (usage && !stopping.signal.aborted)
|
|
121
|
+
learn(agentId, { usage });
|
|
122
|
+
};
|
|
123
|
+
let surveyed = false;
|
|
124
|
+
/**
|
|
125
|
+
* Options for every agent that has not run a turn yet, so the sidebar can offer a model first.
|
|
126
|
+
* Once per run, when an app first links: a computer no app uses never starts its agents.
|
|
127
|
+
*/
|
|
128
|
+
const surveyAgents = async () => {
|
|
129
|
+
if (surveyed)
|
|
130
|
+
return;
|
|
131
|
+
surveyed = true;
|
|
132
|
+
const env = await agentEnvironment();
|
|
133
|
+
for (const agent of agents) {
|
|
134
|
+
const profile = profiles.find((candidate) => candidate.id === agent.id);
|
|
135
|
+
if (!profile || stopping.signal.aborted)
|
|
136
|
+
continue;
|
|
137
|
+
void refreshUsage(agent.id);
|
|
138
|
+
if (learned.knows(agent.id))
|
|
139
|
+
continue;
|
|
140
|
+
const options = await surveyOptions(profile, deps.launcher, env, stopping.signal);
|
|
141
|
+
if (stopping.signal.aborted)
|
|
142
|
+
return;
|
|
143
|
+
logEvent("agent_surveyed", { agentId: agent.id, options: options?.length ?? 0 });
|
|
144
|
+
if (options && options.length > 0)
|
|
145
|
+
learn(agent.id, { options });
|
|
146
|
+
}
|
|
147
|
+
};
|
|
86
148
|
const running = new Set();
|
|
87
149
|
let handedOver = false;
|
|
88
150
|
let finishing = false;
|
|
89
151
|
let finishHandover = () => undefined;
|
|
90
152
|
const drained = new Promise((resolve) => (finishHandover = resolve));
|
|
91
|
-
|
|
153
|
+
/** A turn runs once here, even when a pushed turn its app took back is claimed again. */
|
|
154
|
+
const runningTurns = new Set();
|
|
155
|
+
const run = (state, claimed, how = "claimed") => {
|
|
92
156
|
const { link } = state;
|
|
93
|
-
|
|
157
|
+
if (runningTurns.has(claimed.turnId))
|
|
158
|
+
return;
|
|
159
|
+
runningTurns.add(claimed.turnId);
|
|
160
|
+
logEvent(how === "pushed" ? "turn_pushed" : "turn_claimed", {
|
|
161
|
+
appOrigin: link.grant.appOrigin,
|
|
162
|
+
turnId: claimed.turnId,
|
|
163
|
+
});
|
|
94
164
|
state.turns += 1;
|
|
95
165
|
const turn = (link.grant.dev ? Promise.resolve(DEV_POLICY) : loadPolicy(paths.policy, deps.policy))
|
|
96
166
|
.then((policy) => runTurn(claimed, {
|
|
@@ -113,6 +183,7 @@ export function createHostDaemon(deps) {
|
|
|
113
183
|
policy,
|
|
114
184
|
prompter: deps.prompter,
|
|
115
185
|
now: Date.now,
|
|
186
|
+
learned: learn,
|
|
116
187
|
pairing: {
|
|
117
188
|
secret: async () => (await deps.grants.get(link.grant.appOrigin))?.pairingSecret,
|
|
118
189
|
forget: async () => {
|
|
@@ -126,6 +197,8 @@ export function createHostDaemon(deps) {
|
|
|
126
197
|
}))
|
|
127
198
|
.then((outcome) => logEvent("turn_finished", { turnId: claimed.turnId, outcome }), (error) => logEvent("turn_finished", { turnId: claimed.turnId, error: String(error) }))
|
|
128
199
|
.finally(() => {
|
|
200
|
+
runningTurns.delete(claimed.turnId);
|
|
201
|
+
void refreshUsage(claimed.agent.agentId);
|
|
129
202
|
state.turns -= 1;
|
|
130
203
|
running.delete(turn);
|
|
131
204
|
settleHandover();
|
|
@@ -159,7 +232,79 @@ export function createHostDaemon(deps) {
|
|
|
159
232
|
if (state)
|
|
160
233
|
logEvent("link_closed", { appOrigin: origin, reason });
|
|
161
234
|
};
|
|
235
|
+
const toolsDeps = () => ({
|
|
236
|
+
profiles: profiles.filter((profile) => agents.some((agent) => agent.id === profile.id)),
|
|
237
|
+
hostCommand: join(paths.root, "bin", "parlor-host"),
|
|
238
|
+
env: agentEnvironment,
|
|
239
|
+
});
|
|
240
|
+
const toolsOf = (grant) => {
|
|
241
|
+
const tools = grant?.agentTools;
|
|
242
|
+
if (!tools?.on || !tools.name || !tools.agents?.length)
|
|
243
|
+
return undefined;
|
|
244
|
+
return {
|
|
245
|
+
name: tools.name,
|
|
246
|
+
agents: agentNames(profiles, { name: tools.name, agents: tools.agents }),
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
/** Registers the app with the agents here once the user chose it and it is not yet. */
|
|
250
|
+
const ensureAgentTools = async (grant) => {
|
|
251
|
+
if (!grant.agentTools?.on || grant.dev || grant.agentTools.agents)
|
|
252
|
+
return grant;
|
|
253
|
+
const registered = await registerAgentTools(toolsDeps(), grant);
|
|
254
|
+
const next = { ...grant, agentTools: { on: true, ...registered } };
|
|
255
|
+
await deps.grants.save(next);
|
|
256
|
+
return next;
|
|
257
|
+
};
|
|
258
|
+
const removeAgentTools = async (grant) => {
|
|
259
|
+
const tools = grant?.agentTools;
|
|
260
|
+
if (!tools?.name || !tools.agents?.length)
|
|
261
|
+
return;
|
|
262
|
+
await unregisterAgentTools(toolsDeps(), { name: tools.name, agents: tools.agents });
|
|
263
|
+
};
|
|
264
|
+
const agentTokens = new Map();
|
|
265
|
+
/** The grant as last saved, which a link opened before a change still holds the old copy of. */
|
|
266
|
+
const latest = new Map();
|
|
267
|
+
deps.proxy.serveOutside((appOrigin) => {
|
|
268
|
+
const state = links.get(appOrigin);
|
|
269
|
+
if (!state || state.link.grant.dev)
|
|
270
|
+
return undefined;
|
|
271
|
+
const grant = latest.get(appOrigin) ?? state.link.grant;
|
|
272
|
+
if (!grant.agentTools?.on || !grant.agentTools.name)
|
|
273
|
+
return undefined;
|
|
274
|
+
return {
|
|
275
|
+
appOrigin,
|
|
276
|
+
appName: grant.appName,
|
|
277
|
+
name: grant.agentTools.name,
|
|
278
|
+
account: grant.account.email ?? grant.account.name,
|
|
279
|
+
token: async (agent) => {
|
|
280
|
+
const key = `${grant.grantId}\n${agent}`;
|
|
281
|
+
const held = agentTokens.get(key);
|
|
282
|
+
if (held && held.expiresAt - AGENT_TOKEN_MARGIN_MS > Date.now())
|
|
283
|
+
return held.token;
|
|
284
|
+
const fresh = await state.link.api.calls.call("hostAgentToken", {
|
|
285
|
+
agent: `${agent} on ${name}`.slice(0, 120),
|
|
286
|
+
});
|
|
287
|
+
agentTokens.set(key, fresh);
|
|
288
|
+
return fresh.token;
|
|
289
|
+
},
|
|
290
|
+
askHere: async (question) => (await deps.prompter.answerHere({
|
|
291
|
+
appName: grant.appName,
|
|
292
|
+
agent: "Parlor Host",
|
|
293
|
+
question,
|
|
294
|
+
choices: [
|
|
295
|
+
{ id: "allow", label: "Allow" },
|
|
296
|
+
{ id: "deny", label: "Don't allow" },
|
|
297
|
+
],
|
|
298
|
+
})) === "allow",
|
|
299
|
+
};
|
|
300
|
+
});
|
|
162
301
|
const forget = async (grant) => {
|
|
302
|
+
await removeAgentTools((await deps.grants.get(grant.appOrigin)) ?? grant);
|
|
303
|
+
latest.delete(grant.appOrigin);
|
|
304
|
+
for (const key of agentTokens.keys()) {
|
|
305
|
+
if (key.startsWith(`${grant.grantId}\n`))
|
|
306
|
+
agentTokens.delete(key);
|
|
307
|
+
}
|
|
163
308
|
devTokens.delete(grant.appOrigin);
|
|
164
309
|
await deps.grants.remove(grant.appOrigin);
|
|
165
310
|
await deps.keys.remove(grant.appOrigin);
|
|
@@ -167,7 +312,9 @@ export function createHostDaemon(deps) {
|
|
|
167
312
|
await deps.pool.forget(grant);
|
|
168
313
|
};
|
|
169
314
|
const open = (grant, keys) => {
|
|
315
|
+
void surveyAgents().catch(() => undefined);
|
|
170
316
|
const origin = grant.appOrigin;
|
|
317
|
+
latest.set(origin, grant);
|
|
171
318
|
let state;
|
|
172
319
|
const link = createAppLink(grant, {
|
|
173
320
|
keys,
|
|
@@ -176,14 +323,25 @@ export function createHostDaemon(deps) {
|
|
|
176
323
|
platform,
|
|
177
324
|
fetch: (input, init) => fetch(input, init),
|
|
178
325
|
onWake: () => wake(state),
|
|
326
|
+
onPushed: (_link, turn) => {
|
|
327
|
+
// Tells the app this computer has the turn, while it already starts on it.
|
|
328
|
+
void link.api.calls
|
|
329
|
+
.call("hostAppend", { turnId: turn.turnId, items: [] })
|
|
330
|
+
.catch(() => undefined);
|
|
331
|
+
run(state, turn, "pushed");
|
|
332
|
+
},
|
|
179
333
|
busy: () => state.turns > 0,
|
|
180
334
|
onRevoked: () => {
|
|
181
335
|
if (links.get(origin) !== state)
|
|
182
336
|
return;
|
|
183
337
|
if (grant.dev)
|
|
184
338
|
void reattach(grant);
|
|
185
|
-
else
|
|
186
|
-
|
|
339
|
+
else {
|
|
340
|
+
const removedFrom = toolsOf(latest.get(origin) ?? grant)?.agents;
|
|
341
|
+
void closeLink(origin, "revoked")
|
|
342
|
+
.then(() => forget(grant))
|
|
343
|
+
.then(() => deps.prompter.disconnected?.({ appName: grant.appName, removedFrom }));
|
|
344
|
+
}
|
|
187
345
|
},
|
|
188
346
|
onIncompatible: (refused, welcome) => {
|
|
189
347
|
state.incompatible = incompatibleMessage(refused, welcome);
|
|
@@ -217,10 +375,19 @@ export function createHostDaemon(deps) {
|
|
|
217
375
|
}
|
|
218
376
|
return { devices: pinned.map((device) => device.deviceId), presenceDue };
|
|
219
377
|
};
|
|
378
|
+
/** The agents with what they said about themselves, and the name they use the app by outside. */
|
|
379
|
+
const describeFor = (origin) => {
|
|
380
|
+
const tools = latest.get(origin)?.agentTools;
|
|
381
|
+
return learned
|
|
382
|
+
.describe(agents, Date.now())
|
|
383
|
+
.map((agent) => tools?.on && tools.name && tools.agents?.includes(agent.id)
|
|
384
|
+
? { ...agent, outsideName: tools.name }
|
|
385
|
+
: agent);
|
|
386
|
+
};
|
|
220
387
|
const report = async (state) => {
|
|
221
388
|
const reported = state.link.api.calls.call("hostReport", {
|
|
222
389
|
online: true,
|
|
223
|
-
agents,
|
|
390
|
+
agents: describeFor(state.link.grant.appOrigin),
|
|
224
391
|
...(await pinnedDevices(state)),
|
|
225
392
|
});
|
|
226
393
|
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("The app did not answer in time.")), CONNECT_TIMEOUT_MS));
|
|
@@ -306,6 +473,7 @@ export function createHostDaemon(deps) {
|
|
|
306
473
|
await deps.proxy.listen(paths.proxy);
|
|
307
474
|
profiles = await loadProfiles(paths.agents);
|
|
308
475
|
agents = await detectAgents(profiles);
|
|
476
|
+
await learned.load();
|
|
309
477
|
finishStartup();
|
|
310
478
|
}
|
|
311
479
|
catch (error) {
|
|
@@ -323,25 +491,46 @@ export function createHostDaemon(deps) {
|
|
|
323
491
|
}
|
|
324
492
|
},
|
|
325
493
|
async connect(appOrigin) {
|
|
326
|
-
const
|
|
327
|
-
if (!
|
|
494
|
+
const saved = await deps.grants.get(appOrigin);
|
|
495
|
+
if (!saved || saved.declined) {
|
|
328
496
|
throw new Error(`This computer is not connected to ${appOrigin}.`);
|
|
329
497
|
}
|
|
498
|
+
const grant = await ensureAgentTools(saved);
|
|
330
499
|
const current = links.get(appOrigin);
|
|
331
500
|
if (current?.link.grant.grantId === grant.grantId) {
|
|
501
|
+
latest.set(appOrigin, grant);
|
|
332
502
|
await report(current);
|
|
333
|
-
return;
|
|
503
|
+
return toolsOf(grant);
|
|
334
504
|
}
|
|
335
505
|
await closeLink(appOrigin, "replaced");
|
|
336
506
|
if (current)
|
|
337
507
|
await deps.pool.forget(current.link.grant);
|
|
508
|
+
latest.set(appOrigin, grant);
|
|
338
509
|
await report(open(grant, deps.keys));
|
|
510
|
+
return toolsOf(grant);
|
|
511
|
+
},
|
|
512
|
+
async agentTools(appOrigin, on) {
|
|
513
|
+
const saved = await deps.grants.get(appOrigin);
|
|
514
|
+
if (!saved || saved.declined || saved.dev) {
|
|
515
|
+
throw new Error(`This computer is not connected to ${appOrigin}.`);
|
|
516
|
+
}
|
|
517
|
+
if (!on)
|
|
518
|
+
await removeAgentTools(saved);
|
|
519
|
+
const grant = await ensureAgentTools({ ...saved, agentTools: { on } });
|
|
520
|
+
if (!on)
|
|
521
|
+
await deps.grants.save(grant);
|
|
522
|
+
latest.set(appOrigin, grant);
|
|
523
|
+
const state = links.get(appOrigin);
|
|
524
|
+
if (state)
|
|
525
|
+
await report(state).catch(() => undefined);
|
|
526
|
+
return toolsOf(grant);
|
|
339
527
|
},
|
|
340
528
|
async disconnect(appOrigin) {
|
|
341
529
|
const state = links.get(appOrigin);
|
|
342
|
-
const grant =
|
|
530
|
+
const grant = (await deps.grants.get(appOrigin)) ?? state?.link.grant;
|
|
343
531
|
if (!grant)
|
|
344
532
|
throw new Error(`This computer is not connected to ${appOrigin}.`);
|
|
533
|
+
const removed = toolsOf(grant);
|
|
345
534
|
if (state) {
|
|
346
535
|
await state.link.stop({ revoke: true });
|
|
347
536
|
}
|
|
@@ -354,11 +543,13 @@ export function createHostDaemon(deps) {
|
|
|
354
543
|
}
|
|
355
544
|
await closeLink(appOrigin, "disconnected");
|
|
356
545
|
await forget(grant);
|
|
546
|
+
return removed;
|
|
357
547
|
},
|
|
358
548
|
async handover() {
|
|
359
549
|
if (handedOver)
|
|
360
550
|
return;
|
|
361
551
|
handedOver = true;
|
|
552
|
+
stopping.abort();
|
|
362
553
|
for (const state of links.values())
|
|
363
554
|
state.link.drain();
|
|
364
555
|
await Promise.all([...links.values()].map((state) => state.claiming));
|
|
@@ -369,9 +560,13 @@ export function createHostDaemon(deps) {
|
|
|
369
560
|
logEvent("handover_started", { version: deps.release.version });
|
|
370
561
|
},
|
|
371
562
|
async stop() {
|
|
563
|
+
stopping.abort();
|
|
372
564
|
if (!handedOver) {
|
|
373
565
|
await Promise.all([...links.values()].map((state) => Promise.race([
|
|
374
|
-
state.link.api.calls.call("hostReport", {
|
|
566
|
+
state.link.api.calls.call("hostReport", {
|
|
567
|
+
online: false,
|
|
568
|
+
agents: describeFor(state.link.grant.appOrigin),
|
|
569
|
+
}),
|
|
375
570
|
new Promise((resolve) => setTimeout(resolve, OFFLINE_REPORT_MS)),
|
|
376
571
|
]).catch(() => undefined)));
|
|
377
572
|
}
|
|
@@ -499,12 +694,18 @@ export function createHostDaemon(deps) {
|
|
|
499
694
|
}
|
|
500
695
|
case "confirm_local_pairing":
|
|
501
696
|
return confirmPairing(request);
|
|
502
|
-
case "connect":
|
|
503
|
-
await daemon.connect(request.appOrigin);
|
|
504
|
-
return { ok: true };
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
697
|
+
case "connect": {
|
|
698
|
+
const agentTools = await daemon.connect(request.appOrigin);
|
|
699
|
+
return { ok: true, ...(agentTools ? { agentTools } : {}) };
|
|
700
|
+
}
|
|
701
|
+
case "disconnect": {
|
|
702
|
+
const agentTools = await daemon.disconnect(request.appOrigin);
|
|
703
|
+
return { ok: true, ...(agentTools ? { agentTools } : {}) };
|
|
704
|
+
}
|
|
705
|
+
case "agent_tools": {
|
|
706
|
+
const agentTools = await daemon.agentTools(request.appOrigin, request.on);
|
|
707
|
+
return { ok: true, ...(agentTools ? { agentTools } : {}) };
|
|
708
|
+
}
|
|
508
709
|
case "dev_attach":
|
|
509
710
|
await attach(request.appOrigin, request.hostToken);
|
|
510
711
|
return { ok: true };
|