@sumicom/quicksave 0.4.0 → 0.5.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.
Files changed (75) hide show
  1. package/dist/ai/claudeCodeService.d.ts +89 -0
  2. package/dist/ai/claudeCodeService.d.ts.map +1 -0
  3. package/dist/ai/claudeCodeService.js +602 -0
  4. package/dist/ai/claudeCodeService.js.map +1 -0
  5. package/dist/ai/sessionStore.d.ts +31 -0
  6. package/dist/ai/sessionStore.d.ts.map +1 -0
  7. package/dist/ai/sessionStore.js +72 -0
  8. package/dist/ai/sessionStore.js.map +1 -0
  9. package/dist/ai/sessionStore.test.d.ts +2 -0
  10. package/dist/ai/sessionStore.test.d.ts.map +1 -0
  11. package/dist/ai/sessionStore.test.js +99 -0
  12. package/dist/ai/sessionStore.test.js.map +1 -0
  13. package/dist/config.d.ts +7 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +39 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/connection/connection.d.ts +2 -0
  18. package/dist/connection/connection.d.ts.map +1 -1
  19. package/dist/connection/connection.js +14 -5
  20. package/dist/connection/connection.js.map +1 -1
  21. package/dist/connection/relay.d.ts.map +1 -1
  22. package/dist/connection/relay.js +2 -0
  23. package/dist/connection/relay.js.map +1 -1
  24. package/dist/git/operations.d.ts +8 -1
  25. package/dist/git/operations.d.ts.map +1 -1
  26. package/dist/git/operations.js +71 -4
  27. package/dist/git/operations.js.map +1 -1
  28. package/dist/handlers/messageHandler.d.ts +18 -1
  29. package/dist/handlers/messageHandler.d.ts.map +1 -1
  30. package/dist/handlers/messageHandler.js +231 -11
  31. package/dist/handlers/messageHandler.js.map +1 -1
  32. package/dist/handlers/messageHandler.test.js +1 -1
  33. package/dist/index.js +228 -94
  34. package/dist/index.js.map +1 -1
  35. package/dist/service/ensureDaemon.d.ts +23 -0
  36. package/dist/service/ensureDaemon.d.ts.map +1 -0
  37. package/dist/service/ensureDaemon.js +138 -0
  38. package/dist/service/ensureDaemon.js.map +1 -0
  39. package/dist/service/index.d.ts +8 -0
  40. package/dist/service/index.d.ts.map +1 -0
  41. package/dist/service/index.js +8 -0
  42. package/dist/service/index.js.map +1 -0
  43. package/dist/service/ipc.test.d.ts +2 -0
  44. package/dist/service/ipc.test.d.ts.map +1 -0
  45. package/dist/service/ipc.test.js +140 -0
  46. package/dist/service/ipc.test.js.map +1 -0
  47. package/dist/service/ipcClient.d.ts +36 -0
  48. package/dist/service/ipcClient.d.ts.map +1 -0
  49. package/dist/service/ipcClient.js +167 -0
  50. package/dist/service/ipcClient.js.map +1 -0
  51. package/dist/service/ipcServer.d.ts +53 -0
  52. package/dist/service/ipcServer.d.ts.map +1 -0
  53. package/dist/service/ipcServer.js +245 -0
  54. package/dist/service/ipcServer.js.map +1 -0
  55. package/dist/service/run.d.ts +13 -0
  56. package/dist/service/run.d.ts.map +1 -0
  57. package/dist/service/run.js +292 -0
  58. package/dist/service/run.js.map +1 -0
  59. package/dist/service/singleton.d.ts +31 -0
  60. package/dist/service/singleton.d.ts.map +1 -0
  61. package/dist/service/singleton.js +96 -0
  62. package/dist/service/singleton.js.map +1 -0
  63. package/dist/service/singleton.test.d.ts +2 -0
  64. package/dist/service/singleton.test.d.ts.map +1 -0
  65. package/dist/service/singleton.test.js +39 -0
  66. package/dist/service/singleton.test.js.map +1 -0
  67. package/dist/service/stateStore.d.ts +11 -0
  68. package/dist/service/stateStore.d.ts.map +1 -0
  69. package/dist/service/stateStore.js +35 -0
  70. package/dist/service/stateStore.js.map +1 -0
  71. package/dist/service/types.d.ts +111 -0
  72. package/dist/service/types.d.ts.map +1 -0
  73. package/dist/service/types.js +76 -0
  74. package/dist/service/types.js.map +1 -0
  75. package/package.json +3 -2
@@ -0,0 +1,140 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { join } from 'path';
3
+ import { tmpdir } from 'os';
4
+ import { mkdtempSync, rmSync } from 'fs';
5
+ import { IpcServer } from './ipcServer.js';
6
+ import { IpcClient } from './ipcClient.js';
7
+ function makeTmpDir() {
8
+ return mkdtempSync(join(tmpdir(), 'qs-ipc-test-'));
9
+ }
10
+ describe('IPC Server + Client', () => {
11
+ let tmpDir;
12
+ let server;
13
+ const clients = [];
14
+ afterEach(async () => {
15
+ for (const c of clients)
16
+ c.close();
17
+ clients.length = 0;
18
+ if (server)
19
+ await server.close();
20
+ if (tmpDir) {
21
+ try {
22
+ rmSync(tmpDir, { recursive: true, force: true });
23
+ }
24
+ catch { /* ignore */ }
25
+ }
26
+ });
27
+ function socketPath() {
28
+ tmpDir = makeTmpDir();
29
+ return join(tmpDir, 'test.sock');
30
+ }
31
+ async function createServer() {
32
+ const sock = socketPath();
33
+ server = new IpcServer({ version: '0.5.0' });
34
+ await server.listen(sock);
35
+ return sock;
36
+ }
37
+ async function createClient(sock) {
38
+ const client = new IpcClient();
39
+ const hello = await client.connect(sock);
40
+ clients.push(client);
41
+ return { client, hello };
42
+ }
43
+ it('hello handshake returns daemon version info', async () => {
44
+ const sock = await createServer();
45
+ const { hello } = await createClient(sock);
46
+ expect(hello.daemonVersion).toBe('0.5.0');
47
+ expect(hello.daemonIpcVersion).toBe(1);
48
+ expect(hello.daemonPid).toBe(process.pid);
49
+ expect(typeof hello.daemonBuildId).toBe('string');
50
+ });
51
+ it('ping returns uptime and version', async () => {
52
+ const sock = await createServer();
53
+ const { client } = await createClient(sock);
54
+ const result = await client.request('ping');
55
+ expect(result.version).toBe('0.5.0');
56
+ expect(result.ipcVersion).toBe(1);
57
+ expect(typeof result.uptime).toBe('number');
58
+ expect(result.uptime).toBeGreaterThanOrEqual(0);
59
+ });
60
+ it('status returns daemon status summary', async () => {
61
+ const sock = await createServer();
62
+ const { client } = await createClient(sock);
63
+ const result = await client.request('status');
64
+ expect(result.pid).toBe(process.pid);
65
+ expect(result.version).toBe('0.5.0');
66
+ expect(typeof result.uptime).toBe('number');
67
+ expect(result.connectionState).toBe('disconnected');
68
+ expect(result.peerCount).toBe(0);
69
+ });
70
+ it('unknown method returns error', async () => {
71
+ const sock = await createServer();
72
+ const { client } = await createClient(sock);
73
+ await expect(client.request('nonexistent')).rejects.toThrow('Method not found');
74
+ });
75
+ it('custom method handler works', async () => {
76
+ const sock = await createServer();
77
+ server.registerMethod('echo', (params) => ({ echoed: params }));
78
+ const { client } = await createClient(sock);
79
+ const result = await client.request('echo', { message: 'hello' });
80
+ expect(result.echoed).toEqual({ message: 'hello' });
81
+ });
82
+ it('multiple clients can connect', async () => {
83
+ const sock = await createServer();
84
+ const { client: c1 } = await createClient(sock);
85
+ const { client: c2 } = await createClient(sock);
86
+ const r1 = await c1.request('ping');
87
+ const r2 = await c2.request('ping');
88
+ expect(r1.version).toBe('0.5.0');
89
+ expect(r2.version).toBe('0.5.0');
90
+ expect(server.getClientCount()).toBe(2);
91
+ });
92
+ it('subscribe-events enables notifications', async () => {
93
+ const sock = await createServer();
94
+ const { client } = await createClient(sock);
95
+ const received = [];
96
+ client.onNotification((method, params) => {
97
+ received.push({ method, params });
98
+ });
99
+ await client.request('subscribe-events');
100
+ // Broadcast a notification
101
+ server.broadcast({
102
+ jsonrpc: '2.0',
103
+ method: 'event.test',
104
+ params: { foo: 'bar' },
105
+ });
106
+ // Give the event a moment to arrive
107
+ await new Promise((r) => setTimeout(r, 50));
108
+ expect(received.length).toBe(1);
109
+ expect(received[0].method).toBe('event.test');
110
+ expect(received[0].params).toEqual({ foo: 'bar' });
111
+ });
112
+ it('shutdown request triggers shutdown event', async () => {
113
+ const sock = await createServer();
114
+ const { client } = await createClient(sock);
115
+ let shutdownRequested = false;
116
+ server.on('shutdown-requested', () => {
117
+ shutdownRequested = true;
118
+ });
119
+ const result = await client.request('shutdown');
120
+ expect(result.ok).toBe(true);
121
+ // Wait for nextTick
122
+ await new Promise((r) => setTimeout(r, 10));
123
+ expect(shutdownRequested).toBe(true);
124
+ });
125
+ it('client disconnect is detected by server', async () => {
126
+ const sock = await createServer();
127
+ const { client } = await createClient(sock);
128
+ expect(server.getClientCount()).toBe(1);
129
+ let disconnected = false;
130
+ server.on('client-disconnected', () => {
131
+ disconnected = true;
132
+ });
133
+ client.close();
134
+ // Wait for the close event to propagate
135
+ await new Promise((r) => setTimeout(r, 50));
136
+ expect(server.getClientCount()).toBe(0);
137
+ expect(disconnected).toBe(true);
138
+ });
139
+ });
140
+ //# sourceMappingURL=ipc.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.test.js","sourceRoot":"","sources":["../../src/service/ipc.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * IPC client — connects to the daemon's Unix domain socket
3
+ * and speaks JSON-RPC 2.0 over newline-delimited transport.
4
+ *
5
+ * Used by CLI attach clients and (later) session workers.
6
+ */
7
+ import type { HelloResult } from './types.js';
8
+ export declare class IpcClient {
9
+ private socket;
10
+ private nextId;
11
+ private pending;
12
+ private notificationHandler?;
13
+ private buffer;
14
+ /** Connect to the daemon socket and perform the hello handshake. */
15
+ connect(socketPath: string): Promise<HelloResult>;
16
+ /** Send a JSON-RPC request and wait for the response. */
17
+ request<T = unknown>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
18
+ /** Send a JSON-RPC notification (fire-and-forget, no response expected). */
19
+ notify(method: string, params?: Record<string, unknown>): void;
20
+ /** Register a handler for server-pushed notifications. */
21
+ onNotification(handler: (method: string, params: Record<string, unknown>) => void): void;
22
+ /** Close the connection. */
23
+ close(): void;
24
+ get isConnected(): boolean;
25
+ private setupDataHandler;
26
+ private handleLine;
27
+ }
28
+ export declare class IpcDisconnectedError extends Error {
29
+ code: string;
30
+ constructor(message: string);
31
+ }
32
+ export declare class RpcError extends Error {
33
+ rpcCode: number;
34
+ constructor(message: string, rpcCode: number);
35
+ }
36
+ //# sourceMappingURL=ipcClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipcClient.d.ts","sourceRoot":"","sources":["../../src/service/ipcClient.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAIV,WAAW,EACZ,MAAM,YAAY,CAAC;AAYpB,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAA8C;IAC7D,OAAO,CAAC,mBAAmB,CAAC,CAA4D;IACxF,OAAO,CAAC,MAAM,CAAM;IAEpB,oEAAoE;IAC9D,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAuCvD,yDAAyD;IACnD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC;IA6B5G,4EAA4E;IAC5E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAY9D,0DAA0D;IAC1D,cAAc,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI;IAIxF,4BAA4B;IAC5B,KAAK,IAAI,IAAI;IAOb,IAAI,WAAW,IAAI,OAAO,CAEzB;IAMD,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,UAAU;CA8BnB;AAMD,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,SAAsB;gBACd,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,QAAS,SAAQ,KAAK;IAGxB,OAAO,EAAE,MAAM;gBADtB,OAAO,EAAE,MAAM,EACR,OAAO,EAAE,MAAM;CAKzB"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * IPC client — connects to the daemon's Unix domain socket
3
+ * and speaks JSON-RPC 2.0 over newline-delimited transport.
4
+ *
5
+ * Used by CLI attach clients and (later) session workers.
6
+ */
7
+ import { connect } from 'net';
8
+ import { IPC_VERSION, BUILD_ID } from './types.js';
9
+ // Package version — matches package.json
10
+ const PACKAGE_VERSION = '0.5.0';
11
+ export class IpcClient {
12
+ socket = null;
13
+ nextId = 1;
14
+ pending = new Map();
15
+ notificationHandler;
16
+ buffer = '';
17
+ /** Connect to the daemon socket and perform the hello handshake. */
18
+ async connect(socketPath) {
19
+ return new Promise((resolve, reject) => {
20
+ const socket = connect(socketPath);
21
+ let connected = false;
22
+ socket.on('connect', () => {
23
+ connected = true;
24
+ this.socket = socket;
25
+ this.setupDataHandler(socket);
26
+ // Perform hello handshake
27
+ this.request('hello', {
28
+ role: 'cli',
29
+ version: PACKAGE_VERSION,
30
+ ipcVersion: IPC_VERSION,
31
+ buildId: BUILD_ID,
32
+ })
33
+ .then(resolve)
34
+ .catch(reject);
35
+ });
36
+ socket.on('error', (err) => {
37
+ if (!connected) {
38
+ reject(err);
39
+ }
40
+ });
41
+ socket.on('close', () => {
42
+ // Reject all pending requests
43
+ for (const [, pending] of this.pending) {
44
+ clearTimeout(pending.timer);
45
+ pending.reject(new IpcDisconnectedError('IPC connection closed'));
46
+ }
47
+ this.pending.clear();
48
+ this.socket = null;
49
+ });
50
+ });
51
+ }
52
+ /** Send a JSON-RPC request and wait for the response. */
53
+ async request(method, params, timeoutMs = 10_000) {
54
+ if (!this.socket) {
55
+ throw new IpcDisconnectedError('Not connected');
56
+ }
57
+ const id = this.nextId++;
58
+ const request = {
59
+ jsonrpc: '2.0',
60
+ id,
61
+ method,
62
+ params: params ?? {},
63
+ };
64
+ return new Promise((resolve, reject) => {
65
+ const timer = setTimeout(() => {
66
+ this.pending.delete(id);
67
+ reject(new Error(`RPC timeout: ${method}`));
68
+ }, timeoutMs);
69
+ this.pending.set(id, {
70
+ resolve: resolve,
71
+ reject,
72
+ timer,
73
+ });
74
+ this.socket.write(JSON.stringify(request) + '\n');
75
+ });
76
+ }
77
+ /** Send a JSON-RPC notification (fire-and-forget, no response expected). */
78
+ notify(method, params) {
79
+ if (!this.socket)
80
+ return;
81
+ const notification = {
82
+ jsonrpc: '2.0',
83
+ method,
84
+ params: params ?? {},
85
+ };
86
+ this.socket.write(JSON.stringify(notification) + '\n');
87
+ }
88
+ /** Register a handler for server-pushed notifications. */
89
+ onNotification(handler) {
90
+ this.notificationHandler = handler;
91
+ }
92
+ /** Close the connection. */
93
+ close() {
94
+ if (this.socket) {
95
+ this.socket.destroy();
96
+ this.socket = null;
97
+ }
98
+ }
99
+ get isConnected() {
100
+ return this.socket !== null && !this.socket.destroyed;
101
+ }
102
+ // -----------------------------------------------------------------------
103
+ // Data parsing
104
+ // -----------------------------------------------------------------------
105
+ setupDataHandler(socket) {
106
+ socket.on('data', (data) => {
107
+ this.buffer += data.toString();
108
+ let newlineIndex;
109
+ while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {
110
+ const line = this.buffer.slice(0, newlineIndex);
111
+ this.buffer = this.buffer.slice(newlineIndex + 1);
112
+ if (line.trim()) {
113
+ this.handleLine(line);
114
+ }
115
+ }
116
+ });
117
+ }
118
+ handleLine(line) {
119
+ let msg;
120
+ try {
121
+ msg = JSON.parse(line);
122
+ }
123
+ catch {
124
+ return; // Ignore malformed messages
125
+ }
126
+ // JSON-RPC response (has id + result/error)
127
+ if (msg.id != null && ('result' in msg || 'error' in msg)) {
128
+ const pending = this.pending.get(msg.id);
129
+ if (pending) {
130
+ this.pending.delete(msg.id);
131
+ clearTimeout(pending.timer);
132
+ if (msg.error) {
133
+ const err = new RpcError(msg.error.message, msg.error.code);
134
+ pending.reject(err);
135
+ }
136
+ else {
137
+ pending.resolve(msg.result);
138
+ }
139
+ }
140
+ return;
141
+ }
142
+ // JSON-RPC notification (has method, no id)
143
+ if (msg.method && msg.id == null) {
144
+ this.notificationHandler?.(msg.method, msg.params ?? {});
145
+ return;
146
+ }
147
+ }
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Custom errors
151
+ // ---------------------------------------------------------------------------
152
+ export class IpcDisconnectedError extends Error {
153
+ code = 'IPC_DISCONNECTED';
154
+ constructor(message) {
155
+ super(message);
156
+ this.name = 'IpcDisconnectedError';
157
+ }
158
+ }
159
+ export class RpcError extends Error {
160
+ rpcCode;
161
+ constructor(message, rpcCode) {
162
+ super(message);
163
+ this.rpcCode = rpcCode;
164
+ this.name = 'RpcError';
165
+ }
166
+ }
167
+ //# sourceMappingURL=ipcClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipcClient.js","sourceRoot":"","sources":["../../src/service/ipcClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * IPC server — JSON-RPC 2.0 over newline-delimited Unix domain socket.
3
+ *
4
+ * The daemon creates this server on startup. CLI clients and session workers
5
+ * connect to it as JSON-RPC clients.
6
+ */
7
+ import { type Socket } from 'net';
8
+ import { EventEmitter } from 'events';
9
+ import type { JsonRpcNotification, StatusResult } from './types.js';
10
+ export interface IpcClient {
11
+ id: string;
12
+ role?: 'cli' | 'worker';
13
+ sessionId?: string;
14
+ subscribed: boolean;
15
+ socket: Socket;
16
+ }
17
+ export type MethodHandler = (params: Record<string, unknown>, client: IpcClient) => Promise<unknown> | unknown;
18
+ export declare class IpcServer extends EventEmitter {
19
+ private server;
20
+ private clients;
21
+ private methods;
22
+ private nextClientId;
23
+ private startedAt;
24
+ private version;
25
+ private statusProvider?;
26
+ constructor(opts: {
27
+ version: string;
28
+ });
29
+ /** Register a JSON-RPC method handler. */
30
+ registerMethod(method: string, handler: MethodHandler): void;
31
+ /** Set a provider for live status data (wired after AgentConnection starts). */
32
+ setStatusProvider(fn: () => StatusResult): void;
33
+ /** Start listening on the given Unix socket path. */
34
+ listen(socketPath: string): Promise<void>;
35
+ /** Gracefully close the server and all client connections. */
36
+ close(): Promise<void>;
37
+ /** Get the number of connected clients. */
38
+ getClientCount(): number;
39
+ /** Broadcast a JSON-RPC notification to all subscribed CLI clients. */
40
+ broadcast(notification: JsonRpcNotification): void;
41
+ /** Send a JSON-RPC request to a specific client (for daemon→worker commands). */
42
+ sendRequest(clientId: string, method: string, params: Record<string, unknown>, id: number | string): boolean;
43
+ private onConnection;
44
+ private handleLine;
45
+ private handleHello;
46
+ private handlePing;
47
+ private handleStatus;
48
+ private handleShutdown;
49
+ private handleSubscribe;
50
+ private sendMessage;
51
+ private sendError;
52
+ }
53
+ //# sourceMappingURL=ipcServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipcServer.d.ts","sourceRoot":"","sources":["../../src/service/ipcServer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA6B,KAAK,MAAM,EAAE,MAAM,KAAK,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAGV,mBAAmB,EAKnB,YAAY,EACb,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,MAAM,MAAM,aAAa,GAAG,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,SAAS,KACd,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAMhC,qBAAa,SAAU,SAAQ,YAAY;IACzC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAC,CAAqB;gBAEhC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE;IAarC,0CAA0C;IAC1C,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI;IAI5D,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,EAAE,MAAM,YAAY,GAAG,IAAI;IAI/C,qDAAqD;IAC/C,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/C,8DAA8D;IACxD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB5B,2CAA2C;IAC3C,cAAc,IAAI,MAAM;IAIxB,uEAAuE;IACvE,SAAS,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;IAQlD,iFAAiF;IACjF,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO;IAY5G,OAAO,CAAC,YAAY;YAiCN,UAAU;IAkDxB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,SAAS;CAQlB"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * IPC server — JSON-RPC 2.0 over newline-delimited Unix domain socket.
3
+ *
4
+ * The daemon creates this server on startup. CLI clients and session workers
5
+ * connect to it as JSON-RPC clients.
6
+ */
7
+ import { createServer } from 'net';
8
+ import { unlinkSync, chmodSync } from 'fs';
9
+ import { EventEmitter } from 'events';
10
+ import { IPC_VERSION, BUILD_ID, RpcErrorCode } from './types.js';
11
+ // ---------------------------------------------------------------------------
12
+ // IpcServer
13
+ // ---------------------------------------------------------------------------
14
+ export class IpcServer extends EventEmitter {
15
+ server = null;
16
+ clients = new Map();
17
+ methods = new Map();
18
+ nextClientId = 1;
19
+ startedAt;
20
+ version;
21
+ statusProvider;
22
+ constructor(opts) {
23
+ super();
24
+ this.version = opts.version;
25
+ this.startedAt = new Date();
26
+ // Register built-in methods
27
+ this.registerMethod('hello', (params) => this.handleHello(params));
28
+ this.registerMethod('ping', () => this.handlePing());
29
+ this.registerMethod('status', () => this.handleStatus());
30
+ this.registerMethod('shutdown', (_params, client) => this.handleShutdown(client));
31
+ this.registerMethod('subscribe-events', (_params, client) => this.handleSubscribe(client));
32
+ }
33
+ /** Register a JSON-RPC method handler. */
34
+ registerMethod(method, handler) {
35
+ this.methods.set(method, handler);
36
+ }
37
+ /** Set a provider for live status data (wired after AgentConnection starts). */
38
+ setStatusProvider(fn) {
39
+ this.statusProvider = fn;
40
+ }
41
+ /** Start listening on the given Unix socket path. */
42
+ async listen(socketPath) {
43
+ // Remove stale socket file if present
44
+ try {
45
+ unlinkSync(socketPath);
46
+ }
47
+ catch (err) {
48
+ if (err.code !== 'ENOENT')
49
+ throw err;
50
+ }
51
+ return new Promise((resolve, reject) => {
52
+ this.server = createServer((socket) => this.onConnection(socket));
53
+ this.server.on('error', (err) => {
54
+ reject(err);
55
+ });
56
+ this.server.listen(socketPath, () => {
57
+ // Set socket permissions to owner-only
58
+ try {
59
+ chmodSync(socketPath, 0o600);
60
+ }
61
+ catch {
62
+ // Best effort — may fail on some systems
63
+ }
64
+ resolve();
65
+ });
66
+ });
67
+ }
68
+ /** Gracefully close the server and all client connections. */
69
+ async close() {
70
+ // Notify subscribed clients
71
+ this.broadcast({
72
+ jsonrpc: '2.0',
73
+ method: 'event.daemonStatus',
74
+ params: { shutting_down: true },
75
+ });
76
+ // Close all client sockets
77
+ for (const client of this.clients.values()) {
78
+ client.socket.destroy();
79
+ }
80
+ this.clients.clear();
81
+ return new Promise((resolve) => {
82
+ if (this.server) {
83
+ this.server.close(() => resolve());
84
+ }
85
+ else {
86
+ resolve();
87
+ }
88
+ });
89
+ }
90
+ /** Get the number of connected clients. */
91
+ getClientCount() {
92
+ return this.clients.size;
93
+ }
94
+ /** Broadcast a JSON-RPC notification to all subscribed CLI clients. */
95
+ broadcast(notification) {
96
+ for (const client of this.clients.values()) {
97
+ if (client.subscribed) {
98
+ this.sendMessage(client.socket, notification);
99
+ }
100
+ }
101
+ }
102
+ /** Send a JSON-RPC request to a specific client (for daemon→worker commands). */
103
+ sendRequest(clientId, method, params, id) {
104
+ const client = this.clients.get(clientId);
105
+ if (!client)
106
+ return false;
107
+ const request = { jsonrpc: '2.0', id, method, params };
108
+ this.sendMessage(client.socket, request);
109
+ return true;
110
+ }
111
+ // -----------------------------------------------------------------------
112
+ // Connection handling
113
+ // -----------------------------------------------------------------------
114
+ onConnection(socket) {
115
+ const clientId = `c${this.nextClientId++}`;
116
+ const client = {
117
+ id: clientId,
118
+ subscribed: false,
119
+ socket,
120
+ };
121
+ this.clients.set(clientId, client);
122
+ let buffer = '';
123
+ socket.on('data', (data) => {
124
+ buffer += data.toString();
125
+ let newlineIndex;
126
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
127
+ const line = buffer.slice(0, newlineIndex);
128
+ buffer = buffer.slice(newlineIndex + 1);
129
+ if (line.trim()) {
130
+ this.handleLine(line, client);
131
+ }
132
+ }
133
+ });
134
+ socket.on('close', () => {
135
+ this.clients.delete(clientId);
136
+ this.emit('client-disconnected', client);
137
+ });
138
+ socket.on('error', () => {
139
+ this.clients.delete(clientId);
140
+ });
141
+ }
142
+ async handleLine(line, client) {
143
+ let msg;
144
+ try {
145
+ msg = JSON.parse(line);
146
+ }
147
+ catch {
148
+ this.sendError(client.socket, null, RpcErrorCode.PARSE_ERROR, 'Parse error');
149
+ return;
150
+ }
151
+ // Ignore responses (we don't expect them from clients in this direction, but workers may ack)
152
+ if ('result' in msg || 'error' in msg) {
153
+ this.emit('response', msg, client);
154
+ return;
155
+ }
156
+ // Must have a method
157
+ if (!('method' in msg) || typeof msg.method !== 'string') {
158
+ this.sendError(client.socket, msg.id ?? null, RpcErrorCode.INVALID_REQUEST, 'Invalid request');
159
+ return;
160
+ }
161
+ const handler = this.methods.get(msg.method);
162
+ if (!handler) {
163
+ if ('id' in msg && msg.id != null) {
164
+ this.sendError(client.socket, msg.id, RpcErrorCode.METHOD_NOT_FOUND, `Method not found: ${msg.method}`);
165
+ }
166
+ return;
167
+ }
168
+ const params = ('params' in msg ? msg.params : {});
169
+ try {
170
+ const result = await handler(params, client);
171
+ // Only send response if it's a request (has id), not a notification
172
+ if ('id' in msg && msg.id != null) {
173
+ const response = { jsonrpc: '2.0', id: msg.id, result };
174
+ this.sendMessage(client.socket, response);
175
+ }
176
+ }
177
+ catch (err) {
178
+ if ('id' in msg && msg.id != null) {
179
+ const code = err.rpcCode ?? RpcErrorCode.INVALID_PARAMS;
180
+ this.sendError(client.socket, msg.id, code, err.message ?? 'Internal error');
181
+ }
182
+ }
183
+ }
184
+ // -----------------------------------------------------------------------
185
+ // Built-in method handlers
186
+ // -----------------------------------------------------------------------
187
+ handleHello(_params) {
188
+ // hello always succeeds — client decides based on version fields
189
+ return {
190
+ daemonVersion: this.version,
191
+ daemonIpcVersion: IPC_VERSION,
192
+ daemonBuildId: BUILD_ID,
193
+ daemonPid: process.pid,
194
+ };
195
+ }
196
+ handlePing() {
197
+ const uptimeMs = Date.now() - this.startedAt.getTime();
198
+ return {
199
+ version: this.version,
200
+ ipcVersion: IPC_VERSION,
201
+ buildId: BUILD_ID,
202
+ uptime: Math.floor(uptimeMs / 1000),
203
+ };
204
+ }
205
+ handleStatus() {
206
+ if (this.statusProvider)
207
+ return this.statusProvider();
208
+ const uptimeMs = Date.now() - this.startedAt.getTime();
209
+ return {
210
+ version: this.version,
211
+ pid: process.pid,
212
+ uptime: Math.floor(uptimeMs / 1000),
213
+ connectionState: 'disconnected',
214
+ peerCount: 0,
215
+ activeSessions: 0,
216
+ managedRepos: 0,
217
+ };
218
+ }
219
+ handleShutdown(_client) {
220
+ // Schedule shutdown on next tick so the response gets sent first
221
+ process.nextTick(() => this.emit('shutdown-requested'));
222
+ return { ok: true };
223
+ }
224
+ handleSubscribe(client) {
225
+ client.subscribed = true;
226
+ return { subscribed: true };
227
+ }
228
+ // -----------------------------------------------------------------------
229
+ // Wire helpers
230
+ // -----------------------------------------------------------------------
231
+ sendMessage(socket, msg) {
232
+ if (!socket.destroyed) {
233
+ socket.write(JSON.stringify(msg) + '\n');
234
+ }
235
+ }
236
+ sendError(socket, id, code, message) {
237
+ const response = {
238
+ jsonrpc: '2.0',
239
+ id: id ?? 0,
240
+ error: { code, message },
241
+ };
242
+ this.sendMessage(socket, response);
243
+ }
244
+ }
245
+ //# sourceMappingURL=ipcServer.js.map