@harness-nexus/cli 0.1.0-alpha.2 → 0.1.0-alpha.4

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.
Files changed (68) hide show
  1. package/dist/daemon/acp/adapters.d.ts.map +1 -1
  2. package/dist/daemon/acp/adapters.js +9 -3
  3. package/dist/daemon/acp/adapters.js.map +1 -1
  4. package/dist/daemon/acp/agent-connection.d.ts +67 -4
  5. package/dist/daemon/acp/agent-connection.d.ts.map +1 -1
  6. package/dist/daemon/acp/agent-connection.js +113 -9
  7. package/dist/daemon/acp/agent-connection.js.map +1 -1
  8. package/dist/daemon/adapter-ledger.d.ts +62 -0
  9. package/dist/daemon/adapter-ledger.d.ts.map +1 -0
  10. package/dist/daemon/adapter-ledger.js +207 -0
  11. package/dist/daemon/adapter-ledger.js.map +1 -0
  12. package/dist/daemon/chat.d.ts +33 -2
  13. package/dist/daemon/chat.d.ts.map +1 -1
  14. package/dist/daemon/chat.js +956 -42
  15. package/dist/daemon/chat.js.map +1 -1
  16. package/dist/daemon/client.d.ts +11 -2
  17. package/dist/daemon/client.d.ts.map +1 -1
  18. package/dist/daemon/client.js +122 -24
  19. package/dist/daemon/client.js.map +1 -1
  20. package/dist/daemon/config-view.d.ts +42 -0
  21. package/dist/daemon/config-view.d.ts.map +1 -0
  22. package/dist/daemon/config-view.js +159 -0
  23. package/dist/daemon/config-view.js.map +1 -0
  24. package/dist/daemon/dsh-sessions.d.ts +161 -0
  25. package/dist/daemon/dsh-sessions.d.ts.map +1 -0
  26. package/dist/daemon/dsh-sessions.js +674 -0
  27. package/dist/daemon/dsh-sessions.js.map +1 -0
  28. package/dist/daemon/dsh-tap/index.mjs +99 -0
  29. package/dist/daemon/dsh-tap-listener.d.ts +62 -0
  30. package/dist/daemon/dsh-tap-listener.d.ts.map +1 -0
  31. package/dist/daemon/dsh-tap-listener.js +196 -0
  32. package/dist/daemon/dsh-tap-listener.js.map +1 -0
  33. package/dist/daemon/jobs.d.ts +8 -5
  34. package/dist/daemon/jobs.d.ts.map +1 -1
  35. package/dist/daemon/jobs.js +28 -8
  36. package/dist/daemon/jobs.js.map +1 -1
  37. package/dist/daemon/runtime-config.d.ts +34 -0
  38. package/dist/daemon/runtime-config.d.ts.map +1 -0
  39. package/dist/daemon/runtime-config.js +347 -0
  40. package/dist/daemon/runtime-config.js.map +1 -0
  41. package/dist/daemon/runtime.d.ts +49 -0
  42. package/dist/daemon/runtime.d.ts.map +1 -0
  43. package/dist/daemon/runtime.js +218 -0
  44. package/dist/daemon/runtime.js.map +1 -0
  45. package/dist/daemon/sessions.d.ts +26 -0
  46. package/dist/daemon/sessions.d.ts.map +1 -0
  47. package/dist/daemon/sessions.js +169 -0
  48. package/dist/daemon/sessions.js.map +1 -0
  49. package/dist/daemon/workspace.d.ts +5 -0
  50. package/dist/daemon/workspace.d.ts.map +1 -0
  51. package/dist/daemon/workspace.js +44 -0
  52. package/dist/daemon/workspace.js.map +1 -0
  53. package/dist/install/adapters/deepseek.d.ts +4 -0
  54. package/dist/install/adapters/deepseek.d.ts.map +1 -1
  55. package/dist/install/adapters/deepseek.js +4 -2
  56. package/dist/install/adapters/deepseek.js.map +1 -1
  57. package/dist/inventory/common.d.ts +2 -0
  58. package/dist/inventory/common.d.ts.map +1 -1
  59. package/dist/inventory/common.js.map +1 -1
  60. package/dist/inventory/runtime.d.ts +40 -0
  61. package/dist/inventory/runtime.d.ts.map +1 -0
  62. package/dist/inventory/runtime.js +114 -0
  63. package/dist/inventory/runtime.js.map +1 -0
  64. package/dist/inventory/scanners/claude-code.d.ts +2 -0
  65. package/dist/inventory/scanners/claude-code.d.ts.map +1 -1
  66. package/dist/inventory/scanners/claude-code.js +103 -39
  67. package/dist/inventory/scanners/claude-code.js.map +1 -1
  68. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/daemon/acp/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AA2BzD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,UAAU,GAAG,IAAI,CAQhG"}
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/daemon/acp/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAiCzD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,UAAU,GAAG,IAAI,CAQhG"}
@@ -9,9 +9,15 @@
9
9
  * tests at a fixture agent.
10
10
  */
11
11
  const DEFAULT_ACP_COMMANDS = {
12
- // Official Zed adapter, now powered by the Claude Agent SDK (bundles the CLI;
13
- // needs local Claude auth).
14
- 'claude-code': ['npx', '-y', '@zed-industries/claude-agent-acp'],
12
+ // The ACP project's official wrapper (took over from @zed-industries's
13
+ // 0.23.x, which the daemon shipped until 2026-09). Reason for the swap,
14
+ // rig-verified: the old wrapper never requests thinking on gateway/unknown
15
+ // models (MAX_THINKING_TOKENS on the adapter env didn't help either), so
16
+ // chat showed no thought stream; the official 0.76.0 streams
17
+ // agent_thought_chunk by default, advertises loadSession + list/resume
18
+ // (both caps shapes the daemon already detects), and returns full
19
+ // SessionInfo. Same switch the stable reference CC bridge runs.
20
+ 'claude-code': ['npx', '-y', '@agentclientprotocol/claude-agent-acp'],
15
21
  // Official Zed adapter wrapping the OpenAI Codex CLI (needs `codex` on PATH).
16
22
  codex: ['npx', '-y', '@zed-industries/codex-acp'],
17
23
  // DeepSeek Harness ships a native ACP v1 profile (needs `dsh` on PATH and a
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../../src/daemon/acp/adapters.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAkD;IAC1E,8EAA8E;IAC9E,4BAA4B;IAC5B,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,kCAAkC,CAAC;IAChE,8EAA8E;IAC9E,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,2BAA2B,CAAC;IACjD,4EAA4E;IAC5E,kDAAkD;IAClD,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC;IACrC,yDAAyD;IACzD,MAAM,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC;IACxC,KAAK,EAAE,IAAI,EAAE,oDAAoD;IACjE,OAAO,EAAE,IAAI;CACd,CAAC;AAOF,MAAM,UAAU,iBAAiB,CAAC,MAAmB,EAAE,GAAsB;IAC3E,MAAM,QAAQ,GAAG,GAAG,CAAC,kBAAkB,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../../src/daemon/acp/adapters.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAkD;IAC1E,uEAAuE;IACvE,wEAAwE;IACxE,2EAA2E;IAC3E,yEAAyE;IACzE,6DAA6D;IAC7D,uEAAuE;IACvE,kEAAkE;IAClE,gEAAgE;IAChE,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,uCAAuC,CAAC;IACrE,8EAA8E;IAC9E,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,2BAA2B,CAAC;IACjD,4EAA4E;IAC5E,kDAAkD;IAClD,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC;IACrC,yDAAyD;IACzD,MAAM,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC;IACxC,KAAK,EAAE,IAAI,EAAE,oDAAoD;IACjE,OAAO,EAAE,IAAI;CACd,CAAC;AAOF,MAAM,UAAU,iBAAiB,CAAC,MAAmB,EAAE,GAAsB;IAC3E,MAAM,QAAQ,GAAG,GAAG,CAAC,kBAAkB,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC"}
@@ -4,12 +4,51 @@ export type PermissionOutcome = {
4
4
  } | {
5
5
  outcome: 'cancelled';
6
6
  };
7
+ /**
8
+ * A JSON-RPC request id, echoed VERBATIM when answering an agent-initiated
9
+ * request. Adapters disagree on the shape: the Zed wrappers count ints, but
10
+ * codex-acp sends UUIDs — so a `Number()` coercion turns its id into NaN and
11
+ * the encoded reply carries `"id":null`, which the agent cannot match (the
12
+ * permission then waits forever and the turn hangs).
13
+ */
14
+ export type JsonRpcId = string | number | null;
7
15
  export interface AcpAgentInfo {
8
16
  name?: string | undefined;
9
17
  version?: string | undefined;
10
18
  }
19
+ /** The resume-relevant slice of `agentCapabilities` (9 W7). */
20
+ export interface AcpSessionCaps {
21
+ /** `session/load` — resume WITH history replay (claude/codex adapters). */
22
+ load: boolean;
23
+ /** `session/resume` — resume WITHOUT replay (dsh's native adapter). */
24
+ resume: boolean;
25
+ /** `session/list` exists (the claude/codex listing path). */
26
+ list: boolean;
27
+ }
28
+ /**
29
+ * Advertised caps decide the resume dialect (9 W7): prefer `session/load`
30
+ * (replay for free), fall back to `session/resume`. `loadSession` is the
31
+ * pre-capability legacy flag — and WHERE it sits differs by adapter family:
32
+ * the Zed adapters set it at the initialize RESULT ROOT, the official
33
+ * `@agentclientprotocol/claude-agent-acp` sets it NESTED inside
34
+ * `agentCapabilities.loadSession` while its `sessionCapabilities` has `resume`
35
+ * but NOT `load`. Reading only the root made the official wrapper look like a
36
+ * resume-only adapter — claude-code channels then resumed with NO replay
37
+ * ("opening a history session shows an empty pane").
38
+ */
39
+ export declare function deriveSessionCaps(result: unknown): AcpSessionCaps;
40
+ /**
41
+ * 9 W9 B — prompt-content capabilities from the initialize result. `image`
42
+ * gates the composer's attach affordance; dsh derives it per model route so
43
+ * a live channel may legitimately advertise false.
44
+ */
45
+ export declare function derivePromptCaps(result: unknown): {
46
+ image: boolean;
47
+ };
11
48
  export declare class AcpAgentConnection {
12
49
  private proc;
50
+ /** The detached leader's pid — which is also its process-group id. */
51
+ readonly pgid: number | null;
13
52
  private nextId;
14
53
  private pending;
15
54
  private stderrTail;
@@ -17,25 +56,49 @@ export declare class AcpAgentConnection {
17
56
  private onPermission;
18
57
  private exited;
19
58
  private constructor();
20
- /** Spawn + `initialize` handshake + `initialized` notification. */
59
+ /**
60
+ * Spawn + `initialize` handshake + `initialized` notification.
61
+ *
62
+ * 9 W11 A — `onSpawned` fires synchronously right after the detached spawn
63
+ * (BEFORE initialize) so the caller can ledger the process group while the
64
+ * hard-death window is still open; a throwing callback kills the spawn and
65
+ * fails the start (an unledgered adapter is worse than a closed channel).
66
+ */
21
67
  static start(command: string, args: string[], opts: {
22
68
  cwd: string;
23
69
  env?: NodeJS.ProcessEnv | undefined;
24
70
  initializeTimeoutMs?: number;
71
+ onSpawned?: (pgid: number) => void;
25
72
  }): Promise<{
26
73
  conn: AcpAgentConnection;
27
74
  agentInfo: AcpAgentInfo;
75
+ sessionCaps: AcpSessionCaps;
76
+ promptCaps: {
77
+ image: boolean;
78
+ };
28
79
  }>;
29
80
  /** JSON-RPC request with a timeout; rejects on error/exit. */
30
81
  request(method: string, params: unknown, timeoutMs: number): Promise<unknown>;
31
82
  notify(method: string, params: unknown): void;
32
83
  /** Answer the agent's `session/request_permission` (optionId verbatim). */
33
- respondPermission(jsonrpcId: number, outcome: PermissionOutcome): void;
84
+ respondPermission(jsonrpcId: JsonRpcId, outcome: PermissionOutcome): void;
34
85
  setNotificationHandler(handler: (method: string, params: Record<string, unknown>) => void): void;
35
- setPermissionHandler(handler: (jsonrpcId: number, params: Record<string, unknown>) => void): void;
86
+ setPermissionHandler(handler: (jsonrpcId: JsonRpcId, params: Record<string, unknown>) => void): void;
36
87
  /** Fires when the subprocess exits on its own (crash/quit) — not on kill(). */
37
88
  onExit(handler: () => void): void;
38
- /** SIGTERM, then SIGKILL after `graceMs`. Idempotent. */
89
+ /**
90
+ * Whether the process GROUP still exists (the audit's backstop — a session
91
+ * whose group died without the exit event reaching teardown).
92
+ */
93
+ isGroupAlive(): boolean;
94
+ /**
95
+ * SIGTERM the whole process GROUP, then SIGKILL it after `graceMs`.
96
+ * Idempotent. The group signal is the point: the direct child (npm/npx)
97
+ * dying does NOT take the wrapper and the vendor binary with it — they
98
+ * reparent to init and keep running. The timer is unref'd and NOT cleared
99
+ * on leader exit: grandchildren can outlive the leader, and the follow-up
100
+ * SIGKILL to a dead group is a caught ESRCH.
101
+ */
39
102
  kill(graceMs?: number): void;
40
103
  private handleLine;
41
104
  private send;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-connection.d.ts","sourceRoot":"","sources":["../../../src/daemon/acp/agent-connection.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAC3B;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,cAAc,CAA4E;IAClG,OAAO,CAAC,YAAY,CACb;IACP,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO;IAwBP,mEAAmE;WACtD,KAAK,CAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,GACvF,OAAO,CAAC;QAAE,IAAI,EAAE,kBAAkB,CAAC;QAAC,SAAS,EAAE,YAAY,CAAA;KAAE,CAAC;IAyBjE,8DAA8D;IAC9D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB7E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAI7C,2EAA2E;IAC3E,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAItE,sBAAsB,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI;IAIhG,oBAAoB,CAClB,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GACpE,IAAI;IAIP,+EAA+E;IAC/E,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIjC,yDAAyD;IACzD,IAAI,CAAC,OAAO,SAAO,GAAG,IAAI;IAU1B,OAAO,CAAC,UAAU;IAuClB,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,gBAAgB;CAGzB"}
1
+ {"version":3,"file":"agent-connection.d.ts","sourceRoot":"","sources":["../../../src/daemon/acp/agent-connection.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,41 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
+ import { groupIsAlive } from '../adapter-ledger.js';
4
+ /**
5
+ * Advertised caps decide the resume dialect (9 W7): prefer `session/load`
6
+ * (replay for free), fall back to `session/resume`. `loadSession` is the
7
+ * pre-capability legacy flag — and WHERE it sits differs by adapter family:
8
+ * the Zed adapters set it at the initialize RESULT ROOT, the official
9
+ * `@agentclientprotocol/claude-agent-acp` sets it NESTED inside
10
+ * `agentCapabilities.loadSession` while its `sessionCapabilities` has `resume`
11
+ * but NOT `load`. Reading only the root made the official wrapper look like a
12
+ * resume-only adapter — claude-code channels then resumed with NO replay
13
+ * ("opening a history session shows an empty pane").
14
+ */
15
+ export function deriveSessionCaps(result) {
16
+ const r = (result ?? {});
17
+ const caps = r.agentCapabilities?.sessionCapabilities ?? {};
18
+ return {
19
+ load: caps['load'] !== undefined ||
20
+ r.loadSession === true ||
21
+ r.agentCapabilities?.loadSession === true,
22
+ resume: caps['resume'] !== undefined,
23
+ list: caps['list'] !== undefined,
24
+ };
25
+ }
26
+ /**
27
+ * 9 W9 B — prompt-content capabilities from the initialize result. `image`
28
+ * gates the composer's attach affordance; dsh derives it per model route so
29
+ * a live channel may legitimately advertise false.
30
+ */
31
+ export function derivePromptCaps(result) {
32
+ const r = (result ?? {});
33
+ return { image: r.agentCapabilities?.promptCapabilities?.image === true };
34
+ }
3
35
  export class AcpAgentConnection {
4
36
  proc;
37
+ /** The detached leader's pid — which is also its process-group id. */
38
+ pgid;
5
39
  nextId = 1;
6
40
  pending = new Map();
7
41
  stderrTail = [];
@@ -10,6 +44,7 @@ export class AcpAgentConnection {
10
44
  exited = false;
11
45
  constructor(proc) {
12
46
  this.proc = proc;
47
+ this.pgid = proc.pid ?? null;
13
48
  const rl = createInterface({ input: proc.stdout });
14
49
  rl.on('line', (line) => this.handleLine(line));
15
50
  proc.stderr?.on('data', (chunk) => {
@@ -32,7 +67,14 @@ export class AcpAgentConnection {
32
67
  this.pending.clear();
33
68
  });
34
69
  }
35
- /** Spawn + `initialize` handshake + `initialized` notification. */
70
+ /**
71
+ * Spawn + `initialize` handshake + `initialized` notification.
72
+ *
73
+ * 9 W11 A — `onSpawned` fires synchronously right after the detached spawn
74
+ * (BEFORE initialize) so the caller can ledger the process group while the
75
+ * hard-death window is still open; a throwing callback kills the spawn and
76
+ * fails the start (an unledgered adapter is worse than a closed channel).
77
+ */
36
78
  static async start(command, args, opts) {
37
79
  const initializeTimeoutMs = opts.initializeTimeoutMs ?? 20000;
38
80
  let proc;
@@ -41,6 +83,11 @@ export class AcpAgentConnection {
41
83
  cwd: opts.cwd,
42
84
  env: { ...process.env, ...(opts.env ?? {}) },
43
85
  stdio: ['pipe', 'pipe', 'pipe'],
86
+ // Own process group: adapters spawn their own trees (npm → sh →
87
+ // wrapper → the vendor binary). Signaling only the direct child
88
+ // orphaned the grandchildren — a bare `claude` binary survived every
89
+ // teardown, parented to init. kill() takes the whole group down.
90
+ detached: true,
44
91
  });
45
92
  }
46
93
  catch (e) {
@@ -50,9 +97,34 @@ export class AcpAgentConnection {
50
97
  proc.on('error', (e) => {
51
98
  conn.failAll(new Error(`ACP adapter '${command}' failed: ${errText(e)}`));
52
99
  });
53
- const result = (await conn.request('initialize', { protocolVersion: 1, clientCapabilities: {} }, initializeTimeoutMs));
100
+ if (opts.onSpawned !== undefined && proc.pid !== undefined) {
101
+ try {
102
+ opts.onSpawned(proc.pid);
103
+ }
104
+ catch (e) {
105
+ conn.kill();
106
+ throw new Error(`failed to ledger ACP adapter '${command}': ${errText(e)}`);
107
+ }
108
+ }
109
+ let result;
110
+ try {
111
+ result = (await conn.request('initialize', { protocolVersion: 1, clientCapabilities: {} }, initializeTimeoutMs));
112
+ }
113
+ catch (e) {
114
+ // The caller never receives this connection (start has not returned),
115
+ // so no one else would kill it — a timed-out/failed initialize must
116
+ // not leak the spawned group. This closes the pre-W11 leak where a
117
+ // hung initialize left a live adapter behind a dead channel.
118
+ conn.kill();
119
+ throw e;
120
+ }
54
121
  conn.notify('initialized', {});
55
- return { conn, agentInfo: result?.agentInfo ?? {} };
122
+ return {
123
+ conn,
124
+ agentInfo: result?.agentInfo ?? {},
125
+ sessionCaps: deriveSessionCaps(result),
126
+ promptCaps: derivePromptCaps(result),
127
+ };
56
128
  }
57
129
  /** JSON-RPC request with a timeout; rejects on error/exit. */
58
130
  request(method, params, timeoutMs) {
@@ -90,14 +162,40 @@ export class AcpAgentConnection {
90
162
  onExit(handler) {
91
163
  this.proc.on('exit', handler);
92
164
  }
93
- /** SIGTERM, then SIGKILL after `graceMs`. Idempotent. */
165
+ /**
166
+ * Whether the process GROUP still exists (the audit's backstop — a session
167
+ * whose group died without the exit event reaching teardown).
168
+ */
169
+ isGroupAlive() {
170
+ return this.pgid !== null && groupIsAlive(this.pgid);
171
+ }
172
+ /**
173
+ * SIGTERM the whole process GROUP, then SIGKILL it after `graceMs`.
174
+ * Idempotent. The group signal is the point: the direct child (npm/npx)
175
+ * dying does NOT take the wrapper and the vendor binary with it — they
176
+ * reparent to init and keep running. The timer is unref'd and NOT cleared
177
+ * on leader exit: grandchildren can outlive the leader, and the follow-up
178
+ * SIGKILL to a dead group is a caught ESRCH.
179
+ */
94
180
  kill(graceMs = 3000) {
95
181
  if (this.exited || this.proc.stdin === null)
96
182
  return;
97
183
  this.proc.removeAllListeners('exit');
98
- const killTimer = setTimeout(() => this.proc.kill('SIGKILL'), graceMs);
99
- this.proc.on('exit', () => clearTimeout(killTimer));
100
- this.proc.kill('SIGTERM');
184
+ const pid = this.proc.pid;
185
+ const sigGroup = (sig) => {
186
+ if (pid === undefined)
187
+ return;
188
+ try {
189
+ process.kill(-pid, sig); // negative pid = the process group
190
+ }
191
+ catch {
192
+ // group already gone
193
+ }
194
+ };
195
+ const killTimer = setTimeout(() => sigGroup('SIGKILL'), graceMs);
196
+ killTimer.unref();
197
+ sigGroup('SIGTERM');
198
+ this.proc.kill('SIGTERM'); // the leader too (belt and braces)
101
199
  this.failAll(new Error('agent process killed'));
102
200
  this.exited = true;
103
201
  }
@@ -113,7 +211,7 @@ export class AcpAgentConnection {
113
211
  return; // agents log noise on stdout sometimes; skip non-JSON lines
114
212
  }
115
213
  if (msg.method === 'session/request_permission' && msg.id !== undefined) {
116
- this.onPermission?.(Number(msg.id), msg.params ?? {});
214
+ this.onPermission?.(msg.id, msg.params ?? {});
117
215
  return;
118
216
  }
119
217
  if (msg.method !== undefined) {
@@ -129,7 +227,13 @@ export class AcpAgentConnection {
129
227
  this.pending.delete(Number(msg.id));
130
228
  clearTimeout(pending.timer);
131
229
  if (msg.error !== undefined) {
132
- pending.reject(new Error(msg.error.message ?? 'ACP request failed'));
230
+ // Error `data` rides along — the callers key retry/surface logic off
231
+ // it. The detail FIELD differs per adapter dialect: dsh uses
232
+ // `data.details`, codex-acp uses `data.message` (its top-level
233
+ // message is a useless "Internal error").
234
+ const data = msg.error.data;
235
+ const details = [data?.message, data?.details].filter(Boolean).join(': ');
236
+ pending.reject(new Error(`${msg.error.message ?? 'ACP request failed'}${details ? `: ${details}` : ''}`));
133
237
  }
134
238
  else {
135
239
  pending.resolve(msg.result);
@@ -1 +1 @@
1
- {"version":3,"file":"agent-connection.js","sourceRoot":"","sources":["../../../src/daemon/acp/agent-connection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-connection.js","sourceRoot":"","sources":["../../../src/daemon/acp/agent-connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Adapter process ledger (Phase 9 W11 A) — the daemon's crash-proof account
3
+ * of every adapter process GROUP it created. One JSON file per live adapter
4
+ * under `~/.hnx/adapters/<wireSessionId>.json`:
5
+ *
6
+ * { pgid, target, command, wireSessionId, nativeSessionId?, startedAt }
7
+ *
8
+ * Invariants:
9
+ * - The entry is written INSIDE `AcpAgentConnection.start`, right after the
10
+ * detached spawn and BEFORE initialize — a hard death during establishment
11
+ * still leaves a sweepable record (Principle: a crash may orphan a
12
+ * process, never an UNACCOUNTED one).
13
+ * - Kill paths never unlink: files are removed ONLY by the periodic audit
14
+ * (group gone) or the boot sweep. Removing at teardown would lose
15
+ * accounting if the daemon hard-dies inside the SIGTERM→SIGKILL grace —
16
+ * leaving the file lets the next boot's sweep finish the reap instead.
17
+ * - No secrets: the command name only (credentials live in the target's
18
+ * native config, never in adapter argv/env).
19
+ * - One daemon per (user, home): a second daemon's boot sweep would reap the
20
+ * first one's live adapters. Concurrent daemons on one home are already
21
+ * unsupported (shared ~/.hnx/config.json, double job dispatch).
22
+ */
23
+ export interface AdapterLedgerEntry {
24
+ /** The process-group id (the detached leader's pid). */
25
+ pgid: number;
26
+ target: string;
27
+ command: string;
28
+ wireSessionId: string;
29
+ nativeSessionId?: string;
30
+ startedAt: number;
31
+ }
32
+ export declare function adapterLedgerDir(home: string): string;
33
+ export declare function writeAdapterLedgerEntry(home: string, entry: AdapterLedgerEntry): void;
34
+ /** All valid entries (malformed files are skipped here, not deleted). */
35
+ export declare function readAdapterLedger(home: string): AdapterLedgerEntry[];
36
+ /**
37
+ * Whether the process GROUP still exists. EPERM (the group exists but is
38
+ * owned by someone else — pid reuse made it foreign) counts as NOT ours:
39
+ * every caller treats a false answer as "drop the file without signaling",
40
+ * so a foreign process is never killed on our word alone.
41
+ */
42
+ export declare function groupIsAlive(pgid: number): boolean;
43
+ /** SIGTERM the group, then SIGKILL after `graceMs` (unref'd — mirrors conn.kill). */
44
+ export declare function killProcessGroup(pgid: number, graceMs?: number): void;
45
+ /**
46
+ * Boot sweep — run ONCE at daemon start, before anything here can spawn a
47
+ * new adapter: every still-alive pgid in the ledger is a leftover from a
48
+ * previous instance's hard death (SIGKILL/OOM skip every teardown path; the
49
+ * detached groups survive it). Kills live groups, deletes every file.
50
+ * Returns what happened so the caller can log it.
51
+ */
52
+ export declare function sweepAdapterLedger(home: string): {
53
+ reaped: number;
54
+ dropped: number;
55
+ };
56
+ /**
57
+ * Periodic audit — the ONLY runtime remover of ledger files: an entry whose
58
+ * group is gone (the adapter exited, or a teardown's kill completed) is
59
+ * deleted; live entries stay. Returns how many files were dropped.
60
+ */
61
+ export declare function auditAdapterLedger(home: string): number;
62
+ //# sourceMappingURL=adapter-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-ledger.d.ts","sourceRoot":"","sources":["../../src/daemon/adapter-ledger.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAcrF;AAyBD,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAepE;AAiCD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,IAAI,CAWnE;AAsCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAcpF;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASvD"}
@@ -0,0 +1,207 @@
1
+ import { mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /** Wire session ids are server-generated base64url-ish tokens. */
4
+ const WIRE_ID = /^[A-Za-z0-9_-]{1,128}$/;
5
+ /** Sanity bound — a daemon owns at most CHAT_MAX_SESSIONS_PER_MACHINE of these. */
6
+ const MAX_FILES = 256;
7
+ export function adapterLedgerDir(home) {
8
+ return join(home, '.hnx', 'adapters');
9
+ }
10
+ export function writeAdapterLedgerEntry(home, entry) {
11
+ if (!WIRE_ID.test(entry.wireSessionId)) {
12
+ throw new Error('refusing adapter ledger write: unsafe wire session id');
13
+ }
14
+ if (!Number.isInteger(entry.pgid) || entry.pgid <= 0) {
15
+ throw new Error(`refusing adapter ledger write: invalid pgid ${String(entry.pgid)}`);
16
+ }
17
+ const dir = adapterLedgerDir(home);
18
+ mkdirSync(dir, { recursive: true });
19
+ const file = join(dir, `${entry.wireSessionId}.json`);
20
+ const tmp = `${file}.tmp`;
21
+ writeFileSync(tmp, `${JSON.stringify(entry)}\n`, 'utf8');
22
+ // Atomic swap — a crash mid-write can never half-replace an entry.
23
+ renameSync(tmp, file);
24
+ }
25
+ /** Parse one ledger file; null = malformed (not an entry we ever wrote). */
26
+ function parseEntry(file) {
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(readFileSync(file, 'utf8'));
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ if (typeof parsed['pgid'] === 'number' &&
35
+ Number.isInteger(parsed['pgid']) &&
36
+ parsed['pgid'] > 0 &&
37
+ typeof parsed['target'] === 'string' &&
38
+ typeof parsed['command'] === 'string' &&
39
+ typeof parsed['wireSessionId'] === 'string' &&
40
+ WIRE_ID.test(parsed['wireSessionId']) &&
41
+ typeof parsed['startedAt'] === 'number') {
42
+ return parsed;
43
+ }
44
+ return null;
45
+ }
46
+ /** All valid entries (malformed files are skipped here, not deleted). */
47
+ export function readAdapterLedger(home) {
48
+ const dir = adapterLedgerDir(home);
49
+ let names;
50
+ try {
51
+ names = readdirSync(dir);
52
+ }
53
+ catch {
54
+ return []; // no ledger dir yet — nothing was ever spawned here
55
+ }
56
+ const out = [];
57
+ for (const name of names.slice(0, MAX_FILES)) {
58
+ if (!name.endsWith('.json'))
59
+ continue;
60
+ const entry = parseEntry(join(dir, name));
61
+ if (entry !== null)
62
+ out.push(entry);
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Whether the process GROUP has at least one LIVE (non-zombie) member.
68
+ * `kill(-pgid, 0)` alone cannot tell: a ZOMBIE stays answerable forever,
69
+ * and on machines whose PID 1 does not reap (a bare docker CMD, nohup under
70
+ * a plain shell) orphaned grandchildren zombify and would pin their ledger
71
+ * entry past every audit. Linux /proc walk; where /proc is absent the
72
+ * kill() answer stands (conservative).
73
+ */
74
+ function groupHasLiveMember(pgid) {
75
+ let names;
76
+ try {
77
+ names = readdirSync('/proc');
78
+ }
79
+ catch {
80
+ return true;
81
+ }
82
+ for (const name of names) {
83
+ if (!/^[0-9]+$/.test(name))
84
+ continue;
85
+ let stat;
86
+ try {
87
+ stat = readFileSync(`/proc/${name}/stat`, 'utf8');
88
+ }
89
+ catch {
90
+ continue; // raced an exit — the next call re-reads
91
+ }
92
+ // Fields after `(comm)`: state ppid pgrp … (comm may contain spaces).
93
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
94
+ if (fields.length < 3)
95
+ continue;
96
+ if (Number.parseInt(fields[2], 10) === pgid && fields[0] !== 'Z')
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ /**
102
+ * Whether the process GROUP still exists. EPERM (the group exists but is
103
+ * owned by someone else — pid reuse made it foreign) counts as NOT ours:
104
+ * every caller treats a false answer as "drop the file without signaling",
105
+ * so a foreign process is never killed on our word alone.
106
+ */
107
+ export function groupIsAlive(pgid) {
108
+ try {
109
+ process.kill(-pgid, 0);
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ return groupHasLiveMember(pgid);
115
+ }
116
+ /** SIGTERM the group, then SIGKILL after `graceMs` (unref'd — mirrors conn.kill). */
117
+ export function killProcessGroup(pgid, graceMs = 3000) {
118
+ const sig = (s) => {
119
+ try {
120
+ process.kill(-pgid, s);
121
+ }
122
+ catch {
123
+ // group already gone
124
+ }
125
+ };
126
+ const t = setTimeout(() => sig('SIGKILL'), graceMs);
127
+ t.unref();
128
+ sig('SIGTERM');
129
+ }
130
+ function unlinkQuiet(home, wireSessionId) {
131
+ try {
132
+ unlinkSync(join(adapterLedgerDir(home), `${wireSessionId}.json`));
133
+ }
134
+ catch {
135
+ // already gone
136
+ }
137
+ }
138
+ /**
139
+ * Housekeeping shared by the sweep and the audit: junk files — a torn write,
140
+ * hand-dropped garbage, or a `.json.tmp` stranded by a hard death between
141
+ * the tmp write and the rename — must not accumulate.
142
+ */
143
+ function dropJunkFiles(home) {
144
+ const dir = adapterLedgerDir(home);
145
+ let names;
146
+ try {
147
+ names = readdirSync(dir);
148
+ }
149
+ catch {
150
+ return 0;
151
+ }
152
+ let removed = 0;
153
+ for (const name of names.slice(0, MAX_FILES)) {
154
+ const isTmp = name.endsWith('.json.tmp');
155
+ if (!isTmp && !name.endsWith('.json'))
156
+ continue;
157
+ if (!isTmp && parseEntry(join(dir, name)) !== null)
158
+ continue;
159
+ try {
160
+ unlinkSync(join(dir, name));
161
+ removed++;
162
+ }
163
+ catch {
164
+ // racing writer — the next pass retries
165
+ }
166
+ }
167
+ return removed;
168
+ }
169
+ /**
170
+ * Boot sweep — run ONCE at daemon start, before anything here can spawn a
171
+ * new adapter: every still-alive pgid in the ledger is a leftover from a
172
+ * previous instance's hard death (SIGKILL/OOM skip every teardown path; the
173
+ * detached groups survive it). Kills live groups, deletes every file.
174
+ * Returns what happened so the caller can log it.
175
+ */
176
+ export function sweepAdapterLedger(home) {
177
+ let reaped = 0;
178
+ let dropped = 0;
179
+ for (const entry of readAdapterLedger(home)) {
180
+ if (groupIsAlive(entry.pgid)) {
181
+ killProcessGroup(entry.pgid);
182
+ reaped++;
183
+ }
184
+ else {
185
+ dropped++;
186
+ }
187
+ unlinkQuiet(home, entry.wireSessionId);
188
+ }
189
+ dropJunkFiles(home);
190
+ return { reaped, dropped };
191
+ }
192
+ /**
193
+ * Periodic audit — the ONLY runtime remover of ledger files: an entry whose
194
+ * group is gone (the adapter exited, or a teardown's kill completed) is
195
+ * deleted; live entries stay. Returns how many files were dropped.
196
+ */
197
+ export function auditAdapterLedger(home) {
198
+ let dropped = 0;
199
+ for (const entry of readAdapterLedger(home)) {
200
+ if (!groupIsAlive(entry.pgid)) {
201
+ unlinkQuiet(home, entry.wireSessionId);
202
+ dropped++;
203
+ }
204
+ }
205
+ return dropped + dropJunkFiles(home);
206
+ }
207
+ //# sourceMappingURL=adapter-ledger.js.map