hoomanjs 1.58.0 → 1.59.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/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/cli.js +78 -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-config/SKILL.md +35 -3
- package/dist/daemon/acp-client.d.ts +49 -0
- package/dist/daemon/acp-client.js +130 -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/index.d.ts +16 -6
- package/dist/daemon/index.js +162 -71
- 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 +31 -33
- package/dist/daemon/queue.js.map +1 -1
- package/dist/daemon/session-registry.d.ts +66 -0
- package/dist/daemon/session-registry.js +198 -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/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,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,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
process.on("SIGINT", onSigInt);
|
|
22
|
-
process.on("SIGTERM", onSigTerm);
|
|
23
|
-
return [
|
|
24
|
-
queue,
|
|
25
|
-
async () => {
|
|
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
|
+
state.tail = state.tail.then(async () => {
|
|
26
21
|
try {
|
|
27
|
-
await
|
|
22
|
+
await task();
|
|
28
23
|
}
|
|
29
24
|
finally {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
state.length -= 1;
|
|
26
|
+
if (state.length === 0) {
|
|
27
|
+
this.#tails.delete(key);
|
|
28
|
+
}
|
|
34
29
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Waits for every currently queued task (across every key) to settle. */
|
|
33
|
+
async drain() {
|
|
34
|
+
await Promise.allSettled([...this.#tails.values()].map((s) => s.tail));
|
|
35
|
+
}
|
|
38
36
|
}
|
|
39
37
|
//# 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,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,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,66 @@
|
|
|
1
|
+
import type { ChannelOrigin } from "../core/approvals/channel-ask.js";
|
|
2
|
+
export type DaemonSessionRegistryOptions = {
|
|
3
|
+
/** Bound on concurrently active ACP sessions. Must be a positive integer. */
|
|
4
|
+
maxActiveSessions: number;
|
|
5
|
+
/** Ordinary idle-close delay after a session becomes non-busy. `0` disables it (pressure eviction still applies). */
|
|
6
|
+
idleTimeoutMs: number;
|
|
7
|
+
/** Called to close an ACP session (idle timeout, pool pressure, or shutdown). Must not throw. */
|
|
8
|
+
onClose: (externalKey: string, acpSessionId: string) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Tracks the daemon's channel-conversation → ACP-session bindings, the
|
|
12
|
+
* durable JSONL mapping, and a bounded pool of concurrently active ACP
|
|
13
|
+
* sessions: idle sessions are evicted LRU under pressure, callers otherwise
|
|
14
|
+
* wait FIFO for a slot, and a non-busy session with pool waiters closes
|
|
15
|
+
* immediately instead of waiting out the ordinary idle timeout.
|
|
16
|
+
*/
|
|
17
|
+
export declare class DaemonSessionRegistry {
|
|
18
|
+
#private;
|
|
19
|
+
private readonly options;
|
|
20
|
+
constructor(options: DaemonSessionRegistryOptions);
|
|
21
|
+
hydrate(): Promise<void>;
|
|
22
|
+
/** Persisted ACP session ID for a key from a prior daemon run, if any. */
|
|
23
|
+
persistedAcpSessionId(externalKey: string): string | undefined;
|
|
24
|
+
isActive(externalKey: string): boolean;
|
|
25
|
+
/** Whether an ACP session is already registered (created/resumed) for `externalKey` in this process. */
|
|
26
|
+
hasRuntime(externalKey: string): boolean;
|
|
27
|
+
acpSessionIdFor(externalKey: string): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Acquires one pool slot for `externalKey`. Reuses the caller's own slot
|
|
30
|
+
* if already held. Otherwise joins the FIFO waiter queue and, if a slot
|
|
31
|
+
* exists, immediately requests one idle LRU session be evicted to make
|
|
32
|
+
* room — but never grants the freed slot directly, so fairness holds even
|
|
33
|
+
* when eviction and waiting happen concurrently.
|
|
34
|
+
*/
|
|
35
|
+
acquireSlot(externalKey: string): Promise<void>;
|
|
36
|
+
/** Registers a newly created/resumed active session and marks it busy for the in-flight turn. */
|
|
37
|
+
registerActive(binding: {
|
|
38
|
+
externalKey: string;
|
|
39
|
+
acpSessionId: string;
|
|
40
|
+
cwd: string;
|
|
41
|
+
userId?: string;
|
|
42
|
+
origin: ChannelOrigin | null;
|
|
43
|
+
}): void;
|
|
44
|
+
/** Persists the external-key → ACP-session-ID binding (create, or replacement after a missing-session resume failure). */
|
|
45
|
+
persistBinding(params: {
|
|
46
|
+
externalKey: string;
|
|
47
|
+
acpSessionId: string;
|
|
48
|
+
cwd: string;
|
|
49
|
+
userId?: string | null;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
updateOrigin(externalKey: string, origin: ChannelOrigin | null): void;
|
|
52
|
+
originForAcpSession(acpSessionId: string): ChannelOrigin | null;
|
|
53
|
+
externalKeyForAcpSession(acpSessionId: string): string | undefined;
|
|
54
|
+
/** Marks a session busy for the duration of one `session/prompt` call, cancelling any armed idle timer. */
|
|
55
|
+
markBusy(externalKey: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Marks a session non-busy after its turn settles. When more work for the
|
|
58
|
+
* same key is already queued, the caller keeps its slot untouched. When
|
|
59
|
+
* pool waiters exist, the session closes immediately instead of arming the
|
|
60
|
+
* ordinary idle timer.
|
|
61
|
+
*/
|
|
62
|
+
markIdle(externalKey: string, hasQueuedWork: boolean): void;
|
|
63
|
+
/** Rejects queued waiters, closes every active session, and stops granting new slots. */
|
|
64
|
+
shutdown(): Promise<void>;
|
|
65
|
+
get isShuttingDown(): boolean;
|
|
66
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const waiterPromise = new Promise((resolve) => {
|
|
57
|
+
this.#waiters.push({ key: externalKey, resolve });
|
|
58
|
+
});
|
|
59
|
+
const idleKey = this.#pickIdleLru();
|
|
60
|
+
if (idleKey) {
|
|
61
|
+
void this.#closeRuntime(idleKey);
|
|
62
|
+
}
|
|
63
|
+
await waiterPromise;
|
|
64
|
+
}
|
|
65
|
+
/** Registers a newly created/resumed active session and marks it busy for the in-flight turn. */
|
|
66
|
+
registerActive(binding) {
|
|
67
|
+
this.#runtime.set(binding.externalKey, {
|
|
68
|
+
externalKey: binding.externalKey,
|
|
69
|
+
acpSessionId: binding.acpSessionId,
|
|
70
|
+
cwd: binding.cwd,
|
|
71
|
+
userId: binding.userId,
|
|
72
|
+
origin: binding.origin,
|
|
73
|
+
busy: true,
|
|
74
|
+
closing: false,
|
|
75
|
+
lastActiveAt: Date.now(),
|
|
76
|
+
idleTimer: null,
|
|
77
|
+
});
|
|
78
|
+
this.#reverse.set(binding.acpSessionId, binding.externalKey);
|
|
79
|
+
}
|
|
80
|
+
/** Persists the external-key → ACP-session-ID binding (create, or replacement after a missing-session resume failure). */
|
|
81
|
+
async persistBinding(params) {
|
|
82
|
+
const now = new Date().toISOString();
|
|
83
|
+
const existing = this.#persisted.get(params.externalKey);
|
|
84
|
+
const entry = {
|
|
85
|
+
externalKey: params.externalKey,
|
|
86
|
+
acpSessionId: params.acpSessionId,
|
|
87
|
+
cwd: params.cwd,
|
|
88
|
+
userId: params.userId ?? null,
|
|
89
|
+
createdAt: existing?.createdAt ?? now,
|
|
90
|
+
updatedAt: now,
|
|
91
|
+
};
|
|
92
|
+
this.#persisted.set(params.externalKey, entry);
|
|
93
|
+
await patchDaemonSessionBinding(entry);
|
|
94
|
+
}
|
|
95
|
+
updateOrigin(externalKey, origin) {
|
|
96
|
+
const runtime = this.#runtime.get(externalKey);
|
|
97
|
+
if (runtime) {
|
|
98
|
+
runtime.origin = origin;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
originForAcpSession(acpSessionId) {
|
|
102
|
+
const key = this.#reverse.get(acpSessionId);
|
|
103
|
+
if (!key) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return this.#runtime.get(key)?.origin ?? null;
|
|
107
|
+
}
|
|
108
|
+
externalKeyForAcpSession(acpSessionId) {
|
|
109
|
+
return this.#reverse.get(acpSessionId);
|
|
110
|
+
}
|
|
111
|
+
/** Marks a session busy for the duration of one `session/prompt` call, cancelling any armed idle timer. */
|
|
112
|
+
markBusy(externalKey) {
|
|
113
|
+
const runtime = this.#runtime.get(externalKey);
|
|
114
|
+
if (!runtime) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
runtime.busy = true;
|
|
118
|
+
if (runtime.idleTimer) {
|
|
119
|
+
clearTimeout(runtime.idleTimer);
|
|
120
|
+
runtime.idleTimer = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Marks a session non-busy after its turn settles. When more work for the
|
|
125
|
+
* same key is already queued, the caller keeps its slot untouched. When
|
|
126
|
+
* pool waiters exist, the session closes immediately instead of arming the
|
|
127
|
+
* ordinary idle timer.
|
|
128
|
+
*/
|
|
129
|
+
markIdle(externalKey, hasQueuedWork) {
|
|
130
|
+
const runtime = this.#runtime.get(externalKey);
|
|
131
|
+
if (!runtime || runtime.closing) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
runtime.busy = false;
|
|
135
|
+
runtime.lastActiveAt = Date.now();
|
|
136
|
+
if (hasQueuedWork) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.#waiters.length > 0) {
|
|
140
|
+
void this.#closeRuntime(externalKey);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (this.options.idleTimeoutMs > 0) {
|
|
144
|
+
runtime.idleTimer = setTimeout(() => {
|
|
145
|
+
runtime.idleTimer = null;
|
|
146
|
+
if (!runtime.busy && !runtime.closing) {
|
|
147
|
+
void this.#closeRuntime(externalKey);
|
|
148
|
+
}
|
|
149
|
+
}, this.options.idleTimeoutMs);
|
|
150
|
+
runtime.idleTimer.unref?.();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
#pickIdleLru() {
|
|
154
|
+
let best;
|
|
155
|
+
for (const runtime of this.#runtime.values()) {
|
|
156
|
+
if (runtime.busy || runtime.closing) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!best || runtime.lastActiveAt < best.lastActiveAt) {
|
|
160
|
+
best = runtime;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return best?.externalKey;
|
|
164
|
+
}
|
|
165
|
+
async #closeRuntime(externalKey) {
|
|
166
|
+
const runtime = this.#runtime.get(externalKey);
|
|
167
|
+
if (!runtime || runtime.closing) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
runtime.closing = true;
|
|
171
|
+
if (runtime.idleTimer) {
|
|
172
|
+
clearTimeout(runtime.idleTimer);
|
|
173
|
+
runtime.idleTimer = null;
|
|
174
|
+
}
|
|
175
|
+
await this.options.onClose(externalKey, runtime.acpSessionId);
|
|
176
|
+
this.#runtime.delete(externalKey);
|
|
177
|
+
this.#reverse.delete(runtime.acpSessionId);
|
|
178
|
+
this.#activeKeys.delete(externalKey);
|
|
179
|
+
const waiter = this.#waiters.shift();
|
|
180
|
+
if (waiter) {
|
|
181
|
+
this.#activeKeys.add(waiter.key);
|
|
182
|
+
waiter.resolve();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Rejects queued waiters, closes every active session, and stops granting new slots. */
|
|
186
|
+
async shutdown() {
|
|
187
|
+
this.#shuttingDown = true;
|
|
188
|
+
const pending = this.#waiters.splice(0, this.#waiters.length);
|
|
189
|
+
for (const waiter of pending) {
|
|
190
|
+
waiter.resolve();
|
|
191
|
+
}
|
|
192
|
+
await Promise.all([...this.#runtime.keys()].map((key) => this.#closeRuntime(key)));
|
|
193
|
+
}
|
|
194
|
+
get isShuttingDown() {
|
|
195
|
+
return this.#shuttingDown;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# 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>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { sessionsPath } from "../core/utils/paths.js";
|
|
4
|
+
const bindingsPath = () => join(sessionsPath(), "daemon", "bindings.jsonl");
|
|
5
|
+
function isCompleteBinding(entry) {
|
|
6
|
+
return (typeof entry.externalKey === "string" &&
|
|
7
|
+
typeof entry.acpSessionId === "string" &&
|
|
8
|
+
typeof entry.cwd === "string" &&
|
|
9
|
+
typeof entry.createdAt === "string" &&
|
|
10
|
+
typeof entry.updatedAt === "string");
|
|
11
|
+
}
|
|
12
|
+
/** Reads and folds the append-only bindings log; unparseable (torn) lines are skipped. */
|
|
13
|
+
export async function readDaemonSessionBindings() {
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = await readFile(bindingsPath(), "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return new Map();
|
|
20
|
+
}
|
|
21
|
+
const folded = new Map();
|
|
22
|
+
for (const line of raw.split("\n")) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
let record;
|
|
28
|
+
try {
|
|
29
|
+
record = JSON.parse(trimmed);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!record || typeof record.externalKey !== "string") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (record.deleted) {
|
|
38
|
+
folded.delete(record.externalKey);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
folded.set(record.externalKey, {
|
|
42
|
+
...folded.get(record.externalKey),
|
|
43
|
+
...record,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const entries = new Map();
|
|
47
|
+
for (const [key, entry] of folded) {
|
|
48
|
+
if (isCompleteBinding(entry)) {
|
|
49
|
+
entries.set(key, entry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
54
|
+
/** Appends a full or partial binding patch; `updatedAt` is bumped unless the patch sets it. */
|
|
55
|
+
export async function patchDaemonSessionBinding(patch) {
|
|
56
|
+
const path = bindingsPath();
|
|
57
|
+
await mkdir(dirname(path), { recursive: true });
|
|
58
|
+
await appendFile(path, `${JSON.stringify({
|
|
59
|
+
...patch,
|
|
60
|
+
updatedAt: patch.updatedAt ?? new Date().toISOString(),
|
|
61
|
+
})}\n`, "utf8");
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/daemon/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsBtD,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAE5E,SAAS,iBAAiB,CACxB,KAAoC;IAEpC,OAAO,CACL,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;QACrC,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;QACtC,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;QAC7B,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ;QACnC,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CACpC,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAG7C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyC,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QACD,IAAI,MAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YACtD,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE;YAC7B,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;YACjC,GAAG,MAAM;SACV,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgC,CAAC;IACxD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAEC;IAED,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,UAAU,CACd,IAAI,EACJ,GAAG,IAAI,CAAC,SAAS,CAAC;QAChB,GAAG,KAAK;QACR,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvD,CAAC,IAAI,EACN,MAAM,CACP,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hoomanjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.0",
|
|
4
4
|
"description": "Full-stack open-source agentic ecosystem — local-first CLI, VS Code, ACP, daemon channels, and Design mode. BYOK, custom inference endpoints, llama.cpp & MLX.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Vaibhav Pandey",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Manager as McpManager } from "../core/mcp/index.js";
|
|
2
|
-
import type { AskUserBackend } from "../core/tools/ask-user.js";
|
|
3
|
-
/**
|
|
4
|
-
* `ask_user` backend for channel-driven daemon jobs: relays the question to
|
|
5
|
-
* the originating MCP server over the `hooman/channel/ask` capability (the
|
|
6
|
-
* question-flavoured sibling of `hooman/channel/permission`), so the server
|
|
7
|
-
* can surface it wherever the human actually is — a Slack thread, a Telegram
|
|
8
|
-
* chat, etc. — and post the answer back.
|
|
9
|
-
*
|
|
10
|
-
* Jobs without a channel origin, and servers that don't advertise the
|
|
11
|
-
* capability, report "unavailable" so the model proceeds on its own
|
|
12
|
-
* judgement, matching daemon behaviour before this backend existed.
|
|
13
|
-
*/
|
|
14
|
-
export declare function createDaemonAskUserBackend(manager: McpManager, agent: {
|
|
15
|
-
appState: {
|
|
16
|
-
get(key: string): unknown;
|
|
17
|
-
};
|
|
18
|
-
}): AskUserBackend;
|
package/dist/daemon/questions.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { readChannelOrigin } from "../core/approvals/channel-ask.js";
|
|
2
|
-
/**
|
|
3
|
-
* `ask_user` backend for channel-driven daemon jobs: relays the question to
|
|
4
|
-
* the originating MCP server over the `hooman/channel/ask` capability (the
|
|
5
|
-
* question-flavoured sibling of `hooman/channel/permission`), so the server
|
|
6
|
-
* can surface it wherever the human actually is — a Slack thread, a Telegram
|
|
7
|
-
* chat, etc. — and post the answer back.
|
|
8
|
-
*
|
|
9
|
-
* Jobs without a channel origin, and servers that don't advertise the
|
|
10
|
-
* capability, report "unavailable" so the model proceeds on its own
|
|
11
|
-
* judgement, matching daemon behaviour before this backend existed.
|
|
12
|
-
*/
|
|
13
|
-
export function createDaemonAskUserBackend(manager, agent) {
|
|
14
|
-
return {
|
|
15
|
-
ask: async (request) => {
|
|
16
|
-
const origin = readChannelOrigin(agent);
|
|
17
|
-
if (!origin?.server) {
|
|
18
|
-
return { kind: "unavailable" };
|
|
19
|
-
}
|
|
20
|
-
const supported = await manager
|
|
21
|
-
.supportsChannelAsk(origin.server)
|
|
22
|
-
.catch(() => false);
|
|
23
|
-
if (!supported) {
|
|
24
|
-
return { kind: "unavailable" };
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
return await manager.requestChannelAsk(origin.server, {
|
|
28
|
-
requestId: crypto.randomUUID(),
|
|
29
|
-
question: request.question,
|
|
30
|
-
options: request.options,
|
|
31
|
-
source: origin.source,
|
|
32
|
-
user: origin.user,
|
|
33
|
-
session: origin.session,
|
|
34
|
-
thread: origin.thread,
|
|
35
|
-
}, { signal: request.signal });
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Relay failed or timed out; let the model proceed on its own.
|
|
39
|
-
return { kind: "unavailable" };
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=questions.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"questions.js","sourceRoot":"","sources":["../../src/daemon/questions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAIrE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAmB,EACnB,KAAkD;IAElD,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACrB,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;gBACpB,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;YACjC,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,OAAO;iBAC5B,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC;iBACjC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YACtB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;YACjC,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,MAAM,OAAO,CAAC,iBAAiB,CACpC,MAAM,CAAC,MAAM,EACb;oBACE,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE;oBAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB,EACD,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAC3B,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,+DAA+D;gBAC/D,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|