hoomanjs 1.58.1 → 1.60.0
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 +2 -1
- package/dist/acp/acp-agent.js +17 -3
- package/dist/acp/acp-agent.js.map +1 -1
- package/dist/acp/approvals.js +11 -0
- package/dist/acp/approvals.js.map +1 -1
- package/dist/acp/meta/daemon.d.ts +12 -0
- package/dist/acp/meta/daemon.js +16 -0
- package/dist/acp/meta/daemon.js.map +1 -0
- package/dist/acp/meta/origin.d.ts +8 -0
- package/dist/acp/meta/origin.js +38 -0
- package/dist/acp/meta/origin.js.map +1 -0
- package/dist/acp/sessions/store.d.ts +6 -0
- package/dist/acp/sessions/store.js.map +1 -1
- package/dist/chat/components/StatusBar.js +1 -25
- package/dist/chat/components/StatusBar.js.map +1 -1
- package/dist/cli.js +143 -26
- package/dist/cli.js.map +1 -1
- package/dist/configure/app.js +1 -0
- package/dist/configure/app.js.map +1 -1
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.js +32 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +15 -0
- package/dist/core/mcp/manager.js +28 -0
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/prefixed-mcp-tool.d.ts +1 -0
- package/dist/core/mcp/prefixed-mcp-tool.js +1 -1
- package/dist/core/mcp/prefixed-mcp-tool.js.map +1 -1
- package/dist/core/skills/built-in/hooman-channels/SKILL.md +6 -6
- package/dist/core/skills/built-in/hooman-config/SKILL.md +35 -3
- package/dist/core/utils/usage-format.d.ts +4 -0
- package/dist/core/utils/usage-format.js +27 -0
- package/dist/core/utils/usage-format.js.map +1 -0
- package/dist/daemon/acp-client.d.ts +53 -0
- package/dist/daemon/acp-client.js +132 -0
- package/dist/daemon/acp-client.js.map +1 -0
- package/dist/daemon/approvals.d.ts +9 -2
- package/dist/daemon/approvals.js +151 -6
- package/dist/daemon/approvals.js.map +1 -1
- package/dist/daemon/attachments.d.ts +9 -0
- package/dist/daemon/attachments.js +63 -0
- package/dist/daemon/attachments.js.map +1 -0
- package/dist/daemon/dashboard/store.d.ts +36 -0
- package/dist/daemon/dashboard/store.js +290 -0
- package/dist/daemon/dashboard/store.js.map +1 -0
- package/dist/daemon/dashboard/types.d.ts +60 -0
- package/dist/daemon/dashboard/types.js +2 -0
- package/dist/daemon/dashboard/types.js.map +1 -0
- package/dist/daemon/index.d.ts +19 -6
- package/dist/daemon/index.js +181 -74
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/mcproxy/catalog.d.ts +17 -0
- package/dist/daemon/mcproxy/catalog.js +71 -0
- package/dist/daemon/mcproxy/catalog.js.map +1 -0
- package/dist/daemon/mcproxy/http-server.d.ts +15 -0
- package/dist/daemon/mcproxy/http-server.js +150 -0
- package/dist/daemon/mcproxy/http-server.js.map +1 -0
- package/dist/daemon/mcproxy/index.d.ts +23 -0
- package/dist/daemon/mcproxy/index.js +23 -0
- package/dist/daemon/mcproxy/index.js.map +1 -0
- package/dist/daemon/mcproxy/session.d.ts +9 -0
- package/dist/daemon/mcproxy/session.js +24 -0
- package/dist/daemon/mcproxy/session.js.map +1 -0
- package/dist/daemon/queue.d.ts +14 -5
- package/dist/daemon/queue.js +40 -33
- package/dist/daemon/queue.js.map +1 -1
- package/dist/daemon/session-registry.d.ts +112 -0
- package/dist/daemon/session-registry.js +272 -0
- package/dist/daemon/session-registry.js.map +1 -0
- package/dist/daemon/session-store.d.ts +20 -0
- package/dist/daemon/session-store.js +63 -0
- package/dist/daemon/session-store.js.map +1 -0
- package/dist/daemon/ui/Card.d.ts +8 -0
- package/dist/daemon/ui/Card.js +52 -0
- package/dist/daemon/ui/Card.js.map +1 -0
- package/dist/daemon/ui/DaemonDashboardApp.d.ts +6 -0
- package/dist/daemon/ui/DaemonDashboardApp.js +75 -0
- package/dist/daemon/ui/DaemonDashboardApp.js.map +1 -0
- package/dist/daemon/ui/Footer.d.ts +3 -0
- package/dist/daemon/ui/Footer.js +7 -0
- package/dist/daemon/ui/Footer.js.map +1 -0
- package/dist/daemon/ui/Header.d.ts +4 -0
- package/dist/daemon/ui/Header.js +22 -0
- package/dist/daemon/ui/Header.js.map +1 -0
- package/dist/daemon/ui/Lane.d.ts +11 -0
- package/dist/daemon/ui/Lane.js +10 -0
- package/dist/daemon/ui/Lane.js.map +1 -0
- package/dist/daemon/ui/format.d.ts +7 -0
- package/dist/daemon/ui/format.js +67 -0
- package/dist/daemon/ui/format.js.map +1 -0
- package/dist/daemon/ui/index.d.ts +11 -0
- package/dist/daemon/ui/index.js +14 -0
- package/dist/daemon/ui/index.js.map +1 -0
- package/package.json +1 -1
- package/dist/daemon/questions.d.ts +0 -18
- package/dist/daemon/questions.js +0 -44
- package/dist/daemon/questions.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DaemonToolCatalog } from "./catalog.js";
|
|
2
|
+
import { startDaemonMcpHttpServer } from "./http-server.js";
|
|
3
|
+
import { createDaemonProxyServer } from "./session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Starts the daemon's local, loopback-only aggregate MCP tool proxy in front
|
|
6
|
+
* of the parent `Manager`'s already-connected upstream servers. Call once,
|
|
7
|
+
* after the parent has connected/subscribed to channels and before creating
|
|
8
|
+
* any ACP session; every daemon-hosted ACP session gets the one returned
|
|
9
|
+
* `mcpServer` entry instead of loading local `mcp.json`.
|
|
10
|
+
*/
|
|
11
|
+
export async function startDaemonMcpProxy(manager, options = {}) {
|
|
12
|
+
const catalog = new DaemonToolCatalog(manager);
|
|
13
|
+
const instructions = (await manager.listServerInstructions()).join("\n\n");
|
|
14
|
+
const handle = await startDaemonMcpHttpServer({
|
|
15
|
+
port: options.port ?? 0,
|
|
16
|
+
createSession: () => createDaemonProxyServer(catalog, instructions),
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
mcpServer: { name: "daemon", type: "http", url: handle.url, headers: [] },
|
|
20
|
+
close: handle.close,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/daemon/mcproxy/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAgBvD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAmB,EACnB,UAA6B,EAAE;IAE/B,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,CAAC,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;QAC5C,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC;QACvB,aAAa,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,OAAO,EAAE,YAAY,CAAC;KACpE,CAAC,CAAC;IACH,OAAO;QACL,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE;QACzE,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import type { DaemonToolCatalog } from "./catalog.js";
|
|
3
|
+
/**
|
|
4
|
+
* One frontend MCP session for a daemon-hosted ACP child: a fresh low-level
|
|
5
|
+
* `Server` per HTTP initialize handshake, exposing only the aggregate tool
|
|
6
|
+
* catalog (no experimental channel capabilities — those stay on the parent's
|
|
7
|
+
* singleton upstream connections).
|
|
8
|
+
*/
|
|
9
|
+
export declare function createDaemonProxyServer(catalog: DaemonToolCatalog, instructions: string): Server;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
const packageUrl = new URL("../../../package.json", import.meta.url);
|
|
5
|
+
const packageVersion = JSON.parse(readFileSync(packageUrl, "utf8"))
|
|
6
|
+
.version ?? "0.0.0";
|
|
7
|
+
/**
|
|
8
|
+
* One frontend MCP session for a daemon-hosted ACP child: a fresh low-level
|
|
9
|
+
* `Server` per HTTP initialize handshake, exposing only the aggregate tool
|
|
10
|
+
* catalog (no experimental channel capabilities — those stay on the parent's
|
|
11
|
+
* singleton upstream connections).
|
|
12
|
+
*/
|
|
13
|
+
export function createDaemonProxyServer(catalog, instructions) {
|
|
14
|
+
const server = new Server({ name: "hooman-daemon-mcproxy", version: packageVersion }, {
|
|
15
|
+
capabilities: { tools: {} },
|
|
16
|
+
instructions: instructions.length > 0 ? instructions : undefined,
|
|
17
|
+
});
|
|
18
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
19
|
+
tools: await catalog.listTools(),
|
|
20
|
+
}));
|
|
21
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => catalog.callTool(request.params.name, request.params.arguments, extra.signal));
|
|
22
|
+
return server;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/daemon/mcproxy/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAG5C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrE,MAAM,cAAc,GACjB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAA0B;KACnE,OAAO,IAAI,OAAO,CAAC;AAExB;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EAC1B,YAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,cAAc,EAAE,EAC1D;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAC3B,YAAY,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;KACjE,CACF,CAAC;IACF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,MAAM,OAAO,CAAC,SAAS,EAAE;KACjC,CAAC,CAAC,CAAC;IACJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CACvE,OAAO,CAAC,QAAQ,CACd,OAAO,CAAC,MAAM,CAAC,IAAI,EACnB,OAAO,CAAC,MAAM,CAAC,SAAS,EACxB,KAAK,CAAC,MAAM,CACb,CACF,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/daemon/queue.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Serializes turns per external conversation key while allowing unrelated
|
|
3
|
+
* keys to run fully concurrently — replaces the old single global-concurrency
|
|
4
|
+
* queue now that many ACP sessions may be active at once.
|
|
5
|
+
*/
|
|
6
|
+
export declare class KeyedTurnQueue {
|
|
7
|
+
#private;
|
|
8
|
+
/** Number of tasks currently queued (including the one running, if any) for `key`. */
|
|
9
|
+
length(key: string): number;
|
|
10
|
+
/** Enqueues `task` behind any already-running/queued task for the same `key`. */
|
|
11
|
+
push(key: string, task: () => Promise<void>): void;
|
|
12
|
+
/** Waits for every currently queued task (across every key) to settle. */
|
|
13
|
+
drain(): Promise<void>;
|
|
14
|
+
}
|
package/dist/daemon/queue.js
CHANGED
|
@@ -1,39 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Serializes turns per external conversation key while allowing unrelated
|
|
3
|
+
* keys to run fully concurrently — replaces the old single global-concurrency
|
|
4
|
+
* queue now that many ACP sessions may be active at once.
|
|
5
|
+
*/
|
|
6
|
+
export class KeyedTurnQueue {
|
|
7
|
+
#tails = new Map();
|
|
8
|
+
/** Number of tasks currently queued (including the one running, if any) for `key`. */
|
|
9
|
+
length(key) {
|
|
10
|
+
return this.#tails.get(key)?.length ?? 0;
|
|
11
|
+
}
|
|
12
|
+
/** Enqueues `task` behind any already-running/queued task for the same `key`. */
|
|
13
|
+
push(key, task) {
|
|
14
|
+
const state = this.#tails.get(key) ?? {
|
|
15
|
+
length: 0,
|
|
16
|
+
tail: Promise.resolve(),
|
|
17
|
+
};
|
|
18
|
+
state.length += 1;
|
|
19
|
+
this.#tails.set(key, state);
|
|
20
|
+
// Swallow task rejections here: `state.tail` is chained with `.then()`,
|
|
21
|
+
// so a rejected tail would skip every subsequent `.then()` callback for
|
|
22
|
+
// this key, permanently poisoning the queue instead of just failing one
|
|
23
|
+
// turn. Callers that care about per-task failure (e.g. `runTurn`) already
|
|
24
|
+
// catch their own errors; this is a backstop against anything that slips
|
|
25
|
+
// past that.
|
|
26
|
+
state.tail = state.tail.then(async () => {
|
|
26
27
|
try {
|
|
27
|
-
await
|
|
28
|
+
await task();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* ignore: never poison the chain for this key */
|
|
28
32
|
}
|
|
29
33
|
finally {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
state.length -= 1;
|
|
35
|
+
if (state.length === 0) {
|
|
36
|
+
this.#tails.delete(key);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Waits for every currently queued task (across every key) to settle. */
|
|
42
|
+
async drain() {
|
|
43
|
+
await Promise.allSettled([...this.#tails.values()].map((s) => s.tail));
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
//# sourceMappingURL=queue.js.map
|
package/dist/daemon/queue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/daemon/queue.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/daemon/queue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACzB,MAAM,GAAG,IAAI,GAAG,EAAmD,CAAC;IAEpE,sFAAsF;IAC/E,MAAM,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,iFAAiF;IAC1E,IAAI,CAAC,GAAW,EAAE,IAAyB;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI;YACpC,MAAM,EAAE,CAAC;YACT,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE;SACxB,CAAC;QACF,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC5B,wEAAwE;QACxE,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,yEAAyE;QACzE,aAAa;QACb,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;YAAC,MAAM,CAAC;gBACP,iDAAiD;YACnD,CAAC;oBAAS,CAAC;gBACT,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;gBAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IACnE,KAAK,CAAC,KAAK;QAChB,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,CAAC;CACF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ChannelOrigin } from "../core/approvals/channel-ask.js";
|
|
2
|
+
/** Why a runtime session's pool slot was released. */
|
|
3
|
+
export type DaemonDisposeReason = "idle_timeout" | "pool_pressure" | "shutdown" | "child_exit";
|
|
4
|
+
export type DaemonRegistryEvent = {
|
|
5
|
+
type: "slot_waiting";
|
|
6
|
+
externalKey: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "slot_acquired";
|
|
9
|
+
externalKey: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "active";
|
|
12
|
+
externalKey: string;
|
|
13
|
+
acpSessionId: string;
|
|
14
|
+
userId?: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: "idle";
|
|
17
|
+
externalKey: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "disposed";
|
|
20
|
+
externalKey: string;
|
|
21
|
+
acpSessionId: string;
|
|
22
|
+
reason: DaemonDisposeReason;
|
|
23
|
+
};
|
|
24
|
+
export type DaemonSessionRegistryOptions = {
|
|
25
|
+
/** Bound on concurrently active ACP sessions. Must be a positive integer. */
|
|
26
|
+
maxActiveSessions: number;
|
|
27
|
+
/** Ordinary idle-close delay after a session becomes non-busy. `0` disables it (pressure eviction still applies). */
|
|
28
|
+
idleTimeoutMs: number;
|
|
29
|
+
/** Called to close an ACP session (idle timeout, pool pressure, or shutdown). May throw; cleanup still runs. */
|
|
30
|
+
onClose: (externalKey: string, acpSessionId: string) => Promise<void>;
|
|
31
|
+
/** Notified on every pool/lifecycle transition, for dashboard/diagnostics consumers. */
|
|
32
|
+
onEvent?: (event: DaemonRegistryEvent) => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Tracks the daemon's channel-conversation → ACP-session bindings, the
|
|
36
|
+
* durable JSONL mapping, and a bounded pool of concurrently active ACP
|
|
37
|
+
* sessions: idle sessions are evicted LRU under pressure, callers otherwise
|
|
38
|
+
* wait FIFO for a slot, and a non-busy session with pool waiters closes
|
|
39
|
+
* immediately instead of waiting out the ordinary idle timeout.
|
|
40
|
+
*/
|
|
41
|
+
export declare class DaemonSessionRegistry {
|
|
42
|
+
#private;
|
|
43
|
+
private readonly options;
|
|
44
|
+
constructor(options: DaemonSessionRegistryOptions);
|
|
45
|
+
hydrate(): Promise<void>;
|
|
46
|
+
/** Persisted ACP session ID for a key from a prior daemon run, if any. */
|
|
47
|
+
persistedAcpSessionId(externalKey: string): string | undefined;
|
|
48
|
+
isActive(externalKey: string): boolean;
|
|
49
|
+
/** Whether an ACP session is already registered (created/resumed) for `externalKey` in this process. */
|
|
50
|
+
hasRuntime(externalKey: string): boolean;
|
|
51
|
+
acpSessionIdFor(externalKey: string): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Acquires one pool slot for `externalKey`. Reuses the caller's own slot
|
|
54
|
+
* if already held. Otherwise joins the FIFO waiter queue and, if a slot
|
|
55
|
+
* exists, immediately requests one idle LRU session be evicted to make
|
|
56
|
+
* room — but never grants the freed slot directly, so fairness holds even
|
|
57
|
+
* when eviction and waiting happen concurrently.
|
|
58
|
+
*/
|
|
59
|
+
acquireSlot(externalKey: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Releases a pool slot acquired via {@link acquireSlot} when session setup
|
|
62
|
+
* fails *before* a runtime was ever registered — otherwise the key would
|
|
63
|
+
* stay parked in `#activeKeys` forever (no runtime for `markIdle` to find),
|
|
64
|
+
* slowly exhausting the pool across repeated failures.
|
|
65
|
+
*/
|
|
66
|
+
releaseFailedSlot(externalKey: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Drops every runtime entry (but keeps pool slots reserved) after the
|
|
69
|
+
* shared ACP child process exits unexpectedly. `hasRuntime()` then returns
|
|
70
|
+
* `false` for every key, so the next turn for each falls back to
|
|
71
|
+
* `resumeSession()` against the persisted ACP session ID on the fresh
|
|
72
|
+
* child, instead of silently prompting a runtime the new child never
|
|
73
|
+
* created.
|
|
74
|
+
*/
|
|
75
|
+
invalidateRuntimes(): void;
|
|
76
|
+
/** Registers a newly created/resumed active session and marks it busy for the in-flight turn. */
|
|
77
|
+
registerActive(binding: {
|
|
78
|
+
externalKey: string;
|
|
79
|
+
acpSessionId: string;
|
|
80
|
+
cwd: string;
|
|
81
|
+
userId?: string;
|
|
82
|
+
origin: ChannelOrigin | null;
|
|
83
|
+
}): void;
|
|
84
|
+
/** Persists the external-key → ACP-session-ID binding (create, or replacement after a missing-session resume failure). */
|
|
85
|
+
persistBinding(params: {
|
|
86
|
+
externalKey: string;
|
|
87
|
+
acpSessionId: string;
|
|
88
|
+
cwd: string;
|
|
89
|
+
userId?: string | null;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
updateOrigin(externalKey: string, origin: ChannelOrigin | null): void;
|
|
92
|
+
originForAcpSession(acpSessionId: string): ChannelOrigin | null;
|
|
93
|
+
externalKeyForAcpSession(acpSessionId: string): string | undefined;
|
|
94
|
+
/** Marks a session busy for the duration of one `session/prompt` call, cancelling any armed idle timer. */
|
|
95
|
+
markBusy(externalKey: string): void;
|
|
96
|
+
/**
|
|
97
|
+
* Marks a session non-busy after its turn settles. When more work for the
|
|
98
|
+
* same key is already queued, the caller keeps its slot untouched. When
|
|
99
|
+
* pool waiters exist, the session closes immediately instead of arming the
|
|
100
|
+
* ordinary idle timer.
|
|
101
|
+
*/
|
|
102
|
+
markIdle(externalKey: string, hasQueuedWork: boolean): void;
|
|
103
|
+
/** Rejects queued waiters, closes every active session, and stops granting new slots. */
|
|
104
|
+
shutdown(): Promise<void>;
|
|
105
|
+
/** Snapshot pool occupancy for dashboard/diagnostics consumers. */
|
|
106
|
+
poolStats(): {
|
|
107
|
+
active: number;
|
|
108
|
+
max: number;
|
|
109
|
+
waiting: number;
|
|
110
|
+
};
|
|
111
|
+
get isShuttingDown(): boolean;
|
|
112
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { patchDaemonSessionBinding, readDaemonSessionBindings, } from "./session-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tracks the daemon's channel-conversation → ACP-session bindings, the
|
|
4
|
+
* durable JSONL mapping, and a bounded pool of concurrently active ACP
|
|
5
|
+
* sessions: idle sessions are evicted LRU under pressure, callers otherwise
|
|
6
|
+
* wait FIFO for a slot, and a non-busy session with pool waiters closes
|
|
7
|
+
* immediately instead of waiting out the ordinary idle timeout.
|
|
8
|
+
*/
|
|
9
|
+
export class DaemonSessionRegistry {
|
|
10
|
+
options;
|
|
11
|
+
#persisted = new Map();
|
|
12
|
+
#runtime = new Map();
|
|
13
|
+
#reverse = new Map();
|
|
14
|
+
#activeKeys = new Set();
|
|
15
|
+
#waiters = [];
|
|
16
|
+
#shuttingDown = false;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
if (!Number.isInteger(options.maxActiveSessions) ||
|
|
20
|
+
options.maxActiveSessions < 1) {
|
|
21
|
+
throw new Error("maxActiveSessions must be a positive integer.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async hydrate() {
|
|
25
|
+
this.#persisted = await readDaemonSessionBindings();
|
|
26
|
+
}
|
|
27
|
+
/** Persisted ACP session ID for a key from a prior daemon run, if any. */
|
|
28
|
+
persistedAcpSessionId(externalKey) {
|
|
29
|
+
return this.#persisted.get(externalKey)?.acpSessionId;
|
|
30
|
+
}
|
|
31
|
+
isActive(externalKey) {
|
|
32
|
+
return this.#activeKeys.has(externalKey);
|
|
33
|
+
}
|
|
34
|
+
/** Whether an ACP session is already registered (created/resumed) for `externalKey` in this process. */
|
|
35
|
+
hasRuntime(externalKey) {
|
|
36
|
+
return this.#runtime.has(externalKey);
|
|
37
|
+
}
|
|
38
|
+
acpSessionIdFor(externalKey) {
|
|
39
|
+
return this.#runtime.get(externalKey)?.acpSessionId;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Acquires one pool slot for `externalKey`. Reuses the caller's own slot
|
|
43
|
+
* if already held. Otherwise joins the FIFO waiter queue and, if a slot
|
|
44
|
+
* exists, immediately requests one idle LRU session be evicted to make
|
|
45
|
+
* room — but never grants the freed slot directly, so fairness holds even
|
|
46
|
+
* when eviction and waiting happen concurrently.
|
|
47
|
+
*/
|
|
48
|
+
async acquireSlot(externalKey) {
|
|
49
|
+
if (this.#activeKeys.has(externalKey)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.#activeKeys.size < this.options.maxActiveSessions) {
|
|
53
|
+
this.#activeKeys.add(externalKey);
|
|
54
|
+
this.options.onEvent?.({ type: "slot_acquired", externalKey });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.options.onEvent?.({ type: "slot_waiting", externalKey });
|
|
58
|
+
const waiterPromise = new Promise((resolve) => {
|
|
59
|
+
this.#waiters.push({ key: externalKey, resolve });
|
|
60
|
+
});
|
|
61
|
+
const idleKey = this.#pickIdleLru();
|
|
62
|
+
if (idleKey) {
|
|
63
|
+
void this.#closeRuntime(idleKey, "pool_pressure");
|
|
64
|
+
}
|
|
65
|
+
await waiterPromise;
|
|
66
|
+
this.options.onEvent?.({ type: "slot_acquired", externalKey });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Releases a pool slot acquired via {@link acquireSlot} when session setup
|
|
70
|
+
* fails *before* a runtime was ever registered — otherwise the key would
|
|
71
|
+
* stay parked in `#activeKeys` forever (no runtime for `markIdle` to find),
|
|
72
|
+
* slowly exhausting the pool across repeated failures.
|
|
73
|
+
*/
|
|
74
|
+
releaseFailedSlot(externalKey) {
|
|
75
|
+
if (this.#runtime.has(externalKey)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!this.#activeKeys.delete(externalKey)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const waiter = this.#waiters.shift();
|
|
82
|
+
if (waiter) {
|
|
83
|
+
this.#activeKeys.add(waiter.key);
|
|
84
|
+
waiter.resolve();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Drops every runtime entry (but keeps pool slots reserved) after the
|
|
89
|
+
* shared ACP child process exits unexpectedly. `hasRuntime()` then returns
|
|
90
|
+
* `false` for every key, so the next turn for each falls back to
|
|
91
|
+
* `resumeSession()` against the persisted ACP session ID on the fresh
|
|
92
|
+
* child, instead of silently prompting a runtime the new child never
|
|
93
|
+
* created.
|
|
94
|
+
*/
|
|
95
|
+
invalidateRuntimes() {
|
|
96
|
+
for (const runtime of this.#runtime.values()) {
|
|
97
|
+
if (runtime.idleTimer) {
|
|
98
|
+
clearTimeout(runtime.idleTimer);
|
|
99
|
+
}
|
|
100
|
+
this.options.onEvent?.({
|
|
101
|
+
type: "disposed",
|
|
102
|
+
externalKey: runtime.externalKey,
|
|
103
|
+
acpSessionId: runtime.acpSessionId,
|
|
104
|
+
reason: "child_exit",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
this.#runtime.clear();
|
|
108
|
+
this.#reverse.clear();
|
|
109
|
+
}
|
|
110
|
+
/** Registers a newly created/resumed active session and marks it busy for the in-flight turn. */
|
|
111
|
+
registerActive(binding) {
|
|
112
|
+
this.#runtime.set(binding.externalKey, {
|
|
113
|
+
externalKey: binding.externalKey,
|
|
114
|
+
acpSessionId: binding.acpSessionId,
|
|
115
|
+
cwd: binding.cwd,
|
|
116
|
+
userId: binding.userId,
|
|
117
|
+
origin: binding.origin,
|
|
118
|
+
busy: true,
|
|
119
|
+
closing: false,
|
|
120
|
+
lastActiveAt: Date.now(),
|
|
121
|
+
idleTimer: null,
|
|
122
|
+
});
|
|
123
|
+
this.#reverse.set(binding.acpSessionId, binding.externalKey);
|
|
124
|
+
this.options.onEvent?.({
|
|
125
|
+
type: "active",
|
|
126
|
+
externalKey: binding.externalKey,
|
|
127
|
+
acpSessionId: binding.acpSessionId,
|
|
128
|
+
userId: binding.userId,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Persists the external-key → ACP-session-ID binding (create, or replacement after a missing-session resume failure). */
|
|
132
|
+
async persistBinding(params) {
|
|
133
|
+
const now = new Date().toISOString();
|
|
134
|
+
const existing = this.#persisted.get(params.externalKey);
|
|
135
|
+
const entry = {
|
|
136
|
+
externalKey: params.externalKey,
|
|
137
|
+
acpSessionId: params.acpSessionId,
|
|
138
|
+
cwd: params.cwd,
|
|
139
|
+
userId: params.userId ?? null,
|
|
140
|
+
createdAt: existing?.createdAt ?? now,
|
|
141
|
+
updatedAt: now,
|
|
142
|
+
};
|
|
143
|
+
this.#persisted.set(params.externalKey, entry);
|
|
144
|
+
await patchDaemonSessionBinding(entry);
|
|
145
|
+
}
|
|
146
|
+
updateOrigin(externalKey, origin) {
|
|
147
|
+
const runtime = this.#runtime.get(externalKey);
|
|
148
|
+
if (runtime) {
|
|
149
|
+
runtime.origin = origin;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
originForAcpSession(acpSessionId) {
|
|
153
|
+
const key = this.#reverse.get(acpSessionId);
|
|
154
|
+
if (!key) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return this.#runtime.get(key)?.origin ?? null;
|
|
158
|
+
}
|
|
159
|
+
externalKeyForAcpSession(acpSessionId) {
|
|
160
|
+
return this.#reverse.get(acpSessionId);
|
|
161
|
+
}
|
|
162
|
+
/** Marks a session busy for the duration of one `session/prompt` call, cancelling any armed idle timer. */
|
|
163
|
+
markBusy(externalKey) {
|
|
164
|
+
const runtime = this.#runtime.get(externalKey);
|
|
165
|
+
if (!runtime) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
runtime.busy = true;
|
|
169
|
+
if (runtime.idleTimer) {
|
|
170
|
+
clearTimeout(runtime.idleTimer);
|
|
171
|
+
runtime.idleTimer = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Marks a session non-busy after its turn settles. When more work for the
|
|
176
|
+
* same key is already queued, the caller keeps its slot untouched. When
|
|
177
|
+
* pool waiters exist, the session closes immediately instead of arming the
|
|
178
|
+
* ordinary idle timer.
|
|
179
|
+
*/
|
|
180
|
+
markIdle(externalKey, hasQueuedWork) {
|
|
181
|
+
const runtime = this.#runtime.get(externalKey);
|
|
182
|
+
if (!runtime || runtime.closing) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
runtime.busy = false;
|
|
186
|
+
runtime.lastActiveAt = Date.now();
|
|
187
|
+
if (hasQueuedWork) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.options.onEvent?.({ type: "idle", externalKey });
|
|
191
|
+
if (this.#waiters.length > 0) {
|
|
192
|
+
void this.#closeRuntime(externalKey, "pool_pressure");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (this.options.idleTimeoutMs > 0) {
|
|
196
|
+
runtime.idleTimer = setTimeout(() => {
|
|
197
|
+
runtime.idleTimer = null;
|
|
198
|
+
if (!runtime.busy && !runtime.closing) {
|
|
199
|
+
void this.#closeRuntime(externalKey, "idle_timeout");
|
|
200
|
+
}
|
|
201
|
+
}, this.options.idleTimeoutMs);
|
|
202
|
+
runtime.idleTimer.unref?.();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
#pickIdleLru() {
|
|
206
|
+
let best;
|
|
207
|
+
for (const runtime of this.#runtime.values()) {
|
|
208
|
+
if (runtime.busy || runtime.closing) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!best || runtime.lastActiveAt < best.lastActiveAt) {
|
|
212
|
+
best = runtime;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return best?.externalKey;
|
|
216
|
+
}
|
|
217
|
+
async #closeRuntime(externalKey, reason) {
|
|
218
|
+
const runtime = this.#runtime.get(externalKey);
|
|
219
|
+
if (!runtime || runtime.closing) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
runtime.closing = true;
|
|
223
|
+
if (runtime.idleTimer) {
|
|
224
|
+
clearTimeout(runtime.idleTimer);
|
|
225
|
+
runtime.idleTimer = null;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
await this.options.onClose(externalKey, runtime.acpSessionId);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* `onClose` is documented not to throw, but cleanup below must still
|
|
232
|
+
* run so the slot and waiter queue never leak on a misbehaving caller. */
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
this.#runtime.delete(externalKey);
|
|
236
|
+
this.#reverse.delete(runtime.acpSessionId);
|
|
237
|
+
this.#activeKeys.delete(externalKey);
|
|
238
|
+
this.options.onEvent?.({
|
|
239
|
+
type: "disposed",
|
|
240
|
+
externalKey,
|
|
241
|
+
acpSessionId: runtime.acpSessionId,
|
|
242
|
+
reason,
|
|
243
|
+
});
|
|
244
|
+
const waiter = this.#waiters.shift();
|
|
245
|
+
if (waiter) {
|
|
246
|
+
this.#activeKeys.add(waiter.key);
|
|
247
|
+
waiter.resolve();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Rejects queued waiters, closes every active session, and stops granting new slots. */
|
|
252
|
+
async shutdown() {
|
|
253
|
+
this.#shuttingDown = true;
|
|
254
|
+
const pending = this.#waiters.splice(0, this.#waiters.length);
|
|
255
|
+
for (const waiter of pending) {
|
|
256
|
+
waiter.resolve();
|
|
257
|
+
}
|
|
258
|
+
await Promise.all([...this.#runtime.keys()].map((key) => this.#closeRuntime(key, "shutdown")));
|
|
259
|
+
}
|
|
260
|
+
/** Snapshot pool occupancy for dashboard/diagnostics consumers. */
|
|
261
|
+
poolStats() {
|
|
262
|
+
return {
|
|
263
|
+
active: this.#activeKeys.size,
|
|
264
|
+
max: this.options.maxActiveSessions,
|
|
265
|
+
waiting: this.#waiters.length,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
get isShuttingDown() {
|
|
269
|
+
return this.#shuttingDown;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../src/daemon/session-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted external-conversation-key → ACP-session-ID binding, so daemon
|
|
3
|
+
* restarts can `session/resume` prior conversations instead of losing them.
|
|
4
|
+
* Append-only JSONL patch log (same fold/tombstone shape as
|
|
5
|
+
* `src/acp/sessions/store.ts`), scoped to the current project.
|
|
6
|
+
*/
|
|
7
|
+
export type DaemonSessionBinding = {
|
|
8
|
+
externalKey: string;
|
|
9
|
+
acpSessionId: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
userId?: string | null;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
};
|
|
15
|
+
/** Reads and folds the append-only bindings log; unparseable (torn) lines are skipped. */
|
|
16
|
+
export declare function readDaemonSessionBindings(): Promise<Map<string, DaemonSessionBinding>>;
|
|
17
|
+
/** Appends a full or partial binding patch; `updatedAt` is bumped unless the patch sets it. */
|
|
18
|
+
export declare function patchDaemonSessionBinding(patch: Partial<Omit<DaemonSessionBinding, "externalKey">> & {
|
|
19
|
+
externalKey: string;
|
|
20
|
+
}): Promise<void>;
|