@claudexor/daemon 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joi-lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @claudexor/daemon
2
+
3
+ Internal package of [Claudexor](https://github.com/razzant/claudexor) — Optional local daemon: Unix-socket JSON-RPC, token auth, task queue. Runner injected (no second scheduler).
4
+
5
+ Published as part of the Claudexor toolchain; it follows the monorepo's
6
+ lockstep version and has no separate semver contract. Use the `claudexor`
7
+ CLI (or `@claudexor/cli`) as the supported entry point.
@@ -0,0 +1,40 @@
1
+ /** Thin JSON-RPC client for the daemon over a Unix socket. */
2
+ export declare class DaemonClient {
3
+ private readonly socketPath;
4
+ private readonly token;
5
+ constructor(socketPath: string, token: string);
6
+ call<T = unknown>(method: string, params?: unknown): Promise<T>;
7
+ health(): Promise<unknown>;
8
+ enqueue(params: unknown): Promise<{
9
+ id: string;
10
+ state: string;
11
+ }>;
12
+ status(id: string): Promise<{
13
+ id: string;
14
+ state: string;
15
+ params?: unknown;
16
+ runId?: string;
17
+ taskId?: string;
18
+ runDir?: string;
19
+ result?: unknown;
20
+ error?: string;
21
+ createdAt?: string;
22
+ startedAt?: string;
23
+ finishedAt?: string;
24
+ }>;
25
+ list(): Promise<{
26
+ id: string;
27
+ state: string;
28
+ params?: unknown;
29
+ runId?: string;
30
+ taskId?: string;
31
+ runDir?: string;
32
+ error?: string;
33
+ createdAt?: string;
34
+ startedAt?: string;
35
+ finishedAt?: string;
36
+ }[]>;
37
+ cancel(id: string): Promise<unknown>;
38
+ shutdown(): Promise<unknown>;
39
+ }
40
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,8DAA8D;AAC9D,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM;IAGhC,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAwC/D,MAAM;IAGN,OAAO,CAAC,MAAM,EAAE,OAAO;YACE,MAAM;eAAS,MAAM;;IAE9C,MAAM,CAAC,EAAE,EAAE,MAAM;YAET,MAAM;eACH,MAAM;iBACJ,OAAO;gBACR,MAAM;iBACL,MAAM;iBACN,MAAM;iBACN,OAAO;gBACR,MAAM;oBACF,MAAM;oBACN,MAAM;qBACL,MAAM;;IAMvB,IAAI;YAGM,MAAM;eACH,MAAM;iBACJ,OAAO;gBACR,MAAM;iBACL,MAAM;iBACN,MAAM;gBACP,MAAM;oBACF,MAAM;oBACN,MAAM;qBACL,MAAM;;IAIzB,MAAM,CAAC,EAAE,EAAE,MAAM;IAGjB,QAAQ;CAGT"}
package/dist/client.js ADDED
@@ -0,0 +1,74 @@
1
+ import { connect } from "node:net";
2
+ import { createInterface } from "node:readline";
3
+ /** Thin JSON-RPC client for the daemon over a Unix socket. */
4
+ export class DaemonClient {
5
+ socketPath;
6
+ token;
7
+ constructor(socketPath, token) {
8
+ this.socketPath = socketPath;
9
+ this.token = token;
10
+ }
11
+ call(method, params) {
12
+ return new Promise((resolve, reject) => {
13
+ const sock = connect(this.socketPath);
14
+ const id = Math.floor(Math.random() * 1e9);
15
+ let settled = false;
16
+ let rl;
17
+ const finish = (fn) => {
18
+ if (settled)
19
+ return;
20
+ settled = true;
21
+ clearTimeout(timer);
22
+ rl?.close();
23
+ sock.destroy();
24
+ fn();
25
+ };
26
+ // A socket that accepts but never replies must not hang the caller
27
+ // (`daemon status`) forever — fail loudly after a bounded wait.
28
+ const timer = setTimeout(() => finish(() => reject(new Error(`daemon RPC timeout (${method})`))), 10_000);
29
+ timer.unref?.();
30
+ // Attach the error handler first so connect failures (ENOENT/ECONNREFUSED)
31
+ // never become an unhandled 'error' event.
32
+ sock.on("error", (err) => finish(() => reject(err)));
33
+ sock.on("close", () => finish(() => reject(new Error("daemon connection closed"))));
34
+ rl = createInterface({ input: sock });
35
+ rl.on("error", (err) => finish(() => reject(err))); // readline re-emits input 'error'
36
+ sock.on("connect", () => {
37
+ sock.write(JSON.stringify({ id, method, params, token: this.token }) + "\n");
38
+ });
39
+ rl.on("line", (line) => {
40
+ try {
41
+ const msg = JSON.parse(line);
42
+ if (msg.id !== id)
43
+ return;
44
+ if (msg.error)
45
+ finish(() => reject(new Error(msg.error.message)));
46
+ else
47
+ finish(() => resolve(msg.result));
48
+ }
49
+ catch {
50
+ /* ignore */
51
+ }
52
+ });
53
+ });
54
+ }
55
+ health() {
56
+ return this.call("claudexor.health");
57
+ }
58
+ enqueue(params) {
59
+ return this.call("claudexor.enqueue", params);
60
+ }
61
+ status(id) {
62
+ return this.call("claudexor.status", { id });
63
+ }
64
+ list() {
65
+ return this.call("claudexor.list");
66
+ }
67
+ cancel(id) {
68
+ return this.call("claudexor.cancel", { id });
69
+ }
70
+ shutdown() {
71
+ return this.call("claudexor.shutdown");
72
+ }
73
+ }
74
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,8DAA8D;AAC9D,MAAM,OAAO,YAAY;IAEJ;IACA;IAFnB,YACmB,UAAkB,EAClB,KAAa;QADb,eAAU,GAAV,UAAU,CAAQ;QAClB,UAAK,GAAL,KAAK,CAAQ;IAC7B,CAAC;IAEJ,IAAI,CAAc,MAAc,EAAE,MAAgB;QAChD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,MAAM,IAAI,GAAW,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;YAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,EAAkD,CAAC;YACvD,MAAM,MAAM,GAAG,CAAC,EAAc,EAAE,EAAE;gBAChC,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,EAAE,EAAE,KAAK,EAAE,CAAC;gBACZ,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,EAAE,EAAE,CAAC;YACP,CAAC,CAAC;YACF,mEAAmE;YACnE,gEAAgE;YAChE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAC1G,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;YAChB,2EAA2E;YAC3E,2CAA2C;YAC3C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,kCAAkC;YACtF,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAC/E,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACrB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC7B,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE;wBAAE,OAAO;oBAC1B,IAAI,GAAG,CAAC,KAAK;wBAAE,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;;wBAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAW,CAAC,CAAC,CAAC;gBAC9C,CAAC;gBAAC,MAAM,CAAC;oBACP,YAAY;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,CAAC,MAAe;QACrB,OAAO,IAAI,CAAC,IAAI,CAAgC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAad,kBAAkB,EAClB,EAAE,EAAE,EAAE,CACP,CAAC;IACJ,CAAC;IACD,IAAI;QACF,OAAO,IAAI,CAAC,IAAI,CAad,gBAAgB,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import type { RunEvent } from "@claudexor/schema";
2
+ export type RunEventListener = (event: RunEvent) => void;
3
+ /**
4
+ * In-process run-event bus: the daemon's runner publishes every RunEvent the
5
+ * orchestrator emits (mirroring events.jsonl), and live observers (control-api
6
+ * SSE) subscribe for push delivery. The FILE stays the canonical log and the
7
+ * replay source; this bus only removes tail-polling latency. A throwing
8
+ * listener must never break a run.
9
+ */
10
+ export declare class RunEventBus {
11
+ private readonly listeners;
12
+ publish(event: RunEvent): void;
13
+ subscribe(listener: RunEventListener): () => void;
14
+ }
15
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEzD;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAU9B,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;CAMlD"}
package/dist/events.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * In-process run-event bus: the daemon's runner publishes every RunEvent the
3
+ * orchestrator emits (mirroring events.jsonl), and live observers (control-api
4
+ * SSE) subscribe for push delivery. The FILE stays the canonical log and the
5
+ * replay source; this bus only removes tail-polling latency. A throwing
6
+ * listener must never break a run.
7
+ */
8
+ export class RunEventBus {
9
+ listeners = new Set();
10
+ publish(event) {
11
+ for (const listener of this.listeners) {
12
+ try {
13
+ listener(event);
14
+ }
15
+ catch {
16
+ /* observer errors never break the run */
17
+ }
18
+ }
19
+ }
20
+ subscribe(listener) {
21
+ this.listeners.add(listener);
22
+ return () => {
23
+ this.listeners.delete(listener);
24
+ };
25
+ }
26
+ }
27
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,OAAO,WAAW;IACL,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEzD,OAAO,CAAC,KAAe;QACrB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,yCAAyC;YAC3C,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,CAAC,QAA0B;QAClC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export * from "./token.js";
2
+ export * from "./server.js";
3
+ export * from "./client.js";
4
+ export * from "./events.js";
5
+ export * from "./interactions.js";
6
+ export * from "./threads.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./token.js";
2
+ export * from "./server.js";
3
+ export * from "./client.js";
4
+ export * from "./events.js";
5
+ export * from "./interactions.js";
6
+ export * from "./threads.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { ControlPendingInteraction, InteractionAnswerSet, InteractionRequest } from "@claudexor/schema";
2
+ /** Structural twin of the orchestrator's PendingInteractionContext. */
3
+ export interface InteractionContext {
4
+ runId: string;
5
+ taskId: string;
6
+ attemptId: string;
7
+ harnessId: string;
8
+ request: InteractionRequest;
9
+ requestedAt: string;
10
+ timeoutAt: string;
11
+ }
12
+ export type InteractionAnswerStatus = "delivered" | "not_found" | "already_resolved" | "rejected";
13
+ /**
14
+ * Daemon-side registry of questions waiting for the user (waiting_on_user).
15
+ *
16
+ * The orchestrator's RunInput.onInteraction is wired to `register()`, which
17
+ * parks the attempt's promise here; control-api lists pending entries for the
18
+ * UI and delivers answers via `answer()`. The orchestrator owns the timeout —
19
+ * entries self-expire here only as hygiene so a timed-out question cannot be
20
+ * answered into a session that already moved on.
21
+ */
22
+ export declare class InteractionRegistry {
23
+ private readonly pending;
24
+ /**
25
+ * Entries are keyed by (runId, interactionId): interaction ids originate
26
+ * from the harness's native request ids, which only need to be unique
27
+ * within one session — two concurrent runs may legally collide.
28
+ */
29
+ private key;
30
+ register(ctx: InteractionContext): Promise<InteractionAnswerSet | null>;
31
+ answer(runId: string, interactionId: string, rawAnswers: unknown): {
32
+ status: InteractionAnswerStatus;
33
+ message?: string;
34
+ };
35
+ /** Resolve-and-drop every pending question of a run that reached a terminal
36
+ * (cancel/failed/succeeded): a dead run must not advertise waiting_on_user
37
+ * for up to the interaction timeout. Resolving null mirrors the
38
+ * orchestrator's own decline path. */
39
+ dropForRun(runId: string): void;
40
+ pendingForRun(runId: string): ControlPendingInteraction[];
41
+ /** Drop expired entries (the orchestrator already declined them). */
42
+ private prune;
43
+ }
44
+ //# sourceMappingURL=interactions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG7G,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,WAAW,GAAG,kBAAkB,GAAG,UAAU,CAAC;AAElG;;;;;;;;GAQG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAE3D;;;;OAIG;IACH,OAAO,CAAC,GAAG;IAIX,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAWvE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG;QAAE,MAAM,EAAE,uBAAuB,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAWxH;;;0CAGsC;IACtC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAS/B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,yBAAyB,EAAE;IAgBzD,qEAAqE;IACrE,OAAO,CAAC,KAAK;CASd"}
@@ -0,0 +1,81 @@
1
+ import { InteractionAnswerSet as InteractionAnswerSetSchema } from "@claudexor/schema";
2
+ /**
3
+ * Daemon-side registry of questions waiting for the user (waiting_on_user).
4
+ *
5
+ * The orchestrator's RunInput.onInteraction is wired to `register()`, which
6
+ * parks the attempt's promise here; control-api lists pending entries for the
7
+ * UI and delivers answers via `answer()`. The orchestrator owns the timeout —
8
+ * entries self-expire here only as hygiene so a timed-out question cannot be
9
+ * answered into a session that already moved on.
10
+ */
11
+ export class InteractionRegistry {
12
+ pending = new Map();
13
+ /**
14
+ * Entries are keyed by (runId, interactionId): interaction ids originate
15
+ * from the harness's native request ids, which only need to be unique
16
+ * within one session — two concurrent runs may legally collide.
17
+ */
18
+ key(runId, interactionId) {
19
+ return `${runId}\u0000${interactionId}`;
20
+ }
21
+ register(ctx) {
22
+ this.prune();
23
+ return new Promise((resolve) => {
24
+ this.pending.set(this.key(ctx.runId, ctx.request.interaction_id), {
25
+ ctx,
26
+ resolve,
27
+ expiresAtMs: Date.parse(ctx.timeoutAt) || Date.now() + 900_000,
28
+ });
29
+ });
30
+ }
31
+ answer(runId, interactionId, rawAnswers) {
32
+ this.prune();
33
+ const entry = this.pending.get(this.key(runId, interactionId));
34
+ if (!entry)
35
+ return { status: "not_found", message: `no pending interaction '${interactionId}' for run '${runId}'` };
36
+ const parsed = InteractionAnswerSetSchema.safeParse(rawAnswers);
37
+ if (!parsed.success)
38
+ return { status: "rejected", message: parsed.error.issues[0]?.message ?? "invalid answers" };
39
+ this.pending.delete(this.key(runId, interactionId));
40
+ entry.resolve(parsed.data);
41
+ return { status: "delivered" };
42
+ }
43
+ /** Resolve-and-drop every pending question of a run that reached a terminal
44
+ * (cancel/failed/succeeded): a dead run must not advertise waiting_on_user
45
+ * for up to the interaction timeout. Resolving null mirrors the
46
+ * orchestrator's own decline path. */
47
+ dropForRun(runId) {
48
+ for (const [id, entry] of this.pending) {
49
+ if (entry.ctx.runId === runId) {
50
+ this.pending.delete(id);
51
+ entry.resolve(null);
52
+ }
53
+ }
54
+ }
55
+ pendingForRun(runId) {
56
+ this.prune();
57
+ return [...this.pending.values()]
58
+ .filter((e) => e.ctx.runId === runId)
59
+ .map((e) => ({
60
+ interactionId: e.ctx.request.interaction_id,
61
+ runId: e.ctx.runId,
62
+ attemptId: e.ctx.attemptId,
63
+ harnessId: e.ctx.harnessId,
64
+ sourceTool: e.ctx.request.source_tool,
65
+ questions: e.ctx.request.questions,
66
+ requestedAt: e.ctx.requestedAt,
67
+ timeoutAt: e.ctx.timeoutAt,
68
+ }));
69
+ }
70
+ /** Drop expired entries (the orchestrator already declined them). */
71
+ prune() {
72
+ const now = Date.now();
73
+ for (const [id, entry] of this.pending) {
74
+ if (entry.expiresAtMs <= now) {
75
+ this.pending.delete(id);
76
+ entry.resolve(null);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=interactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactions.js","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /** Context the daemon supplies to the runner so a job can be observed and cancelled. */
2
+ export interface RunContext {
3
+ signal: AbortSignal;
4
+ /** Called by the runner once the run id/dir are known (lets a client tail events.jsonl). */
5
+ onRunStart: (info: {
6
+ runId: string;
7
+ taskId: string;
8
+ runDir: string;
9
+ }) => void;
10
+ }
11
+ export type RunnerFn = (params: unknown, ctx: RunContext) => Promise<unknown>;
12
+ export interface DaemonOptions {
13
+ socketPath: string;
14
+ token: string;
15
+ runner: RunnerFn;
16
+ /** Max concurrently-running jobs (parallel projects/runs). Default 4. */
17
+ maxConcurrent?: number;
18
+ /** Optional JSON file to persist the job registry across restarts (durable run list). */
19
+ persistPath?: string;
20
+ /** Max retained terminal jobs (older ones are pruned to bound memory/disk). Default 500. */
21
+ maxHistory?: number;
22
+ /** Called when a job reaches a terminal state (any path) with its runId —
23
+ * used to drop pending interactions so a dead run never advertises
24
+ * waiting_on_user. */
25
+ onRunTerminal?: (runId: string) => void;
26
+ /** Called when a job that carried a pre-created thread turn (params.turnId)
27
+ * settles failure-shaped WITHOUT ever binding a run — i.e. the refusal
28
+ * happened before the run materialized (trust gate, preflight validation).
29
+ * The observer persists the reason on the turn so it is never a silent
30
+ * orphan bubble. `code` is the typed throw's machine code (null if none). */
31
+ onTurnEnqueueFailed?: (turnId: string, error: string, code: string | null) => void;
32
+ }
33
+ export declare const JOB_STATES: readonly ["queued", "running", "blocked", "succeeded", "no_op", "ungated", "review_not_run", "failed", "cancelled", "interrupted", "exhausted", "not_converged", "stuck_no_progress"];
34
+ export type JobState = (typeof JOB_STATES)[number];
35
+ export interface JobRecord {
36
+ id: string;
37
+ state: JobState;
38
+ params: unknown;
39
+ result?: unknown;
40
+ error?: string;
41
+ /** Machine-readable code carried by a typed throw (e.g. the trust gate's
42
+ * trust_full_access_required) — lets surfaces key remedies on the CODE,
43
+ * never on substring-matching the human message. */
44
+ errorCode?: string;
45
+ createdAt: string;
46
+ /** Surfaced as soon as the run starts so a client can tail .claudexor/runs/<runId>/events.jsonl. */
47
+ runId?: string;
48
+ taskId?: string;
49
+ runDir?: string;
50
+ startedAt?: string;
51
+ finishedAt?: string;
52
+ }
53
+ /**
54
+ * Local daemon: Unix-socket JSON-RPC with token auth + a bounded-concurrency
55
+ * worker pool (up to maxConcurrent jobs in parallel) backed by an optional
56
+ * durable, atomically-written job registry. It does NOT contain a second
57
+ * scheduler — it calls the injected runner (the same Orchestrator the CLI uses).
58
+ */
59
+ export declare class DaemonServer {
60
+ private readonly opts;
61
+ private server?;
62
+ private readonly queue;
63
+ private readonly records;
64
+ private readonly cancelled;
65
+ private readonly controllers;
66
+ private active;
67
+ private readonly startedAt;
68
+ private onClosed?;
69
+ constructor(opts: DaemonOptions);
70
+ start(): Promise<void>;
71
+ stop(): Promise<void>;
72
+ /** Resolves when the daemon is shut down via RPC. */
73
+ waitForShutdown(): Promise<void>;
74
+ private onConnection;
75
+ private send;
76
+ private handle;
77
+ private dispatch;
78
+ private get maxConcurrent();
79
+ /**
80
+ * Best-effort durable persistence of the job registry. Writes atomically
81
+ * (temp file + rename) so a crash mid-write cannot corrupt/drop the registry.
82
+ * The raw run `result` is intentionally NOT persisted: canonical output lives
83
+ * in .claudexor/runs (redacted), and result.summary can contain raw model text —
84
+ * keeping it out of jobs.json upholds the redaction-at-persistence invariant.
85
+ */
86
+ private persist;
87
+ /** Bound memory/disk: prune the oldest terminal jobs beyond maxHistory.
88
+ * `blocked` runs are NEVER pruned — they are the needs-human inbox awaiting an
89
+ * operator decision; dropping one would silently lose a pending action. */
90
+ private pruneHistory;
91
+ /** Reload the registry on startup; a fresh process cannot resume in-memory runs. */
92
+ private load;
93
+ /** Preserve the raw bytes of a damaged registry and report the loss loudly. */
94
+ private backupCorruptRegistry;
95
+ private threadIdOf;
96
+ /**
97
+ * Schedule queued jobs up to the concurrency limit (non-blocking).
98
+ *
99
+ * One active run per thread: a thread is a linear conversation and an in-place
100
+ * turn mutates the live tree, so two concurrent turns on the same thread would
101
+ * race the same files. We pick the first queued job whose thread is idle rather
102
+ * than always taking the head; thread-less jobs (CLI/MCP) keep running in
103
+ * parallel as before. drain() re-runs on every completion, so a thread's next
104
+ * turn starts as soon as its previous one settles.
105
+ */
106
+ private drain;
107
+ private runJob;
108
+ }
109
+ /** True when something is actively accepting connections on the socket path. */
110
+ /** Is a daemon already listening on this socket? Exported so the claudexord
111
+ * entrypoint can refuse BEFORE running crash GC — a second daemon must never
112
+ * reap the live daemon's children or sweep envelopes its jobs still own. */
113
+ export declare function socketAlive(socketPath: string): Promise<boolean>;
114
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}