@cello-protocol/daemon 0.0.3 → 0.0.5
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/agent-loader.d.ts +41 -0
- package/dist/agent-loader.d.ts.map +1 -0
- package/dist/agent-loader.js +94 -0
- package/dist/agent-loader.js.map +1 -0
- package/dist/bin/cello-daemon.d.ts +13 -0
- package/dist/bin/cello-daemon.d.ts.map +1 -0
- package/dist/bin/cello-daemon.js +170 -0
- package/dist/bin/cello-daemon.js.map +1 -0
- package/dist/cello-node-transport-dialer.d.ts +59 -0
- package/dist/cello-node-transport-dialer.d.ts.map +1 -0
- package/dist/cello-node-transport-dialer.js +108 -0
- package/dist/cello-node-transport-dialer.js.map +1 -0
- package/dist/challenge-verifier.d.ts +12 -0
- package/dist/challenge-verifier.d.ts.map +1 -0
- package/dist/challenge-verifier.js +11 -0
- package/dist/challenge-verifier.js.map +1 -0
- package/dist/connect-or-start.d.ts +25 -0
- package/dist/connect-or-start.d.ts.map +1 -0
- package/dist/connect-or-start.js +117 -0
- package/dist/connect-or-start.js.map +1 -0
- package/dist/content-park-client.d.ts +49 -0
- package/dist/content-park-client.d.ts.map +1 -0
- package/dist/content-park-client.js +196 -0
- package/dist/content-park-client.js.map +1 -0
- package/dist/daemon.d.ts +65 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +3202 -0
- package/dist/daemon.js.map +1 -0
- package/dist/directory-bootstrap.d.ts +55 -0
- package/dist/directory-bootstrap.d.ts.map +1 -0
- package/dist/directory-bootstrap.js +102 -0
- package/dist/directory-bootstrap.js.map +1 -0
- package/dist/file-manifest-provider.d.ts +18 -0
- package/dist/file-manifest-provider.d.ts.map +1 -0
- package/dist/file-manifest-provider.js +72 -0
- package/dist/file-manifest-provider.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-client.d.ts +31 -0
- package/dist/ipc-client.d.ts.map +1 -0
- package/dist/ipc-client.js +112 -0
- package/dist/ipc-client.js.map +1 -0
- package/dist/ipc-server.d.ts +49 -0
- package/dist/ipc-server.d.ts.map +1 -0
- package/dist/ipc-server.js +268 -0
- package/dist/ipc-server.js.map +1 -0
- package/dist/lock-file.d.ts +27 -0
- package/dist/lock-file.d.ts.map +1 -0
- package/dist/lock-file.js +84 -0
- package/dist/lock-file.js.map +1 -0
- package/dist/manifest-loader.d.ts +33 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +70 -0
- package/dist/manifest-loader.js.map +1 -0
- package/dist/manifest-poll-scheduler.d.ts +31 -0
- package/dist/manifest-poll-scheduler.d.ts.map +1 -0
- package/dist/manifest-poll-scheduler.js +59 -0
- package/dist/manifest-poll-scheduler.js.map +1 -0
- package/dist/manifest-version-store-file.d.ts +18 -0
- package/dist/manifest-version-store-file.d.ts.map +1 -0
- package/dist/manifest-version-store-file.js +40 -0
- package/dist/manifest-version-store-file.js.map +1 -0
- package/dist/manifest-version-store.d.ts +14 -0
- package/dist/manifest-version-store.d.ts.map +1 -0
- package/dist/manifest-version-store.js +13 -0
- package/dist/manifest-version-store.js.map +1 -0
- package/dist/network-directory-node.d.ts +94 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +626 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/nonce-dedup.d.ts +68 -0
- package/dist/nonce-dedup.d.ts.map +1 -0
- package/dist/nonce-dedup.js +204 -0
- package/dist/nonce-dedup.js.map +1 -0
- package/dist/notification-dispatcher.d.ts +65 -0
- package/dist/notification-dispatcher.d.ts.map +1 -0
- package/dist/notification-dispatcher.js +138 -0
- package/dist/notification-dispatcher.js.map +1 -0
- package/dist/registration-context.d.ts +69 -0
- package/dist/registration-context.d.ts.map +1 -0
- package/dist/registration-context.js +118 -0
- package/dist/registration-context.js.map +1 -0
- package/dist/registration-manager.d.ts +72 -0
- package/dist/registration-manager.d.ts.map +1 -0
- package/dist/registration-manager.js +267 -0
- package/dist/registration-manager.js.map +1 -0
- package/dist/registration-persistence.d.ts +131 -0
- package/dist/registration-persistence.d.ts.map +1 -0
- package/dist/registration-persistence.js +233 -0
- package/dist/registration-persistence.js.map +1 -0
- package/dist/retry-queue.d.ts +144 -0
- package/dist/retry-queue.d.ts.map +1 -0
- package/dist/retry-queue.js +444 -0
- package/dist/retry-queue.js.map +1 -0
- package/dist/seal-frontier-verify.d.ts +58 -0
- package/dist/seal-frontier-verify.d.ts.map +1 -0
- package/dist/seal-frontier-verify.js +87 -0
- package/dist/seal-frontier-verify.js.map +1 -0
- package/dist/seal-legibility-tbs.d.ts +25 -0
- package/dist/seal-legibility-tbs.d.ts.map +1 -0
- package/dist/seal-legibility-tbs.js +78 -0
- package/dist/seal-legibility-tbs.js.map +1 -0
- package/dist/seal-upgrade.d.ts +90 -0
- package/dist/seal-upgrade.d.ts.map +1 -0
- package/dist/seal-upgrade.js +178 -0
- package/dist/seal-upgrade.js.map +1 -0
- package/dist/session-assignment-parser.d.ts +22 -0
- package/dist/session-assignment-parser.d.ts.map +1 -0
- package/dist/session-assignment-parser.js +139 -0
- package/dist/session-assignment-parser.js.map +1 -0
- package/dist/session-ceremony.d.ts +156 -0
- package/dist/session-ceremony.d.ts.map +1 -0
- package/dist/session-ceremony.js +447 -0
- package/dist/session-ceremony.js.map +1 -0
- package/dist/session-connection-gater.d.ts +91 -0
- package/dist/session-connection-gater.d.ts.map +1 -0
- package/dist/session-connection-gater.js +146 -0
- package/dist/session-connection-gater.js.map +1 -0
- package/dist/session-node-manager.d.ts +585 -0
- package/dist/session-node-manager.d.ts.map +1 -0
- package/dist/session-node-manager.js +2609 -0
- package/dist/session-node-manager.js.map +1 -0
- package/dist/session-relay-client.d.ts +101 -0
- package/dist/session-relay-client.d.ts.map +1 -0
- package/dist/session-relay-client.js +520 -0
- package/dist/session-relay-client.js.map +1 -0
- package/dist/session-tree.d.ts +80 -0
- package/dist/session-tree.d.ts.map +1 -0
- package/dist/session-tree.js +123 -0
- package/dist/session-tree.js.map +1 -0
- package/dist/signaling-connect.d.ts +83 -0
- package/dist/signaling-connect.d.ts.map +1 -0
- package/dist/signaling-connect.js +266 -0
- package/dist/signaling-connect.js.map +1 -0
- package/dist/transcript-cipher.d.ts +31 -0
- package/dist/transcript-cipher.d.ts.map +1 -0
- package/dist/transcript-cipher.js +74 -0
- package/dist/transcript-cipher.js.map +1 -0
- package/dist/transport-composition.d.ts +31 -0
- package/dist/transport-composition.d.ts.map +1 -0
- package/dist/transport-composition.js +55 -0
- package/dist/transport-composition.js.map +1 -0
- package/dist/transport-selector.d.ts +189 -0
- package/dist/transport-selector.d.ts.map +1 -0
- package/dist/transport-selector.js +195 -0
- package/dist/transport-selector.js.map +1 -0
- package/dist/types.d.ts +265 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC client for connecting to the CELLO daemon.
|
|
3
|
+
*
|
|
4
|
+
* Pseudocode:
|
|
5
|
+
* 1. connectToDaemon(socketPath):
|
|
6
|
+
* a. Create net.Socket
|
|
7
|
+
* b. Connect to Unix domain socket
|
|
8
|
+
* c. Return IpcClient with send(method, params) → Promise<result>
|
|
9
|
+
*
|
|
10
|
+
* 2. send(method, params):
|
|
11
|
+
* a. Generate unique request ID
|
|
12
|
+
* b. Write JSON + newline to socket
|
|
13
|
+
* c. Wait for response with matching ID
|
|
14
|
+
* d. Return result or throw error
|
|
15
|
+
*/
|
|
16
|
+
import { createConnection } from "node:net";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
export class IpcError extends Error {
|
|
19
|
+
code;
|
|
20
|
+
guidance;
|
|
21
|
+
constructor(code, message, guidance) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "IpcError";
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.guidance = guidance;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function connectToDaemon(socketPath, opts) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const socket = createConnection(socketPath);
|
|
31
|
+
const pending = new Map();
|
|
32
|
+
let buffer = "";
|
|
33
|
+
let notificationHandler = null;
|
|
34
|
+
const client = {
|
|
35
|
+
send(method, params) {
|
|
36
|
+
return new Promise((res, rej) => {
|
|
37
|
+
if (socket.destroyed) {
|
|
38
|
+
rej(new Error("Socket closed"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const id = randomUUID();
|
|
42
|
+
pending.set(id, { resolve: res, reject: rej });
|
|
43
|
+
const request = { id, method, params };
|
|
44
|
+
socket.write(JSON.stringify(request) + "\n");
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
close() {
|
|
48
|
+
socket.end();
|
|
49
|
+
},
|
|
50
|
+
onNotification(handler) {
|
|
51
|
+
notificationHandler = handler;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
socket.on("connect", () => {
|
|
55
|
+
resolve(client);
|
|
56
|
+
});
|
|
57
|
+
socket.on("error", (err) => {
|
|
58
|
+
reject(err);
|
|
59
|
+
for (const [, p] of pending) {
|
|
60
|
+
p.reject(err);
|
|
61
|
+
}
|
|
62
|
+
pending.clear();
|
|
63
|
+
});
|
|
64
|
+
socket.on("data", (chunk) => {
|
|
65
|
+
buffer += chunk.toString("utf-8");
|
|
66
|
+
let newlineIdx;
|
|
67
|
+
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
68
|
+
const line = buffer.slice(0, newlineIdx);
|
|
69
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
70
|
+
if (line.trim().length === 0)
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const frame = JSON.parse(line);
|
|
74
|
+
// Notifications have a "notification" field — they are server-initiated
|
|
75
|
+
// and never correlate to a request.
|
|
76
|
+
if ("notification" in frame) {
|
|
77
|
+
if (notificationHandler) {
|
|
78
|
+
notificationHandler(frame);
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Regular response — correlate by id
|
|
83
|
+
const response = frame;
|
|
84
|
+
const p = pending.get(response.id);
|
|
85
|
+
if (p) {
|
|
86
|
+
pending.delete(response.id);
|
|
87
|
+
if ("error" in response) {
|
|
88
|
+
const errResp = response;
|
|
89
|
+
p.reject(new IpcError(errResp.error.code, errResp.error.message, errResp.error.guidance));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
p.resolve(response.result);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
if (opts?.onFrameError) {
|
|
99
|
+
opts.onFrameError(msg);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
socket.on("close", () => {
|
|
105
|
+
for (const [, p] of pending) {
|
|
106
|
+
p.reject(new Error("Connection closed"));
|
|
107
|
+
}
|
|
108
|
+
pending.clear();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=ipc-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipc-client.js","sourceRoot":"","sources":["../src/ipc-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC server for the CELLO daemon.
|
|
3
|
+
*
|
|
4
|
+
* Pseudocode:
|
|
5
|
+
* 1. createIpcServer(config, handlers):
|
|
6
|
+
* a. Create net.Server bound to Unix domain socket
|
|
7
|
+
* b. Set socket permissions to 0o600 (SI-001: owner-only access)
|
|
8
|
+
* c. On connection:
|
|
9
|
+
* - Check connectionCount < maxConnections (16)
|
|
10
|
+
* - If limit reached, send error frame and close
|
|
11
|
+
* - Otherwise, increment counter, assign connectionId
|
|
12
|
+
* - Log daemon.ipc.connected
|
|
13
|
+
* - Set up newline-delimited JSON parser on the socket
|
|
14
|
+
* - For each parsed request, dispatch to handler map
|
|
15
|
+
* - On close, decrement counter, log daemon.ipc.disconnected
|
|
16
|
+
* d. Return server handle with start() and stop() methods
|
|
17
|
+
*
|
|
18
|
+
* 2. IPC framing: JSON-newline-delimited
|
|
19
|
+
* - Each message is a single JSON object followed by \n
|
|
20
|
+
* - Requests: {id: string, method: string, params?: object}
|
|
21
|
+
* - Responses: {id: string, result: any} or {id: string, error: {code, message, guidance}}
|
|
22
|
+
*
|
|
23
|
+
* 3. Graceful shutdown:
|
|
24
|
+
* a. Stop accepting new connections
|
|
25
|
+
* b. Wait for in-flight requests to complete (with timeout)
|
|
26
|
+
* c. Send shutdown frame to all connected clients
|
|
27
|
+
* d. Close all connections
|
|
28
|
+
* e. Unlink socket file
|
|
29
|
+
*/
|
|
30
|
+
import type { Logger, IpcNotification } from "./types.js";
|
|
31
|
+
export type IpcHandler = (params: Record<string, unknown> | undefined, connectionId: string) => Promise<unknown>;
|
|
32
|
+
export interface IpcServerConfig {
|
|
33
|
+
socketPath: string;
|
|
34
|
+
maxConnections: number;
|
|
35
|
+
logger: Logger;
|
|
36
|
+
}
|
|
37
|
+
export type IpcDisconnectHandler = (connectionId: string) => void;
|
|
38
|
+
export interface IpcServer {
|
|
39
|
+
start(): Promise<void>;
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
getConnectionCount(): number;
|
|
42
|
+
onDisconnect(handler: IpcDisconnectHandler): void;
|
|
43
|
+
/** Write a notification to a specific connection. Returns false on write failure. */
|
|
44
|
+
sendNotification(connectionId: string, notification: IpcNotification): boolean;
|
|
45
|
+
/** Return all active connection IDs. */
|
|
46
|
+
getConnectionIds(): string[];
|
|
47
|
+
}
|
|
48
|
+
export declare function createIpcServer(config: IpcServerConfig, handlers: Map<string, IpcHandler>): IpcServer;
|
|
49
|
+
//# sourceMappingURL=ipc-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../src/ipc-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,KAAK,EAAE,MAAM,EAA2B,eAAe,EAAE,MAAM,YAAY,CAAC;AAEnF,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEjH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;AAElE,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,kBAAkB,IAAI,MAAM,CAAC;IAC7B,YAAY,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD,qFAAqF;IACrF,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,GAAG,OAAO,CAAC;IAC/E,wCAAwC;IACxC,gBAAgB,IAAI,MAAM,EAAE,CAAC;CAC9B;AASD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAChC,SAAS,CAyPX"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC server for the CELLO daemon.
|
|
3
|
+
*
|
|
4
|
+
* Pseudocode:
|
|
5
|
+
* 1. createIpcServer(config, handlers):
|
|
6
|
+
* a. Create net.Server bound to Unix domain socket
|
|
7
|
+
* b. Set socket permissions to 0o600 (SI-001: owner-only access)
|
|
8
|
+
* c. On connection:
|
|
9
|
+
* - Check connectionCount < maxConnections (16)
|
|
10
|
+
* - If limit reached, send error frame and close
|
|
11
|
+
* - Otherwise, increment counter, assign connectionId
|
|
12
|
+
* - Log daemon.ipc.connected
|
|
13
|
+
* - Set up newline-delimited JSON parser on the socket
|
|
14
|
+
* - For each parsed request, dispatch to handler map
|
|
15
|
+
* - On close, decrement counter, log daemon.ipc.disconnected
|
|
16
|
+
* d. Return server handle with start() and stop() methods
|
|
17
|
+
*
|
|
18
|
+
* 2. IPC framing: JSON-newline-delimited
|
|
19
|
+
* - Each message is a single JSON object followed by \n
|
|
20
|
+
* - Requests: {id: string, method: string, params?: object}
|
|
21
|
+
* - Responses: {id: string, result: any} or {id: string, error: {code, message, guidance}}
|
|
22
|
+
*
|
|
23
|
+
* 3. Graceful shutdown:
|
|
24
|
+
* a. Stop accepting new connections
|
|
25
|
+
* b. Wait for in-flight requests to complete (with timeout)
|
|
26
|
+
* c. Send shutdown frame to all connected clients
|
|
27
|
+
* d. Close all connections
|
|
28
|
+
* e. Unlink socket file
|
|
29
|
+
*/
|
|
30
|
+
import { createServer } from "node:net";
|
|
31
|
+
import { chmod, unlink } from "node:fs/promises";
|
|
32
|
+
import { randomUUID } from "node:crypto";
|
|
33
|
+
export function createIpcServer(config, handlers) {
|
|
34
|
+
const { socketPath, maxConnections, logger } = config;
|
|
35
|
+
let server = null;
|
|
36
|
+
const connections = new Map();
|
|
37
|
+
let stopping = false;
|
|
38
|
+
let disconnectHandler = null;
|
|
39
|
+
function handleConnection(socket) {
|
|
40
|
+
if (stopping) {
|
|
41
|
+
socket.destroy();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (connections.size >= maxConnections) {
|
|
45
|
+
logger.warn("daemon.ipc.connection.limit.reached", {
|
|
46
|
+
currentCount: connections.size,
|
|
47
|
+
maxCount: maxConnections,
|
|
48
|
+
});
|
|
49
|
+
// Destroy immediately — the client has no pending requests to correlate
|
|
50
|
+
// an error response against. Client's connectToDaemon() will reject with
|
|
51
|
+
// a socket error, which is the correct signal.
|
|
52
|
+
socket.destroy();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const connectionId = randomUUID();
|
|
56
|
+
const conn = { id: connectionId, socket, inFlightCount: 0, shutdownReason: null };
|
|
57
|
+
connections.set(connectionId, conn);
|
|
58
|
+
logger.info("daemon.ipc.accepted", { connectionId });
|
|
59
|
+
// CELLO-M7-MSG-001: the IPC buffer cap MUST exceed the application content cap
|
|
60
|
+
// (MAX_CONTENT_BYTES = 1 MB) plus the JSON request envelope, or a max-size
|
|
61
|
+
// cello_send message would trip this overflow and the connection would be killed
|
|
62
|
+
// BEFORE cello_send's content_too_large check could run — turning a clean,
|
|
63
|
+
// recoverable "content_too_large" into a fatal connection drop, and making even a
|
|
64
|
+
// VALID 1 MB message unsendable. 4 MB matches the it-length-prefixed transport
|
|
65
|
+
// default (IT_LENGTH_PREFIX_DEFAULT_MAX) so the IPC, app-cap, and transport layers
|
|
66
|
+
// are coherent: content up to the 1 MB cap always traverses IPC, oversize content
|
|
67
|
+
// reaches cello_send and is rejected with content_too_large, and only a payload
|
|
68
|
+
// beyond the transport frame is a hard stop. Must stay in sync with the matching
|
|
69
|
+
// constant in adapter-claude-code/src/ipc-proxy.ts.
|
|
70
|
+
const MAX_BUFFER_SIZE = 4 * 1024 * 1024; // 4MB per connection (> 1MB content cap + envelope)
|
|
71
|
+
let buffer = "";
|
|
72
|
+
socket.on("data", (chunk) => {
|
|
73
|
+
buffer += chunk.toString("utf-8");
|
|
74
|
+
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
75
|
+
logger.warn("daemon.ipc.buffer.overflow", { connectionId, bufferSize: buffer.length });
|
|
76
|
+
buffer = "";
|
|
77
|
+
socket.removeAllListeners("data");
|
|
78
|
+
socket.destroy();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let newlineIdx;
|
|
82
|
+
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
83
|
+
const line = buffer.slice(0, newlineIdx);
|
|
84
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
85
|
+
if (line.trim().length === 0)
|
|
86
|
+
continue;
|
|
87
|
+
handleMessage(conn, line);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
socket.on("close", () => {
|
|
91
|
+
connections.delete(connectionId);
|
|
92
|
+
const reason = conn.shutdownReason || "client_closed";
|
|
93
|
+
logger.info("daemon.ipc.disconnected", { connectionId, reason });
|
|
94
|
+
if (disconnectHandler)
|
|
95
|
+
disconnectHandler(connectionId);
|
|
96
|
+
});
|
|
97
|
+
// Set shutdownReason so the 'close' handler (which always fires after 'error') logs it.
|
|
98
|
+
// Do NOT call disconnectHandler or log here — 'close' fires immediately after 'error'.
|
|
99
|
+
socket.on("error", (err) => {
|
|
100
|
+
conn.shutdownReason = err.message;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function handleMessage(conn, line) {
|
|
104
|
+
let request;
|
|
105
|
+
try {
|
|
106
|
+
request = JSON.parse(line);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Cannot recover the request ID from unparseable input.
|
|
110
|
+
// Close the connection — client's pending promises will all reject
|
|
111
|
+
// with "Connection closed", which correctly propagates the failure.
|
|
112
|
+
logger.warn("daemon.ipc.parse.error", { connectionId: conn.id });
|
|
113
|
+
conn.socket.destroy();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!request.id || !request.method) {
|
|
117
|
+
// If id is missing/falsy, we can't correlate an error response.
|
|
118
|
+
// Close the connection rather than sending an unmatchable frame.
|
|
119
|
+
logger.warn("daemon.ipc.invalid.request", { connectionId: conn.id, hasId: !!request.id, hasMethod: !!request.method });
|
|
120
|
+
conn.socket.destroy();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const handler = handlers.get(request.method);
|
|
124
|
+
if (!handler) {
|
|
125
|
+
const errorResp = {
|
|
126
|
+
id: request.id,
|
|
127
|
+
error: {
|
|
128
|
+
code: "method_not_found",
|
|
129
|
+
message: `Unknown method: ${request.method}`,
|
|
130
|
+
guidance: `Unknown IPC method '${request.method}'. Check that cello-mcp and the daemon are the same version. Run 'cello status' to verify the daemon is running.`,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
conn.socket.write(JSON.stringify(errorResp) + "\n");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
conn.inFlightCount++;
|
|
137
|
+
Promise.resolve(handler(request.params, conn.id))
|
|
138
|
+
.then((result) => {
|
|
139
|
+
try {
|
|
140
|
+
const resp = { id: request.id, result };
|
|
141
|
+
conn.socket.write(JSON.stringify(resp) + "\n");
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Socket closed before response could be written
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.catch((err) => {
|
|
148
|
+
try {
|
|
149
|
+
const resp = {
|
|
150
|
+
id: request.id,
|
|
151
|
+
error: {
|
|
152
|
+
code: "internal_error",
|
|
153
|
+
message: err instanceof Error ? err.message : String(err),
|
|
154
|
+
guidance: "An unexpected error occurred. Check daemon logs for details.",
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
conn.socket.write(JSON.stringify(resp) + "\n");
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Socket closed before error could be written
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.finally(() => {
|
|
164
|
+
conn.inFlightCount--;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
async start() {
|
|
169
|
+
// Remove stale socket file if it exists
|
|
170
|
+
try {
|
|
171
|
+
await unlink(socketPath);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (err.code !== "ENOENT") {
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
server = createServer(handleConnection);
|
|
179
|
+
await new Promise((resolve, reject) => {
|
|
180
|
+
server.on("error", (err) => {
|
|
181
|
+
reject(err);
|
|
182
|
+
});
|
|
183
|
+
server.listen(socketPath, () => {
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
// SI-001: Set socket permissions to owner-only
|
|
188
|
+
await chmod(socketPath, 0o600);
|
|
189
|
+
},
|
|
190
|
+
async stop() {
|
|
191
|
+
stopping = true;
|
|
192
|
+
if (!server)
|
|
193
|
+
return;
|
|
194
|
+
// Wait for in-flight requests to complete (max 5 seconds)
|
|
195
|
+
const deadline = Date.now() + 5000;
|
|
196
|
+
let totalInFlight = 0;
|
|
197
|
+
while (Date.now() < deadline) {
|
|
198
|
+
totalInFlight = 0;
|
|
199
|
+
for (const conn of connections.values()) {
|
|
200
|
+
totalInFlight += conn.inFlightCount;
|
|
201
|
+
}
|
|
202
|
+
if (totalInFlight === 0)
|
|
203
|
+
break;
|
|
204
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
205
|
+
}
|
|
206
|
+
if (totalInFlight > 0) {
|
|
207
|
+
logger.warn("daemon.ipc.shutdown.timeout", { abandonedRequests: totalInFlight });
|
|
208
|
+
}
|
|
209
|
+
// Send shutdown notification to all connected clients.
|
|
210
|
+
// Uses IpcNotification shape — distinct from IpcResponse so clients
|
|
211
|
+
// never confuse it with a response to their request.
|
|
212
|
+
const shutdownNotification = { notification: "shutdown" };
|
|
213
|
+
const shutdownFrame = JSON.stringify(shutdownNotification) + "\n";
|
|
214
|
+
for (const conn of connections.values()) {
|
|
215
|
+
try {
|
|
216
|
+
conn.shutdownReason = "daemon_shutdown";
|
|
217
|
+
conn.socket.write(shutdownFrame);
|
|
218
|
+
conn.socket.end();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Connection may already be closed
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Close server and remove socket
|
|
225
|
+
await new Promise((resolve) => {
|
|
226
|
+
server.close(() => resolve());
|
|
227
|
+
});
|
|
228
|
+
try {
|
|
229
|
+
await unlink(socketPath);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if (err.code !== "ENOENT") {
|
|
233
|
+
logger.warn("daemon.ipc.socket.unlink.failed", {
|
|
234
|
+
socketPath,
|
|
235
|
+
error: err instanceof Error ? err.message : String(err),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
server = null;
|
|
240
|
+
},
|
|
241
|
+
getConnectionCount() {
|
|
242
|
+
return connections.size;
|
|
243
|
+
},
|
|
244
|
+
onDisconnect(handler) {
|
|
245
|
+
disconnectHandler = handler;
|
|
246
|
+
},
|
|
247
|
+
sendNotification(connectionId, notification) {
|
|
248
|
+
const conn = connections.get(connectionId);
|
|
249
|
+
if (!conn)
|
|
250
|
+
return false;
|
|
251
|
+
try {
|
|
252
|
+
const frame = JSON.stringify(notification) + "\n";
|
|
253
|
+
return conn.socket.write(frame);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
logger.debug("daemon.ipc.notification.write.failed", {
|
|
257
|
+
connectionId,
|
|
258
|
+
error: err instanceof Error ? err.message : String(err),
|
|
259
|
+
});
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
getConnectionIds() {
|
|
264
|
+
return Array.from(connections.keys());
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=ipc-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipc-server.js","sourceRoot":"","sources":["../src/ipc-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock file management for the CELLO daemon.
|
|
3
|
+
*
|
|
4
|
+
* Pseudocode:
|
|
5
|
+
* 1. acquireLock(path, content):
|
|
6
|
+
* - Write JSON content to path.tmp atomically
|
|
7
|
+
* - Rename path.tmp → path (atomic on POSIX)
|
|
8
|
+
* - Return true on success
|
|
9
|
+
*
|
|
10
|
+
* 2. readLock(path):
|
|
11
|
+
* - Read file, parse JSON
|
|
12
|
+
* - Validate shape: {pid: number, socketPath: string, version: string}
|
|
13
|
+
* - Return parsed content or null if missing/invalid
|
|
14
|
+
*
|
|
15
|
+
* 3. isProcessAlive(pid):
|
|
16
|
+
* - process.kill(pid, 0) — signal 0 checks existence without killing
|
|
17
|
+
* - Return true if no error thrown
|
|
18
|
+
*
|
|
19
|
+
* 4. removeLock(path):
|
|
20
|
+
* - Unlink the file, ignoring ENOENT
|
|
21
|
+
*/
|
|
22
|
+
import type { LockFileContent, Logger } from "./types.js";
|
|
23
|
+
export declare function readLock(lockPath: string): Promise<LockFileContent | null>;
|
|
24
|
+
export declare function acquireLock(lockPath: string, content: LockFileContent): Promise<void>;
|
|
25
|
+
export declare function removeLock(lockPath: string, logger: Logger): Promise<void>;
|
|
26
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
27
|
+
//# sourceMappingURL=lock-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-file.d.ts","sourceRoot":"","sources":["../src/lock-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE1D,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAwBhF;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3F;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock file management for the CELLO daemon.
|
|
3
|
+
*
|
|
4
|
+
* Pseudocode:
|
|
5
|
+
* 1. acquireLock(path, content):
|
|
6
|
+
* - Write JSON content to path.tmp atomically
|
|
7
|
+
* - Rename path.tmp → path (atomic on POSIX)
|
|
8
|
+
* - Return true on success
|
|
9
|
+
*
|
|
10
|
+
* 2. readLock(path):
|
|
11
|
+
* - Read file, parse JSON
|
|
12
|
+
* - Validate shape: {pid: number, socketPath: string, version: string}
|
|
13
|
+
* - Return parsed content or null if missing/invalid
|
|
14
|
+
*
|
|
15
|
+
* 3. isProcessAlive(pid):
|
|
16
|
+
* - process.kill(pid, 0) — signal 0 checks existence without killing
|
|
17
|
+
* - Return true if no error thrown
|
|
18
|
+
*
|
|
19
|
+
* 4. removeLock(path):
|
|
20
|
+
* - Unlink the file, ignoring ENOENT
|
|
21
|
+
*/
|
|
22
|
+
import { readFile, writeFile, rename, unlink } from "node:fs/promises";
|
|
23
|
+
export async function readLock(lockPath) {
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = await readFile(lockPath, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err.code === "ENOENT") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
if (typeof parsed.pid !== "number" ||
|
|
37
|
+
typeof parsed.socketPath !== "string" ||
|
|
38
|
+
typeof parsed.version !== "string") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return { pid: parsed.pid, socketPath: parsed.socketPath, version: parsed.version };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function acquireLock(lockPath, content) {
|
|
48
|
+
const tmpPath = lockPath + ".tmp";
|
|
49
|
+
const json = JSON.stringify(content, null, 2) + "\n";
|
|
50
|
+
await writeFile(tmpPath, json, { mode: 0o600 });
|
|
51
|
+
try {
|
|
52
|
+
await rename(tmpPath, lockPath);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
try {
|
|
56
|
+
await unlink(tmpPath);
|
|
57
|
+
}
|
|
58
|
+
catch { /* best-effort cleanup */ }
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function removeLock(lockPath, logger) {
|
|
63
|
+
try {
|
|
64
|
+
await unlink(lockPath);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (err.code !== "ENOENT") {
|
|
68
|
+
logger.warn("daemon.lock.remove.failed", {
|
|
69
|
+
lockPath,
|
|
70
|
+
error: err instanceof Error ? err.message : String(err),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function isProcessAlive(pid) {
|
|
76
|
+
try {
|
|
77
|
+
process.kill(pid, 0);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=lock-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-file.js","sourceRoot":"","sources":["../src/lock-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAGvE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,IACE,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;YACrC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAClC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,OAAwB;IAC1E,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,MAAc;IAC/D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;gBACvC,QAAQ;gBACR,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M7-MANIFEST-002 — FileManifestProvider: production IManifestProvider implementation.
|
|
3
|
+
*
|
|
4
|
+
* Reads the bundled consortium-manifest.json from the package root, parses it,
|
|
5
|
+
* verifies the threshold signatures, and caches the result for subsequent calls
|
|
6
|
+
* to getCurrentManifest().
|
|
7
|
+
*
|
|
8
|
+
* Tests use TestManifestProvider (from @cello-protocol/transport) which
|
|
9
|
+
* takes a pre-built ConsortiumManifest and skips file read and signature verification.
|
|
10
|
+
*
|
|
11
|
+
* Crypto reference: RFC 8032 (Ed25519 threshold signature verification via verifyManifest).
|
|
12
|
+
*/
|
|
13
|
+
import type { ConsortiumManifest } from "@cello-protocol/protocol-types";
|
|
14
|
+
import type { IManifestProvider } from "@cello-protocol/transport";
|
|
15
|
+
/**
|
|
16
|
+
* FileManifestProvider — reads consortium-manifest.json from the package root.
|
|
17
|
+
*
|
|
18
|
+
* Pseudocode:
|
|
19
|
+
* loadAndVerify(rootKeys, threshold):
|
|
20
|
+
* 1. Read consortium-manifest.json from the package root (same directory as this file).
|
|
21
|
+
* 2. Parse as JSON — throw on parse failure.
|
|
22
|
+
* 3. Call verifyManifest(parsed, rootKeys, threshold) — RFC 8032 Ed25519 threshold check.
|
|
23
|
+
* 4. If ok: cache the manifest, return it.
|
|
24
|
+
* 5. If not ok: throw Error with reason from verifyManifest diagnostics.
|
|
25
|
+
*/
|
|
26
|
+
export declare class FileManifestProvider implements IManifestProvider {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(manifestPath?: string);
|
|
29
|
+
loadAndVerify(rootKeys: readonly string[], threshold: number): Promise<ConsortiumManifest>;
|
|
30
|
+
getCurrentManifest(): ConsortiumManifest | null;
|
|
31
|
+
updateManifest(manifest: ConsortiumManifest): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=manifest-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE;;;;;;;;;;GAUG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;;gBAIhD,YAAY,CAAC,EAAE,MAAM;IAQ3B,aAAa,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmChG,kBAAkB,IAAI,kBAAkB,GAAG,IAAI;IAI/C,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;CAGnD"}
|