@parlor/agent-host 0.3.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/agent-options.d.ts +5 -0
- package/dist/agent-options.d.ts.map +1 -1
- package/dist/agent-options.js +32 -0
- package/dist/agent-options.js.map +1 -1
- 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/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/daemon.d.ts.map +1 -1
- package/dist/daemon.js +79 -13
- package/dist/daemon.js.map +1 -1
- package/dist/learned.d.ts +2 -0
- package/dist/learned.d.ts.map +1 -1
- package/dist/learned.js +1 -0
- package/dist/learned.js.map +1 -1
- 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/mcp-proxy.d.ts.map +1 -1
- package/dist/mcp-proxy.js +108 -16
- package/dist/mcp-proxy.js.map +1 -1
- package/dist/spec-kit.d.ts +2 -0
- package/dist/spec-kit.d.ts.map +1 -1
- package/dist/spec-kit.js +6 -1
- package/dist/spec-kit.js.map +1 -1
- package/package.json +9 -9
package/dist/agent-options.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export declare function optionChanges(options: readonly AgentOption[], chosen: R
|
|
|
9
9
|
* CLI names under `unifiedWindows`, or the one window an event is about.
|
|
10
10
|
*/
|
|
11
11
|
export declare function agentUsageOf(meta: Readonly<Record<string, unknown>> | null | undefined, at: number): AgentUsage | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Codex's rate limit, from `account/rateLimits/read` on its app-server: two windows it calls
|
|
14
|
+
* `primary` and `secondary`, each carrying its own length, so neither is taken to be the short one.
|
|
15
|
+
*/
|
|
16
|
+
export declare function codexUsageOf(result: unknown, at: number): AgentUsage | undefined;
|
|
12
17
|
/**
|
|
13
18
|
* `next` laid over `held` window by window: an event names one window, and the others it
|
|
14
19
|
* leaves out still stand until they roll over.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-options.d.ts","sourceRoot":"","sources":["../src/agent-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAoB,MAAM,cAAc,CAAC;AAE9E,qFAAqF;AACrF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GACnD;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAQnD;AAiCD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC1D,EAAE,EAAE,MAAM,GACT,UAAU,GAAG,SAAS,CAmBxB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAQrF"}
|
|
1
|
+
{"version":3,"file":"agent-options.d.ts","sourceRoot":"","sources":["../src/agent-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAoB,MAAM,cAAc,CAAC;AAE9E,qFAAqF;AACrF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GACnD;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAQnD;AAiCD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EAC1D,EAAE,EAAE,MAAM,GACT,UAAU,GAAG,SAAS,CAmBxB;AAQD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CA2BhF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAQrF"}
|
package/dist/agent-options.js
CHANGED
|
@@ -51,6 +51,36 @@ export function agentUsageOf(meta, at) {
|
|
|
51
51
|
});
|
|
52
52
|
return windows.length === 0 ? undefined : { windows: ordered(windows), at };
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Codex's rate limit, from `account/rateLimits/read` on its app-server: two windows it calls
|
|
56
|
+
* `primary` and `secondary`, each carrying its own length, so neither is taken to be the short one.
|
|
57
|
+
*/
|
|
58
|
+
export function codexUsageOf(result, at) {
|
|
59
|
+
const limits = result?.rateLimits;
|
|
60
|
+
if (!limits || typeof limits !== "object")
|
|
61
|
+
return undefined;
|
|
62
|
+
const windows = ["primary", "secondary"].flatMap((id) => {
|
|
63
|
+
const slot = limits[id];
|
|
64
|
+
const used = slot?.usedPercent;
|
|
65
|
+
const minutes = slot?.windowDurationMins;
|
|
66
|
+
if (typeof used !== "number" || !Number.isFinite(used))
|
|
67
|
+
return [];
|
|
68
|
+
if (typeof minutes !== "number" || !(minutes > 0))
|
|
69
|
+
return [];
|
|
70
|
+
const resetsAt = typeof slot?.resetsAt === "number" && Number.isFinite(slot.resetsAt)
|
|
71
|
+
? slot.resetsAt * 1000
|
|
72
|
+
: undefined;
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
id,
|
|
76
|
+
minutes,
|
|
77
|
+
used: Math.min(1, Math.max(0, used / 100)),
|
|
78
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
});
|
|
82
|
+
return windows.length === 0 ? undefined : { windows: ordered(windows), at };
|
|
83
|
+
}
|
|
54
84
|
/**
|
|
55
85
|
* `next` laid over `held` window by window: an event names one window, and the others it
|
|
56
86
|
* leaves out still stand until they roll over.
|
|
@@ -69,5 +99,7 @@ function ordered(windows) {
|
|
|
69
99
|
const ACCOUNT_WINDOWS = {
|
|
70
100
|
five_hour: true,
|
|
71
101
|
seven_day: true,
|
|
102
|
+
primary: true,
|
|
103
|
+
secondary: true,
|
|
72
104
|
};
|
|
73
105
|
//# sourceMappingURL=agent-options.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-options.js","sourceRoot":"","sources":["../src/agent-options.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentOption, AgentUsage, AgentUsageWindow } from \"@parlor/chat\";\n\n/** The values `chosen` names that `options` offers and does not already run with. */\nexport function optionChanges(\n options: readonly AgentOption[],\n chosen: Readonly<Record<string, string>> | undefined,\n): { readonly id: string; readonly value: string }[] {\n return Object.entries(chosen ?? {}).flatMap(([id, value]) => {\n const option = options.find((candidate) => candidate.id === id);\n if (!option || option.current === value) return [];\n return option.values.some((candidate) => candidate.id === value)\n ? [{ id, value }]\n : [];\n });\n}\n\nconst RATE_LIMIT_META = \"_claude/rateLimit\";\nconst WINDOW_MINUTES: Readonly<Record<string, number>> = {\n five_hour: 5 * 60,\n seven_day: 7 * 24 * 60,\n seven_day_opus: 7 * 24 * 60,\n seven_day_sonnet: 7 * 24 * 60,\n};\n\ninterface RateLimitSlot {\n readonly utilization?: unknown;\n readonly resetsAt?: unknown;\n}\n\nfunction windowOf(id: string, slot: RateLimitSlot): AgentUsageWindow | undefined {\n const minutes = WINDOW_MINUTES[id];\n const used = slot.utilization;\n if (minutes === undefined || typeof used !== \"number\" || !Number.isFinite(used)) {\n return undefined;\n }\n const resetsAt =\n typeof slot.resetsAt === \"number\" && Number.isFinite(slot.resetsAt)\n ? slot.resetsAt * 1000\n : undefined;\n return {\n id,\n minutes,\n used: Math.min(1, Math.max(0, used)),\n ...(resetsAt === undefined ? {} : { resetsAt }),\n };\n}\n\n/**\n * Claude Code's rate limit, which rides on a `usage_update`'s `_meta`: every window a newer\n * CLI names under `unifiedWindows`, or the one window an event is about.\n */\nexport function agentUsageOf(\n meta: Readonly<Record<string, unknown>> | null | undefined,\n at: number,\n): AgentUsage | undefined {\n const info = meta?.[RATE_LIMIT_META] as\n | (RateLimitSlot & {\n readonly rateLimitType?: unknown;\n readonly unifiedWindows?: Readonly<Record<string, RateLimitSlot>>;\n })\n | undefined;\n if (!info || typeof info !== \"object\") return undefined;\n const slots: [string, RateLimitSlot][] =\n info.unifiedWindows && typeof info.unifiedWindows === \"object\"\n ? Object.entries(info.unifiedWindows)\n : typeof info.rateLimitType === \"string\"\n ? [[info.rateLimitType, info]]\n : [];\n const windows = slots.flatMap(([id, slot]) => {\n const window = windowOf(id, slot);\n return window ? [window] : [];\n });\n return windows.length === 0 ? undefined : { windows: ordered(windows), at };\n}\n\n/**\n * `next` laid over `held` window by window: an event names one window, and the others it\n * leaves out still stand until they roll over.\n */\nexport function mergeUsage(held: AgentUsage | undefined, next: AgentUsage): AgentUsage {\n const named = new Set(next.windows.map((window) => window.id));\n const kept = (held?.windows ?? []).filter(\n (window) =>\n !named.has(window.id) &&\n (window.resetsAt === undefined || window.resetsAt > next.at),\n );\n return { windows: ordered([...next.windows, ...kept]), at: next.at };\n}\n\n/** The account's own windows before a model's, each set shortest first. */\nfunction ordered(windows: readonly AgentUsageWindow[]): AgentUsageWindow[] {\n const perModel = (window: AgentUsageWindow) => (window.id in ACCOUNT_WINDOWS ? 0 : 1);\n return [...windows].sort(\n (a, b) =>\n perModel(a) - perModel(b) || a.minutes - b.minutes || a.id.localeCompare(b.id),\n );\n}\n\nconst ACCOUNT_WINDOWS: Readonly<Record<string, true>> = {\n five_hour: true,\n seven_day: true,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"agent-options.js","sourceRoot":"","sources":["../src/agent-options.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentOption, AgentUsage, AgentUsageWindow } from \"@parlor/chat\";\n\n/** The values `chosen` names that `options` offers and does not already run with. */\nexport function optionChanges(\n options: readonly AgentOption[],\n chosen: Readonly<Record<string, string>> | undefined,\n): { readonly id: string; readonly value: string }[] {\n return Object.entries(chosen ?? {}).flatMap(([id, value]) => {\n const option = options.find((candidate) => candidate.id === id);\n if (!option || option.current === value) return [];\n return option.values.some((candidate) => candidate.id === value)\n ? [{ id, value }]\n : [];\n });\n}\n\nconst RATE_LIMIT_META = \"_claude/rateLimit\";\nconst WINDOW_MINUTES: Readonly<Record<string, number>> = {\n five_hour: 5 * 60,\n seven_day: 7 * 24 * 60,\n seven_day_opus: 7 * 24 * 60,\n seven_day_sonnet: 7 * 24 * 60,\n};\n\ninterface RateLimitSlot {\n readonly utilization?: unknown;\n readonly resetsAt?: unknown;\n}\n\nfunction windowOf(id: string, slot: RateLimitSlot): AgentUsageWindow | undefined {\n const minutes = WINDOW_MINUTES[id];\n const used = slot.utilization;\n if (minutes === undefined || typeof used !== \"number\" || !Number.isFinite(used)) {\n return undefined;\n }\n const resetsAt =\n typeof slot.resetsAt === \"number\" && Number.isFinite(slot.resetsAt)\n ? slot.resetsAt * 1000\n : undefined;\n return {\n id,\n minutes,\n used: Math.min(1, Math.max(0, used)),\n ...(resetsAt === undefined ? {} : { resetsAt }),\n };\n}\n\n/**\n * Claude Code's rate limit, which rides on a `usage_update`'s `_meta`: every window a newer\n * CLI names under `unifiedWindows`, or the one window an event is about.\n */\nexport function agentUsageOf(\n meta: Readonly<Record<string, unknown>> | null | undefined,\n at: number,\n): AgentUsage | undefined {\n const info = meta?.[RATE_LIMIT_META] as\n | (RateLimitSlot & {\n readonly rateLimitType?: unknown;\n readonly unifiedWindows?: Readonly<Record<string, RateLimitSlot>>;\n })\n | undefined;\n if (!info || typeof info !== \"object\") return undefined;\n const slots: [string, RateLimitSlot][] =\n info.unifiedWindows && typeof info.unifiedWindows === \"object\"\n ? Object.entries(info.unifiedWindows)\n : typeof info.rateLimitType === \"string\"\n ? [[info.rateLimitType, info]]\n : [];\n const windows = slots.flatMap(([id, slot]) => {\n const window = windowOf(id, slot);\n return window ? [window] : [];\n });\n return windows.length === 0 ? undefined : { windows: ordered(windows), at };\n}\n\ninterface CodexWindow {\n readonly usedPercent?: unknown;\n readonly windowDurationMins?: unknown;\n readonly resetsAt?: unknown;\n}\n\n/**\n * Codex's rate limit, from `account/rateLimits/read` on its app-server: two windows it calls\n * `primary` and `secondary`, each carrying its own length, so neither is taken to be the short one.\n */\nexport function codexUsageOf(result: unknown, at: number): AgentUsage | undefined {\n const limits = (\n result as { readonly rateLimits?: Readonly<Record<string, CodexWindow>> }\n )?.rateLimits;\n if (!limits || typeof limits !== \"object\") return undefined;\n const windows = ([\"primary\", \"secondary\"] as const).flatMap(\n (id): AgentUsageWindow[] => {\n const slot = limits[id];\n const used = slot?.usedPercent;\n const minutes = slot?.windowDurationMins;\n if (typeof used !== \"number\" || !Number.isFinite(used)) return [];\n if (typeof minutes !== \"number\" || !(minutes > 0)) return [];\n const resetsAt =\n typeof slot?.resetsAt === \"number\" && Number.isFinite(slot.resetsAt)\n ? slot.resetsAt * 1000\n : undefined;\n return [\n {\n id,\n minutes,\n used: Math.min(1, Math.max(0, used / 100)),\n ...(resetsAt === undefined ? {} : { resetsAt }),\n },\n ];\n },\n );\n return windows.length === 0 ? undefined : { windows: ordered(windows), at };\n}\n\n/**\n * `next` laid over `held` window by window: an event names one window, and the others it\n * leaves out still stand until they roll over.\n */\nexport function mergeUsage(held: AgentUsage | undefined, next: AgentUsage): AgentUsage {\n const named = new Set(next.windows.map((window) => window.id));\n const kept = (held?.windows ?? []).filter(\n (window) =>\n !named.has(window.id) &&\n (window.resetsAt === undefined || window.resetsAt > next.at),\n );\n return { windows: ordered([...next.windows, ...kept]), at: next.at };\n}\n\n/** The account's own windows before a model's, each set shortest first. */\nfunction ordered(windows: readonly AgentUsageWindow[]): AgentUsageWindow[] {\n const perModel = (window: AgentUsageWindow) => (window.id in ACCOUNT_WINDOWS ? 0 : 1);\n return [...windows].sort(\n (a, b) =>\n perModel(a) - perModel(b) || a.minutes - b.minutes || a.id.localeCompare(b.id),\n );\n}\n\nconst ACCOUNT_WINDOWS: Readonly<Record<string, true>> = {\n five_hour: true,\n seven_day: true,\n primary: true,\n secondary: true,\n};\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentOption, AgentUsage } from "@parlor/chat";
|
|
2
|
+
import type { AgentLauncher } from "./acp.js";
|
|
3
|
+
import type { AgentProfile } from "./agents.js";
|
|
4
|
+
/**
|
|
5
|
+
* The options `profile` offers, from a session opened in a scratch directory only to read
|
|
6
|
+
* them and then closed: no prompt, so no model turn. Lets the sidebar offer a model before
|
|
7
|
+
* the agent's first turn. Undefined when the agent cannot be started or is not logged in.
|
|
8
|
+
*/
|
|
9
|
+
export declare function surveyOptions(profile: AgentProfile, launcher: AgentLauncher, env: Readonly<Record<string, string>>, stop?: AbortSignal): Promise<readonly AgentOption[] | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* The account's allowance from the agent's own CLI, for an agent whose ACP adapter does not
|
|
12
|
+
* carry it: `codex-acp` drops Codex's rate limits. Claude Code sends its own over ACP.
|
|
13
|
+
*/
|
|
14
|
+
export declare function readUsage(profile: AgentProfile, env: Readonly<Record<string, string>>, now: number): Promise<AgentUsage | undefined>;
|
|
15
|
+
//# sourceMappingURL=agent-survey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-survey.d.ts","sourceRoot":"","sources":["../src/agent-survey.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACrC,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,SAAS,WAAW,EAAE,GAAG,SAAS,CAAC,CAyB7C;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACrC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAMjC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { codexUsageOf } from "./agent-options.js";
|
|
5
|
+
import { askCodex } from "./codex-app-server.js";
|
|
6
|
+
const SURVEY_TIMEOUT_MS = 60_000;
|
|
7
|
+
/**
|
|
8
|
+
* The options `profile` offers, from a session opened in a scratch directory only to read
|
|
9
|
+
* them and then closed: no prompt, so no model turn. Lets the sidebar offer a model before
|
|
10
|
+
* the agent's first turn. Undefined when the agent cannot be started or is not logged in.
|
|
11
|
+
*/
|
|
12
|
+
export async function surveyOptions(profile, launcher, env, stop) {
|
|
13
|
+
const cwd = await mkdtemp(join(tmpdir(), "parlor-survey-"));
|
|
14
|
+
const timeout = AbortSignal.timeout(SURVEY_TIMEOUT_MS);
|
|
15
|
+
const signal = stop ? AbortSignal.any([stop, timeout]) : timeout;
|
|
16
|
+
try {
|
|
17
|
+
const connection = await launcher.launch(profile, { cwd, env, signal });
|
|
18
|
+
const end = () => void connection.kill();
|
|
19
|
+
signal.addEventListener("abort", end, { once: true });
|
|
20
|
+
try {
|
|
21
|
+
if (!connection.capabilities.ready)
|
|
22
|
+
return undefined;
|
|
23
|
+
const session = await connection.openSession({
|
|
24
|
+
cwd,
|
|
25
|
+
mcpServers: [],
|
|
26
|
+
pinAskMode: true,
|
|
27
|
+
});
|
|
28
|
+
return session.options;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
signal.removeEventListener("abort", end);
|
|
32
|
+
await connection.kill();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await rm(cwd, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The account's allowance from the agent's own CLI, for an agent whose ACP adapter does not
|
|
44
|
+
* carry it: `codex-acp` drops Codex's rate limits. Claude Code sends its own over ACP.
|
|
45
|
+
*/
|
|
46
|
+
export async function readUsage(profile, env, now) {
|
|
47
|
+
if (profile.id !== "codex")
|
|
48
|
+
return undefined;
|
|
49
|
+
return codexUsageOf(await askCodex(profile.binary, "account/rateLimits/read", null, env), now);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=agent-survey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-survey.js","sourceRoot":"","sources":["../src/agent-survey.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAqB,EACrB,QAAuB,EACvB,GAAqC,EACrC,IAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACjE,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,KAAK,UAAU,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;YACrD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC;gBAC3C,GAAG;gBACH,UAAU,EAAE,EAAE;gBACd,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,OAAO,CAAC;QACzB,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAqB,EACrB,GAAqC,EACrC,GAAW;IAEX,IAAI,OAAO,CAAC,EAAE,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO,YAAY,CACjB,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,yBAAyB,EAAE,IAAI,EAAE,GAAG,CAAC,EACpE,GAAG,CACJ,CAAC;AACJ,CAAC","sourcesContent":["import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport type { AgentOption, AgentUsage } from \"@parlor/chat\";\n\nimport type { AgentLauncher } from \"./acp.js\";\nimport { codexUsageOf } from \"./agent-options.js\";\nimport type { AgentProfile } from \"./agents.js\";\nimport { askCodex } from \"./codex-app-server.js\";\n\nconst SURVEY_TIMEOUT_MS = 60_000;\n\n/**\n * The options `profile` offers, from a session opened in a scratch directory only to read\n * them and then closed: no prompt, so no model turn. Lets the sidebar offer a model before\n * the agent's first turn. Undefined when the agent cannot be started or is not logged in.\n */\nexport async function surveyOptions(\n profile: AgentProfile,\n launcher: AgentLauncher,\n env: Readonly<Record<string, string>>,\n stop?: AbortSignal,\n): Promise<readonly AgentOption[] | undefined> {\n const cwd = await mkdtemp(join(tmpdir(), \"parlor-survey-\"));\n const timeout = AbortSignal.timeout(SURVEY_TIMEOUT_MS);\n const signal = stop ? AbortSignal.any([stop, timeout]) : timeout;\n try {\n const connection = await launcher.launch(profile, { cwd, env, signal });\n const end = () => void connection.kill();\n signal.addEventListener(\"abort\", end, { once: true });\n try {\n if (!connection.capabilities.ready) return undefined;\n const session = await connection.openSession({\n cwd,\n mcpServers: [],\n pinAskMode: true,\n });\n return session.options;\n } finally {\n signal.removeEventListener(\"abort\", end);\n await connection.kill();\n }\n } catch {\n return undefined;\n } finally {\n await rm(cwd, { recursive: true, force: true });\n }\n}\n\n/**\n * The account's allowance from the agent's own CLI, for an agent whose ACP adapter does not\n * carry it: `codex-acp` drops Codex's rate limits. Claude Code sends its own over ACP.\n */\nexport async function readUsage(\n profile: AgentProfile,\n env: Readonly<Record<string, string>>,\n now: number,\n): Promise<AgentUsage | undefined> {\n if (profile.id !== \"codex\") return undefined;\n return codexUsageOf(\n await askCodex(profile.binary, \"account/rateLimits/read\", null, env),\n now,\n );\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One JSON-RPC call to a Codex app-server of its own, started for it and stopped after. For
|
|
3
|
+
* what Codex keeps but `codex-acp` drops, such as the account's rate limits; it costs no
|
|
4
|
+
* model turn. Undefined when anything goes wrong: Codex is missing, slow, or answers oddly.
|
|
5
|
+
*/
|
|
6
|
+
export declare function askCodex(binary: string, method: string, params: unknown, env: Readonly<Record<string, string>>): Promise<unknown>;
|
|
7
|
+
//# sourceMappingURL=codex-app-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server.d.ts","sourceRoot":"","sources":["../src/codex-app-server.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACpC,OAAO,CAAC,OAAO,CAAC,CA+BlB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { onLines } from "./lines.js";
|
|
3
|
+
const INITIALIZE = 1;
|
|
4
|
+
const CALL = 2;
|
|
5
|
+
const TIMEOUT_MS = 30_000;
|
|
6
|
+
/** Codex only echoes it back inside a user agent. */
|
|
7
|
+
const CLIENT_INFO = { name: "parlor-host", version: "1" };
|
|
8
|
+
/**
|
|
9
|
+
* One JSON-RPC call to a Codex app-server of its own, started for it and stopped after. For
|
|
10
|
+
* what Codex keeps but `codex-acp` drops, such as the account's rate limits; it costs no
|
|
11
|
+
* model turn. Undefined when anything goes wrong: Codex is missing, slow, or answers oddly.
|
|
12
|
+
*/
|
|
13
|
+
export function askCodex(binary, method, params, env) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let answer;
|
|
16
|
+
const child = spawn(binary, ["app-server"], {
|
|
17
|
+
env,
|
|
18
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
19
|
+
});
|
|
20
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), TIMEOUT_MS);
|
|
21
|
+
const send = (message) => child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", ...message })}\n`);
|
|
22
|
+
onLines(child.stdout, (line) => {
|
|
23
|
+
let message;
|
|
24
|
+
try {
|
|
25
|
+
message = JSON.parse(line);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (message.id === INITIALIZE)
|
|
31
|
+
send({ id: CALL, method, params });
|
|
32
|
+
else if (message.id === CALL) {
|
|
33
|
+
answer = message.result;
|
|
34
|
+
child.kill();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
child.stdin.on("error", () => undefined);
|
|
38
|
+
child.once("error", () => resolve(undefined));
|
|
39
|
+
child.once("close", () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
resolve(answer);
|
|
42
|
+
});
|
|
43
|
+
send({ id: INITIALIZE, method: "initialize", params: { clientInfo: CLIENT_INFO } });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=codex-app-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-app-server.js","sourceRoot":"","sources":["../src/codex-app-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB,MAAM,IAAI,GAAG,CAAC,CAAC;AACf,MAAM,UAAU,GAAG,MAAM,CAAC;AAC1B,qDAAqD;AACrD,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAc,EACd,MAAc,EACd,MAAe,EACf,GAAqC;IAErC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAe,CAAC;QACpB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,YAAY,CAAC,EAAE;YAC1C,GAAG;YACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,EAAE,CAC/B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC7B,IAAI,OAA6D,CAAC;YAClE,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;YAC/C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,EAAE,KAAK,UAAU;gBAAE,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;iBAC7D,IAAI,OAAO,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC7B,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;gBACxB,KAAK,CAAC,IAAI,EAAE,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\n\nimport { onLines } from \"./lines.js\";\n\nconst INITIALIZE = 1;\nconst CALL = 2;\nconst TIMEOUT_MS = 30_000;\n/** Codex only echoes it back inside a user agent. */\nconst CLIENT_INFO = { name: \"parlor-host\", version: \"1\" };\n\n/**\n * One JSON-RPC call to a Codex app-server of its own, started for it and stopped after. For\n * what Codex keeps but `codex-acp` drops, such as the account's rate limits; it costs no\n * model turn. Undefined when anything goes wrong: Codex is missing, slow, or answers oddly.\n */\nexport function askCodex(\n binary: string,\n method: string,\n params: unknown,\n env: Readonly<Record<string, string>>,\n): Promise<unknown> {\n return new Promise((resolve) => {\n let answer: unknown;\n const child = spawn(binary, [\"app-server\"], {\n env,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n const timer = setTimeout(() => child.kill(\"SIGKILL\"), TIMEOUT_MS);\n const send = (message: object) =>\n child.stdin.write(`${JSON.stringify({ jsonrpc: \"2.0\", ...message })}\\n`);\n onLines(child.stdout, (line) => {\n let message: { readonly id?: unknown; readonly result?: unknown };\n try {\n message = JSON.parse(line) as typeof message;\n } catch {\n return;\n }\n if (message.id === INITIALIZE) send({ id: CALL, method, params });\n else if (message.id === CALL) {\n answer = message.result;\n child.kill();\n }\n });\n child.stdin.on(\"error\", () => undefined);\n child.once(\"error\", () => resolve(undefined));\n child.once(\"close\", () => {\n clearTimeout(timer);\n resolve(answer);\n });\n send({ id: INITIALIZE, method: \"initialize\", params: { clientInfo: CLIENT_INFO } });\n });\n}\n"]}
|
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAW9C,OAAO,EAA8B,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAMzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAQ/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAyC,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,eAAe,CAAC;AAI9D,OAAO,EAAE,KAAK,WAAW,EAAoC,MAAM,cAAc,CAAC;AAElF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,6GAA6G;IAC7G,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,kFAAkF;IAClF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAChE,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACnE,kFAAkF;IAClF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAChF,4EAA4E;IAC5E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAiBD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,GAAG,SAAuB,GACtC,OAAO,CAAC,IAAI,CAAC,CAqBf;AAWD,6FAA6F;AAC7F,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAkB,IAAI,wBAAwB;IAC9C,cAEC;CACF;AAKD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAssBhE"}
|
package/dist/daemon.js
CHANGED
|
@@ -3,6 +3,7 @@ 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";
|
|
6
7
|
import { agentNames, registerAgentTools, unregisterAgentTools, } from "./agent-tools.js";
|
|
7
8
|
import { detectAgents, loadProfiles } from "./agents.js";
|
|
8
9
|
import { connectControl } from "./control.js";
|
|
@@ -63,6 +64,8 @@ export class HostAlreadyRunning extends Error {
|
|
|
63
64
|
super("Parlor Host is already running on this computer.");
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
/** Codex's own CLI throttles its rate-limit reports on the same interval. */
|
|
68
|
+
const USAGE_READ_FLOOR_MS = 30_000;
|
|
66
69
|
export function createHostDaemon(deps) {
|
|
67
70
|
const { paths } = deps;
|
|
68
71
|
const links = new Map();
|
|
@@ -89,14 +92,75 @@ export function createHostDaemon(deps) {
|
|
|
89
92
|
return undefined;
|
|
90
93
|
}
|
|
91
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
|
+
};
|
|
92
148
|
const running = new Set();
|
|
93
149
|
let handedOver = false;
|
|
94
150
|
let finishing = false;
|
|
95
151
|
let finishHandover = () => undefined;
|
|
96
152
|
const drained = new Promise((resolve) => (finishHandover = resolve));
|
|
97
|
-
|
|
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") => {
|
|
98
156
|
const { link } = state;
|
|
99
|
-
|
|
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
|
+
});
|
|
100
164
|
state.turns += 1;
|
|
101
165
|
const turn = (link.grant.dev ? Promise.resolve(DEV_POLICY) : loadPolicy(paths.policy, deps.policy))
|
|
102
166
|
.then((policy) => runTurn(claimed, {
|
|
@@ -119,17 +183,7 @@ export function createHostDaemon(deps) {
|
|
|
119
183
|
policy,
|
|
120
184
|
prompter: deps.prompter,
|
|
121
185
|
now: Date.now,
|
|
122
|
-
learned:
|
|
123
|
-
void learned
|
|
124
|
-
.learn(agentId, news)
|
|
125
|
-
.then((changed) => {
|
|
126
|
-
if (!changed)
|
|
127
|
-
return;
|
|
128
|
-
for (const linked of links.values())
|
|
129
|
-
void report(linked).catch(() => undefined);
|
|
130
|
-
})
|
|
131
|
-
.catch(() => undefined);
|
|
132
|
-
},
|
|
186
|
+
learned: learn,
|
|
133
187
|
pairing: {
|
|
134
188
|
secret: async () => (await deps.grants.get(link.grant.appOrigin))?.pairingSecret,
|
|
135
189
|
forget: async () => {
|
|
@@ -143,6 +197,8 @@ export function createHostDaemon(deps) {
|
|
|
143
197
|
}))
|
|
144
198
|
.then((outcome) => logEvent("turn_finished", { turnId: claimed.turnId, outcome }), (error) => logEvent("turn_finished", { turnId: claimed.turnId, error: String(error) }))
|
|
145
199
|
.finally(() => {
|
|
200
|
+
runningTurns.delete(claimed.turnId);
|
|
201
|
+
void refreshUsage(claimed.agent.agentId);
|
|
146
202
|
state.turns -= 1;
|
|
147
203
|
running.delete(turn);
|
|
148
204
|
settleHandover();
|
|
@@ -256,6 +312,7 @@ export function createHostDaemon(deps) {
|
|
|
256
312
|
await deps.pool.forget(grant);
|
|
257
313
|
};
|
|
258
314
|
const open = (grant, keys) => {
|
|
315
|
+
void surveyAgents().catch(() => undefined);
|
|
259
316
|
const origin = grant.appOrigin;
|
|
260
317
|
latest.set(origin, grant);
|
|
261
318
|
let state;
|
|
@@ -266,6 +323,13 @@ export function createHostDaemon(deps) {
|
|
|
266
323
|
platform,
|
|
267
324
|
fetch: (input, init) => fetch(input, init),
|
|
268
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
|
+
},
|
|
269
333
|
busy: () => state.turns > 0,
|
|
270
334
|
onRevoked: () => {
|
|
271
335
|
if (links.get(origin) !== state)
|
|
@@ -485,6 +549,7 @@ export function createHostDaemon(deps) {
|
|
|
485
549
|
if (handedOver)
|
|
486
550
|
return;
|
|
487
551
|
handedOver = true;
|
|
552
|
+
stopping.abort();
|
|
488
553
|
for (const state of links.values())
|
|
489
554
|
state.link.drain();
|
|
490
555
|
await Promise.all([...links.values()].map((state) => state.claiming));
|
|
@@ -495,6 +560,7 @@ export function createHostDaemon(deps) {
|
|
|
495
560
|
logEvent("handover_started", { version: deps.release.version });
|
|
496
561
|
},
|
|
497
562
|
async stop() {
|
|
563
|
+
stopping.abort();
|
|
498
564
|
if (!handedOver) {
|
|
499
565
|
await Promise.all([...links.values()].map((state) => Promise.race([
|
|
500
566
|
state.link.api.calls.call("hostReport", {
|