codedeck 0.7.1 → 0.7.2
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/core/errors.js +2 -39
- package/dist/core/errors.js.map +1 -1
- package/dist/daemon/daemon.js +89 -124
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/drivers/antigravity/parser.js +7 -10
- package/dist/drivers/antigravity/parser.js.map +1 -1
- package/dist/git/repository.js +0 -20
- package/dist/git/repository.js.map +1 -1
- package/dist/git/worktree.js +0 -14
- package/dist/git/worktree.js.map +1 -1
- package/dist/store/database.js +0 -6
- package/dist/store/database.js.map +1 -1
- package/dist/store/sessions.js +62 -72
- package/dist/store/sessions.js.map +1 -1
- package/dist/utils/process.js +2 -35
- package/dist/utils/process.js.map +1 -1
- package/package.json +1 -1
package/dist/core/errors.js
CHANGED
|
@@ -8,55 +8,18 @@ export class RunAgentError extends Error {
|
|
|
8
8
|
this.name = this.constructor.name;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
export class AgentNotInstalledError extends RunAgentError {
|
|
12
|
-
constructor(agent) {
|
|
13
|
-
super(`Agent "${agent}" is not installed`, "AGENT_NOT_INSTALLED", { agent });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export class AgentAuthenticationRequiredError extends RunAgentError {
|
|
17
|
-
constructor(agent) {
|
|
18
|
-
super(`Agent "${agent}" requires authentication`, "AGENT_AUTH_REQUIRED", { agent });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export class AgentStartFailedError extends RunAgentError {
|
|
22
|
-
constructor(agent, cause) {
|
|
23
|
-
super(`Failed to start agent "${agent}"`, "AGENT_START_FAILED", { agent, cause });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
11
|
export class SessionNotFoundError extends RunAgentError {
|
|
27
12
|
constructor(id) {
|
|
28
13
|
super(`Session "${id}" not found`, "SESSION_NOT_FOUND", { id });
|
|
29
14
|
}
|
|
30
15
|
}
|
|
31
|
-
export class SessionNotRunningError extends RunAgentError {
|
|
32
|
-
constructor(id) {
|
|
33
|
-
super(`Session "${id}" is not running`, "SESSION_NOT_RUNNING", { id });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export class CapabilityNotSupportedError extends RunAgentError {
|
|
37
|
-
constructor(agent, capability) {
|
|
38
|
-
super(`Agent "${agent}" does not support "${capability}"`, "CAPABILITY_NOT_SUPPORTED", { agent, capability });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export class RepositoryNotFoundError extends RunAgentError {
|
|
42
|
-
constructor(cwd) {
|
|
43
|
-
super(`No git repository found at "${cwd}"`, "REPOSITORY_NOT_FOUND", { cwd });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
16
|
export class WorktreeCreationFailedError extends RunAgentError {
|
|
47
17
|
constructor(cause) {
|
|
48
18
|
super("Failed to create git worktree", "WORKTREE_CREATION_FAILED", { cause });
|
|
49
19
|
}
|
|
50
20
|
}
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
super("CodeDeck daemon is unavailable", "DAEMON_UNAVAILABLE", { cause });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
export class ProtocolError extends RunAgentError {
|
|
57
|
-
constructor(message, cause) {
|
|
58
|
-
super(message, "PROTOCOL_ERROR", { cause });
|
|
59
|
-
}
|
|
21
|
+
export function errorMessage(e) {
|
|
22
|
+
return e instanceof Error ? e.message : String(e);
|
|
60
23
|
}
|
|
61
24
|
// Order matters: the most specific signatures first. "EPIPE" alone is LAST —
|
|
62
25
|
// it is a substring task output can legitimately contain (e.g. a test grepping
|
package/dist/core/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAGpB;IACA;IAHlB,YACE,OAAe,EACC,IAAY,EACZ,OAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAQ;QACZ,YAAO,GAAP,OAAO,CAAU;QAGjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon/daemon.js
CHANGED
|
@@ -21,7 +21,7 @@ import { readSessionProcessMetadata } from "../drivers/session-runtime.js";
|
|
|
21
21
|
import { loadConfig, resolveDefaultSandbox } from "../config/config.js";
|
|
22
22
|
import { invalidWebHostMessage, resolveWebHost } from "../config/web-host.js";
|
|
23
23
|
import { invalidWebPortMessage, resolveWebPort } from "../config/web-port.js";
|
|
24
|
-
import { classifyFailure, isStoreBusy, RunAgentError } from "../core/errors.js";
|
|
24
|
+
import { classifyFailure, errorMessage, isStoreBusy, RunAgentError } from "../core/errors.js";
|
|
25
25
|
import { parseRole } from "../core/roles.js";
|
|
26
26
|
import { getCachedOrDiscoverModels } from "../core/models.js";
|
|
27
27
|
import { aggregateRunUsage } from "../core/run-usage.js";
|
|
@@ -97,6 +97,7 @@ class Daemon {
|
|
|
97
97
|
nativeLinks;
|
|
98
98
|
registry = getRegistry();
|
|
99
99
|
server;
|
|
100
|
+
boundPaths;
|
|
100
101
|
subscribers = new Map(); // sessionId -> sockets
|
|
101
102
|
startTime = Date.now();
|
|
102
103
|
startupReconcilePromise = Promise.resolve();
|
|
@@ -261,7 +262,7 @@ class Daemon {
|
|
|
261
262
|
queueReleasedTranscriptCursorCleanup(sessionId) {
|
|
262
263
|
queueMicrotask(() => {
|
|
263
264
|
void this.cleanupReleasedTranscriptCursors(sessionId).catch((error) => {
|
|
264
|
-
const detail =
|
|
265
|
+
const detail = errorMessage(error);
|
|
265
266
|
try {
|
|
266
267
|
fs.appendFileSync(getPaths().daemonLog, `[${new Date().toISOString()}] usage cursor cleanup failed session=${sessionId}: ${detail}\n`);
|
|
267
268
|
}
|
|
@@ -315,7 +316,7 @@ class Daemon {
|
|
|
315
316
|
await this.handleRequest(req, socket);
|
|
316
317
|
}
|
|
317
318
|
catch (e) {
|
|
318
|
-
const res = { id: req.id, error: { code: "INTERNAL", message:
|
|
319
|
+
const res = { id: req.id, error: { code: "INTERNAL", message: errorMessage(e) } };
|
|
319
320
|
try {
|
|
320
321
|
socket.write(JSON.stringify(res) + "\n");
|
|
321
322
|
}
|
|
@@ -326,6 +327,10 @@ class Daemon {
|
|
|
326
327
|
this.server.listen(paths.daemonSock, () => resolve());
|
|
327
328
|
this.server.on("error", reject);
|
|
328
329
|
});
|
|
330
|
+
this.boundPaths = {
|
|
331
|
+
sock: paths.daemonSock,
|
|
332
|
+
pid: paths.daemonPid,
|
|
333
|
+
};
|
|
329
334
|
// Write pid file
|
|
330
335
|
try {
|
|
331
336
|
fs.writeFileSync(paths.daemonPid, String(process.pid), "utf-8");
|
|
@@ -554,7 +559,7 @@ class Daemon {
|
|
|
554
559
|
this.sessions.update(sessionId, { completedAt: heal.endedAt });
|
|
555
560
|
}
|
|
556
561
|
catch (error) {
|
|
557
|
-
appendDaemonLog(`heal timestamps for ${sessionId} failed: ${
|
|
562
|
+
appendDaemonLog(`heal timestamps for ${sessionId} failed: ${errorMessage(error)}`);
|
|
558
563
|
}
|
|
559
564
|
}
|
|
560
565
|
async reconcileOpenUsage(sessionId, nativeIds) {
|
|
@@ -605,7 +610,7 @@ class Daemon {
|
|
|
605
610
|
return true;
|
|
606
611
|
}
|
|
607
612
|
catch (error) {
|
|
608
|
-
const detail =
|
|
613
|
+
const detail = errorMessage(error);
|
|
609
614
|
try {
|
|
610
615
|
fs.appendFileSync(getPaths().daemonLog, `[${new Date().toISOString()}] usage reconcile failed session=${sessionId}: ${detail}\n`);
|
|
611
616
|
}
|
|
@@ -626,6 +631,23 @@ class Daemon {
|
|
|
626
631
|
releaseSessionId(sessionId) {
|
|
627
632
|
this.reservedSessionIds.delete(sessionId);
|
|
628
633
|
}
|
|
634
|
+
resolveRegisteredAgent(rawAgent) {
|
|
635
|
+
const agent = (normalizeAgentId(rawAgent) ?? rawAgent);
|
|
636
|
+
return { agent, registered: this.registry.has(agent) };
|
|
637
|
+
}
|
|
638
|
+
handleClaimRequest(requestedSessionId, operation, result, send) {
|
|
639
|
+
try {
|
|
640
|
+
const sessionId = this.sessions.get(requestedSessionId)?.id ?? requestedSessionId;
|
|
641
|
+
send(result(operation(sessionId)));
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
if (error instanceof RunAgentError) {
|
|
645
|
+
send({ error: { code: error.code, message: error.message, details: error.details } });
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
629
651
|
async handleRequest(req, socket) {
|
|
630
652
|
const { id, method, params } = req;
|
|
631
653
|
const send = (res) => {
|
|
@@ -634,11 +656,13 @@ class Daemon {
|
|
|
634
656
|
}
|
|
635
657
|
catch { }
|
|
636
658
|
};
|
|
659
|
+
const sendSessionNotFound = (sessionId) => send({ error: { code: "SESSION_NOT_FOUND", message: `Session ${sessionId} not found` } });
|
|
660
|
+
const sendShuttingDown = () => send({ error: { code: "SERVICE_UNAVAILABLE", message: "daemon is shutting down" } });
|
|
637
661
|
// Power-shutdown guard: while draining, refuse new work so a send/stop
|
|
638
662
|
// cannot race the interrupted persist. daemon.stop is the shutdown
|
|
639
663
|
// trigger itself, so it stays admitted (idempotent via handleShutdown).
|
|
640
664
|
if (this.shuttingDown && method !== "daemon.stop") {
|
|
641
|
-
|
|
665
|
+
sendShuttingDown();
|
|
642
666
|
return;
|
|
643
667
|
}
|
|
644
668
|
try {
|
|
@@ -653,8 +677,8 @@ class Daemon {
|
|
|
653
677
|
const cwdIn = p.cwd || process.cwd();
|
|
654
678
|
const cfg = loadConfig();
|
|
655
679
|
const rawAgent = p.agent || cfg.defaultAgent || "claude";
|
|
656
|
-
|
|
657
|
-
if (!
|
|
680
|
+
const { agent, registered } = this.resolveRegisteredAgent(rawAgent);
|
|
681
|
+
if (!registered) {
|
|
658
682
|
send({ error: { code: "AGENT_NOT_FOUND", message: `Unknown agent ${agent}` } });
|
|
659
683
|
return;
|
|
660
684
|
}
|
|
@@ -682,7 +706,7 @@ class Daemon {
|
|
|
682
706
|
}
|
|
683
707
|
if (this.shuttingDown) {
|
|
684
708
|
this.releaseSessionId(sessionId);
|
|
685
|
-
|
|
709
|
+
sendShuttingDown();
|
|
686
710
|
return;
|
|
687
711
|
}
|
|
688
712
|
if (gitInfo)
|
|
@@ -694,7 +718,7 @@ class Daemon {
|
|
|
694
718
|
const wt = await createWorktree({ repoRoot: gitInfo.root, sessionId, prompt, name: p.name });
|
|
695
719
|
if (this.shuttingDown) {
|
|
696
720
|
this.releaseSessionId(sessionId);
|
|
697
|
-
|
|
721
|
+
sendShuttingDown();
|
|
698
722
|
return;
|
|
699
723
|
}
|
|
700
724
|
worktree = wt.path;
|
|
@@ -704,7 +728,7 @@ class Daemon {
|
|
|
704
728
|
}
|
|
705
729
|
catch (e) {
|
|
706
730
|
this.releaseSessionId(sessionId);
|
|
707
|
-
send({ error: { code: "WORKTREE_FAILED", message:
|
|
731
|
+
send({ error: { code: "WORKTREE_FAILED", message: errorMessage(e) } });
|
|
708
732
|
return;
|
|
709
733
|
}
|
|
710
734
|
}
|
|
@@ -741,13 +765,12 @@ class Daemon {
|
|
|
741
765
|
this.releaseSessionId(sessionId);
|
|
742
766
|
}
|
|
743
767
|
// Start driver async (don't block response too long)
|
|
744
|
-
// But we need to start before returning? Return quickly then start
|
|
745
768
|
send({ result: { session } });
|
|
746
769
|
// Now start driver in background
|
|
747
770
|
this.startDriverForSession(sessionId, prompt, p.model).catch((e) => {
|
|
748
771
|
if (this.shuttingDown)
|
|
749
772
|
return;
|
|
750
|
-
const msg =
|
|
773
|
+
const msg = errorMessage(e);
|
|
751
774
|
const failure = classifyFailure(msg);
|
|
752
775
|
const failEv = {
|
|
753
776
|
type: "session.failed",
|
|
@@ -769,8 +792,8 @@ class Daemon {
|
|
|
769
792
|
const p = params;
|
|
770
793
|
const cwdIn = p.cwd || process.cwd();
|
|
771
794
|
const rawAgent = p.agent || "claude";
|
|
772
|
-
const agent = (
|
|
773
|
-
if (!
|
|
795
|
+
const { agent, registered } = this.resolveRegisteredAgent(rawAgent);
|
|
796
|
+
if (!registered) {
|
|
774
797
|
send({ error: { code: "AGENT_NOT_FOUND", message: `Unknown agent ${agent}` } });
|
|
775
798
|
return;
|
|
776
799
|
}
|
|
@@ -779,7 +802,7 @@ class Daemon {
|
|
|
779
802
|
let baseCommit = p.baseCommit;
|
|
780
803
|
const gitInfo = await getGitInfo(cwd);
|
|
781
804
|
if (this.shuttingDown) {
|
|
782
|
-
|
|
805
|
+
sendShuttingDown();
|
|
783
806
|
return;
|
|
784
807
|
}
|
|
785
808
|
const matchingSession = typeof p.resume === "string" && p.resume.length > 0
|
|
@@ -808,8 +831,7 @@ class Daemon {
|
|
|
808
831
|
agent,
|
|
809
832
|
nativeSessionId: p.resume,
|
|
810
833
|
};
|
|
811
|
-
this.
|
|
812
|
-
this.broadcast(revived.id, event);
|
|
834
|
+
this.emit(revived.id, event);
|
|
813
835
|
send({ result: { session: revived } });
|
|
814
836
|
break;
|
|
815
837
|
}
|
|
@@ -851,7 +873,7 @@ class Daemon {
|
|
|
851
873
|
const p = params;
|
|
852
874
|
const s = this.sessions.get(p.id);
|
|
853
875
|
if (!s) {
|
|
854
|
-
|
|
876
|
+
sendSessionNotFound(p.id);
|
|
855
877
|
return;
|
|
856
878
|
}
|
|
857
879
|
let pidStartTime = p.pidStartTime;
|
|
@@ -884,7 +906,7 @@ class Daemon {
|
|
|
884
906
|
}
|
|
885
907
|
const session = this.sessions.get(p.id);
|
|
886
908
|
if (!session) {
|
|
887
|
-
|
|
909
|
+
sendSessionNotFound(p.id);
|
|
888
910
|
return;
|
|
889
911
|
}
|
|
890
912
|
if (session.origin !== "open") {
|
|
@@ -901,7 +923,7 @@ class Daemon {
|
|
|
901
923
|
const p = params;
|
|
902
924
|
const s = this.sessions.get(p.id);
|
|
903
925
|
if (!s) {
|
|
904
|
-
|
|
926
|
+
sendSessionNotFound(p.id);
|
|
905
927
|
return;
|
|
906
928
|
}
|
|
907
929
|
// Release owns the terminal outcome: a queued message must not
|
|
@@ -953,8 +975,7 @@ class Daemon {
|
|
|
953
975
|
timestamp: new Date().toISOString(),
|
|
954
976
|
reason: "released",
|
|
955
977
|
};
|
|
956
|
-
this.
|
|
957
|
-
this.broadcast(s.id, ev);
|
|
978
|
+
this.emit(s.id, ev);
|
|
958
979
|
const updated = this.sessions.get(s.id);
|
|
959
980
|
send({ result: { session: updated } });
|
|
960
981
|
break;
|
|
@@ -964,7 +985,6 @@ class Daemon {
|
|
|
964
985
|
const all = p?.all;
|
|
965
986
|
const list = this.sessions.list(100, all).map(withLiveStatus);
|
|
966
987
|
const hidden = all ? 0 : this.sessions.countHiddenByWindow();
|
|
967
|
-
// Enrich with last event?
|
|
968
988
|
send({ result: { sessions: list, hidden } });
|
|
969
989
|
break;
|
|
970
990
|
}
|
|
@@ -972,7 +992,7 @@ class Daemon {
|
|
|
972
992
|
const p = params;
|
|
973
993
|
const stored = this.sessions.get(p.id);
|
|
974
994
|
if (!stored) {
|
|
975
|
-
|
|
995
|
+
sendSessionNotFound(p.id);
|
|
976
996
|
return;
|
|
977
997
|
}
|
|
978
998
|
const s = withLiveStatus(stored);
|
|
@@ -985,7 +1005,7 @@ class Daemon {
|
|
|
985
1005
|
const p = params;
|
|
986
1006
|
const s = this.sessions.get(p.id);
|
|
987
1007
|
if (!s) {
|
|
988
|
-
|
|
1008
|
+
sendSessionNotFound(p.id);
|
|
989
1009
|
return;
|
|
990
1010
|
}
|
|
991
1011
|
if (!p.name || !p.name.trim()) {
|
|
@@ -1006,7 +1026,7 @@ class Daemon {
|
|
|
1006
1026
|
const message = validated.message;
|
|
1007
1027
|
const s = this.sessions.get(p.id);
|
|
1008
1028
|
if (!s) {
|
|
1009
|
-
|
|
1029
|
+
sendSessionNotFound(p.id);
|
|
1010
1030
|
return;
|
|
1011
1031
|
}
|
|
1012
1032
|
if (s.origin === "open") {
|
|
@@ -1053,7 +1073,7 @@ class Daemon {
|
|
|
1053
1073
|
this.sessions.update(s.id, { pendingMessage: message, pendingAt });
|
|
1054
1074
|
}
|
|
1055
1075
|
catch (e) {
|
|
1056
|
-
send({ error: { code: "SEND_FAILED", message:
|
|
1076
|
+
send({ error: { code: "SEND_FAILED", message: errorMessage(e) } });
|
|
1057
1077
|
return;
|
|
1058
1078
|
}
|
|
1059
1079
|
const queuedEvent = {
|
|
@@ -1089,7 +1109,7 @@ class Daemon {
|
|
|
1089
1109
|
send({ result: { ok: true, queued: false } });
|
|
1090
1110
|
}
|
|
1091
1111
|
catch (e) {
|
|
1092
|
-
const errText =
|
|
1112
|
+
const errText = errorMessage(e);
|
|
1093
1113
|
const code = e instanceof Error && e.code === "SERVICE_UNAVAILABLE"
|
|
1094
1114
|
? "SERVICE_UNAVAILABLE"
|
|
1095
1115
|
: this.shuttingDown ? "SERVICE_UNAVAILABLE" : "SEND_FAILED";
|
|
@@ -1113,7 +1133,7 @@ class Daemon {
|
|
|
1113
1133
|
const p = params;
|
|
1114
1134
|
const s = this.sessions.get(p.id);
|
|
1115
1135
|
if (!s) {
|
|
1116
|
-
|
|
1136
|
+
sendSessionNotFound(p.id);
|
|
1117
1137
|
return;
|
|
1118
1138
|
}
|
|
1119
1139
|
// Stop cancels a queued message on every exit (explicit cancel),
|
|
@@ -1126,10 +1146,7 @@ class Daemon {
|
|
|
1126
1146
|
}
|
|
1127
1147
|
const handle = driver.getHandle?.(s.id);
|
|
1128
1148
|
const hasRuntime = handle !== undefined;
|
|
1129
|
-
const liveIdentity = s
|
|
1130
|
-
s.pidStartTime != null &&
|
|
1131
|
-
processAlive(s.pid) &&
|
|
1132
|
-
processStartTime(s.pid) === s.pidStartTime;
|
|
1149
|
+
const liveIdentity = livePidIdentity(s);
|
|
1133
1150
|
if (isTerminalStatus(s.status) && !hasRuntime && !liveIdentity) {
|
|
1134
1151
|
send({ error: { code: "SESSION_NOT_RUNNING", message: `Session ${s.id} is ${s.status}` } });
|
|
1135
1152
|
return;
|
|
@@ -1155,7 +1172,7 @@ class Daemon {
|
|
|
1155
1172
|
pidStartTime: s.pidStartTime,
|
|
1156
1173
|
});
|
|
1157
1174
|
if (this.shuttingDown) {
|
|
1158
|
-
|
|
1175
|
+
sendShuttingDown();
|
|
1159
1176
|
return;
|
|
1160
1177
|
}
|
|
1161
1178
|
const last = this.events.last(s.id);
|
|
@@ -1177,8 +1194,7 @@ class Daemon {
|
|
|
1177
1194
|
reason: "stopped",
|
|
1178
1195
|
exitCode: 130,
|
|
1179
1196
|
};
|
|
1180
|
-
this.
|
|
1181
|
-
this.broadcast(s.id, ev);
|
|
1197
|
+
this.emit(s.id, ev);
|
|
1182
1198
|
}
|
|
1183
1199
|
// A send may have persisted a slot after the entry clear (it saw a
|
|
1184
1200
|
// live turn before this stop took the lock): stop wins, clear again.
|
|
@@ -1187,14 +1203,14 @@ class Daemon {
|
|
|
1187
1203
|
}
|
|
1188
1204
|
catch (e) {
|
|
1189
1205
|
if (this.shuttingDown) {
|
|
1190
|
-
|
|
1206
|
+
sendShuttingDown();
|
|
1191
1207
|
return;
|
|
1192
1208
|
}
|
|
1193
1209
|
try {
|
|
1194
1210
|
this.sessions.setStatus(s.id, previousStatus);
|
|
1195
1211
|
}
|
|
1196
1212
|
catch { }
|
|
1197
|
-
send({ error: { code: "STOP_FAILED", message:
|
|
1213
|
+
send({ error: { code: "STOP_FAILED", message: errorMessage(e) } });
|
|
1198
1214
|
}
|
|
1199
1215
|
finally {
|
|
1200
1216
|
if (!this.shuttingDown)
|
|
@@ -1206,7 +1222,7 @@ class Daemon {
|
|
|
1206
1222
|
const p = params;
|
|
1207
1223
|
const s = this.sessions.get(p.id);
|
|
1208
1224
|
if (!s) {
|
|
1209
|
-
|
|
1225
|
+
sendSessionNotFound(p.id);
|
|
1210
1226
|
return;
|
|
1211
1227
|
}
|
|
1212
1228
|
if (p.follow) {
|
|
@@ -1230,7 +1246,7 @@ class Daemon {
|
|
|
1230
1246
|
const p = params;
|
|
1231
1247
|
const s = this.sessions.get(p.id);
|
|
1232
1248
|
if (!s) {
|
|
1233
|
-
|
|
1249
|
+
sendSessionNotFound(p.id);
|
|
1234
1250
|
return;
|
|
1235
1251
|
}
|
|
1236
1252
|
// A late subscriber must not wait forever for a broadcast that already
|
|
@@ -1250,23 +1266,14 @@ class Daemon {
|
|
|
1250
1266
|
catch { }
|
|
1251
1267
|
return;
|
|
1252
1268
|
}
|
|
1253
|
-
// Send buffered events first?
|
|
1254
|
-
// Then keep streaming
|
|
1255
1269
|
this.addSubscriber(s.id, socket);
|
|
1256
|
-
// Send ack? keep open
|
|
1257
|
-
// Optionally send done when session terminal? We'll handle via broadcast.
|
|
1258
|
-
// Don't send immediate response; just keep socket open for events
|
|
1259
|
-
// But we should send an initial ack to confirm subscription?
|
|
1260
|
-
// Use a separate message to indicate subscription started
|
|
1261
|
-
// Let's not send anything; client will just wait for events
|
|
1262
|
-
// Keep socket open
|
|
1263
1270
|
return;
|
|
1264
1271
|
}
|
|
1265
1272
|
case "session.diff": {
|
|
1266
1273
|
const p = params;
|
|
1267
1274
|
const s = this.sessions.get(p.id);
|
|
1268
1275
|
if (!s) {
|
|
1269
|
-
|
|
1276
|
+
sendSessionNotFound(p.id);
|
|
1270
1277
|
return;
|
|
1271
1278
|
}
|
|
1272
1279
|
const diff = await getDiff({ cwd: s.cwd, worktree: s.worktree, baseCommit: s.baseCommit, repository: s.repository });
|
|
@@ -1275,56 +1282,17 @@ class Daemon {
|
|
|
1275
1282
|
}
|
|
1276
1283
|
case "claims.add": {
|
|
1277
1284
|
const p = params;
|
|
1278
|
-
|
|
1279
|
-
const sessionId = this.sessions.get(p.sessionId)?.id ?? p.sessionId;
|
|
1280
|
-
const claim = this.claims.add(sessionId, p.pathGlob, p.reason);
|
|
1281
|
-
send({ result: { claim } });
|
|
1282
|
-
}
|
|
1283
|
-
catch (error) {
|
|
1284
|
-
if (error instanceof RunAgentError) {
|
|
1285
|
-
send({ error: { code: error.code, message: error.message, details: error.details } });
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
throw error;
|
|
1289
|
-
}
|
|
1285
|
+
this.handleClaimRequest(p.sessionId, (sessionId) => this.claims.add(sessionId, p.pathGlob, p.reason), (claim) => ({ result: { claim } }), send);
|
|
1290
1286
|
break;
|
|
1291
1287
|
}
|
|
1292
1288
|
case "claims.query": {
|
|
1293
1289
|
const p = params;
|
|
1294
|
-
|
|
1295
|
-
const sessionId = this.sessions.get(p.sessionId)?.id ?? p.sessionId;
|
|
1296
|
-
const claims = this.claims.query(sessionId, p.path);
|
|
1297
|
-
send({ result: { claims } });
|
|
1298
|
-
}
|
|
1299
|
-
catch (error) {
|
|
1300
|
-
if (error instanceof RunAgentError) {
|
|
1301
|
-
send({ error: { code: error.code, message: error.message, details: error.details } });
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
throw error;
|
|
1305
|
-
}
|
|
1290
|
+
this.handleClaimRequest(p.sessionId, (sessionId) => this.claims.query(sessionId, p.path), (claims) => ({ result: { claims } }), send);
|
|
1306
1291
|
break;
|
|
1307
1292
|
}
|
|
1308
1293
|
case "claims.release": {
|
|
1309
1294
|
const p = params;
|
|
1310
|
-
|
|
1311
|
-
const sessionId = this.sessions.get(p.sessionId)?.id ?? p.sessionId;
|
|
1312
|
-
const claim = this.claims.release(sessionId, p.claimId);
|
|
1313
|
-
send({ result: { claim } });
|
|
1314
|
-
}
|
|
1315
|
-
catch (error) {
|
|
1316
|
-
if (error instanceof RunAgentError) {
|
|
1317
|
-
send({ error: { code: error.code, message: error.message, details: error.details } });
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
throw error;
|
|
1321
|
-
}
|
|
1322
|
-
break;
|
|
1323
|
-
}
|
|
1324
|
-
case "daemon.status": {
|
|
1325
|
-
const paths = getPaths();
|
|
1326
|
-
let uptime = Date.now() - this.startTime;
|
|
1327
|
-
send({ result: { running: true, pid: process.pid, uptime, db: paths.db } });
|
|
1295
|
+
this.handleClaimRequest(p.sessionId, (sessionId) => this.claims.release(sessionId, p.claimId), (claim) => ({ result: { claim } }), send);
|
|
1328
1296
|
break;
|
|
1329
1297
|
}
|
|
1330
1298
|
case "doctor": {
|
|
@@ -1461,7 +1429,7 @@ class Daemon {
|
|
|
1461
1429
|
send({ result });
|
|
1462
1430
|
}
|
|
1463
1431
|
catch (error) {
|
|
1464
|
-
send({ error: { code: "USAGE_QUERY_ERROR", message:
|
|
1432
|
+
send({ error: { code: "USAGE_QUERY_ERROR", message: errorMessage(error) } });
|
|
1465
1433
|
}
|
|
1466
1434
|
break;
|
|
1467
1435
|
}
|
|
@@ -1475,7 +1443,7 @@ class Daemon {
|
|
|
1475
1443
|
send({ error: { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) } });
|
|
1476
1444
|
}
|
|
1477
1445
|
else {
|
|
1478
|
-
send({ error: { code: "WEB_START_FAILED", message:
|
|
1446
|
+
send({ error: { code: "WEB_START_FAILED", message: errorMessage(error) } });
|
|
1479
1447
|
}
|
|
1480
1448
|
}
|
|
1481
1449
|
break;
|
|
@@ -1531,6 +1499,10 @@ class Daemon {
|
|
|
1531
1499
|
}
|
|
1532
1500
|
}
|
|
1533
1501
|
}
|
|
1502
|
+
emit(sessionId, event) {
|
|
1503
|
+
this.events.append(sessionId, event);
|
|
1504
|
+
this.broadcast(sessionId, event);
|
|
1505
|
+
}
|
|
1534
1506
|
webHost() {
|
|
1535
1507
|
this.web ??= new WebSupervisor({ log: appendDaemonLog, spawnChild: this.spawnWebChild });
|
|
1536
1508
|
return this.web;
|
|
@@ -1549,7 +1521,7 @@ class Daemon {
|
|
|
1549
1521
|
appendDaemonLog(invalidWebHostMessage(invalidHost));
|
|
1550
1522
|
this.webHost()
|
|
1551
1523
|
.ensure({ preferredPort: port, preferredHost: host })
|
|
1552
|
-
.catch((error) => appendDaemonLog(`web autostart failed: ${
|
|
1524
|
+
.catch((error) => appendDaemonLog(`web autostart failed: ${errorMessage(error)}`));
|
|
1553
1525
|
}
|
|
1554
1526
|
async startDriverForSession(sessionId, prompt, model) {
|
|
1555
1527
|
if (this.shuttingDown)
|
|
@@ -1564,8 +1536,7 @@ class Daemon {
|
|
|
1564
1536
|
return;
|
|
1565
1537
|
this.sessions.update(sessionId, { status: "working" });
|
|
1566
1538
|
const turnEvent = { type: "turn.started", sessionId, timestamp: new Date().toISOString(), prompt };
|
|
1567
|
-
this.
|
|
1568
|
-
this.broadcast(sessionId, turnEvent);
|
|
1539
|
+
this.emit(sessionId, turnEvent);
|
|
1569
1540
|
const drvSession = await driver.start({
|
|
1570
1541
|
sessionId,
|
|
1571
1542
|
prompt,
|
|
@@ -1632,10 +1603,10 @@ class Daemon {
|
|
|
1632
1603
|
catch { }
|
|
1633
1604
|
}
|
|
1634
1605
|
/**
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1606
|
+
* Starts a resumed turn; caller MUST hold sessionLocks for the session.
|
|
1607
|
+
* Shared by session.send (immediate) and tryDispatch (queued) so both
|
|
1608
|
+
* paths emit the same frames in the same order.
|
|
1609
|
+
*/
|
|
1639
1610
|
async runResumeTurn(s, message) {
|
|
1640
1611
|
const driver = this.registry.get(s.agent);
|
|
1641
1612
|
if (this.shuttingDown) {
|
|
@@ -1650,8 +1621,7 @@ class Daemon {
|
|
|
1650
1621
|
timestamp: new Date().toISOString(),
|
|
1651
1622
|
prompt: message,
|
|
1652
1623
|
};
|
|
1653
|
-
this.
|
|
1654
|
-
this.broadcast(s.id, turnEvent);
|
|
1624
|
+
this.emit(s.id, turnEvent);
|
|
1655
1625
|
const drvSession = {
|
|
1656
1626
|
id: s.id,
|
|
1657
1627
|
runId: s.runId ?? undefined,
|
|
@@ -1756,7 +1726,7 @@ class Daemon {
|
|
|
1756
1726
|
return true;
|
|
1757
1727
|
}
|
|
1758
1728
|
catch (e) {
|
|
1759
|
-
const errText =
|
|
1729
|
+
const errText = errorMessage(e);
|
|
1760
1730
|
const unavailable = e?.code === "SERVICE_UNAVAILABLE" || this.shuttingDown;
|
|
1761
1731
|
if (!unavailable) {
|
|
1762
1732
|
const failure = classifyFailure(errText);
|
|
@@ -1859,7 +1829,7 @@ class Daemon {
|
|
|
1859
1829
|
return;
|
|
1860
1830
|
// A driver exception mid-stream is a harness/pipe failure, not task
|
|
1861
1831
|
// output — classify so agents can retry instead of blaming the work.
|
|
1862
|
-
const msg =
|
|
1832
|
+
const msg = errorMessage(e);
|
|
1863
1833
|
const failure = classifyFailure(msg);
|
|
1864
1834
|
const errEv = {
|
|
1865
1835
|
type: "session.failed",
|
|
@@ -1869,8 +1839,7 @@ class Daemon {
|
|
|
1869
1839
|
failure,
|
|
1870
1840
|
raw: e,
|
|
1871
1841
|
};
|
|
1872
|
-
this.
|
|
1873
|
-
this.broadcast(sessionId, errEv);
|
|
1842
|
+
this.emit(sessionId, errEv);
|
|
1874
1843
|
this.sessions.setStatus(sessionId, "failed", { lastEvent: msg.slice(0, 200), failure });
|
|
1875
1844
|
return;
|
|
1876
1845
|
}
|
|
@@ -1902,8 +1871,7 @@ class Daemon {
|
|
|
1902
1871
|
};
|
|
1903
1872
|
const error = sess.lastEvent || "event stream ended without a terminal event";
|
|
1904
1873
|
const errEv = { type: "session.failed", sessionId, timestamp: new Date().toISOString(), error, failure };
|
|
1905
|
-
this.
|
|
1906
|
-
this.broadcast(sessionId, errEv);
|
|
1874
|
+
this.emit(sessionId, errEv);
|
|
1907
1875
|
this.sessions.setStatus(sessionId, "failed", { lastEvent: error.slice(0, 200), failure });
|
|
1908
1876
|
}
|
|
1909
1877
|
}
|
|
@@ -2068,25 +2036,23 @@ class Daemon {
|
|
|
2068
2036
|
handle.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
2069
2037
|
}
|
|
2070
2038
|
catch { }
|
|
2071
|
-
|
|
2072
|
-
const paths = getPaths();
|
|
2073
|
-
try {
|
|
2074
|
-
fs.unlinkSync(paths.daemonSock);
|
|
2075
|
-
}
|
|
2076
|
-
catch { }
|
|
2077
|
-
try {
|
|
2078
|
-
fs.unlinkSync(paths.daemonPid);
|
|
2079
|
-
}
|
|
2080
|
-
catch { }
|
|
2039
|
+
if (this.boundPaths) {
|
|
2081
2040
|
try {
|
|
2082
|
-
this.
|
|
2041
|
+
fs.unlinkSync(this.boundPaths.sock);
|
|
2083
2042
|
}
|
|
2084
2043
|
catch { }
|
|
2085
2044
|
try {
|
|
2086
|
-
this.
|
|
2045
|
+
fs.unlinkSync(this.boundPaths.pid);
|
|
2087
2046
|
}
|
|
2088
2047
|
catch { }
|
|
2089
2048
|
}
|
|
2049
|
+
try {
|
|
2050
|
+
this.server?.close();
|
|
2051
|
+
}
|
|
2052
|
+
catch { }
|
|
2053
|
+
try {
|
|
2054
|
+
this.db.close();
|
|
2055
|
+
}
|
|
2090
2056
|
catch { }
|
|
2091
2057
|
}
|
|
2092
2058
|
safeListActive() {
|
|
@@ -2152,8 +2118,7 @@ class Daemon {
|
|
|
2152
2118
|
failure,
|
|
2153
2119
|
raw: { reason },
|
|
2154
2120
|
};
|
|
2155
|
-
this.
|
|
2156
|
-
this.broadcast(s.id, event);
|
|
2121
|
+
this.emit(s.id, event);
|
|
2157
2122
|
this.sessions.setStatus(s.id, "interrupted", { lastEvent: detail.slice(0, 200), failure });
|
|
2158
2123
|
}
|
|
2159
2124
|
catch { }
|