nolo-cli 0.1.33 → 0.1.35
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/README.md +15 -5
- package/agent-runtime/agentRecordConfig.ts +8 -0
- package/agent-runtime/hostAdapter.ts +9 -1
- package/agent-runtime/index.ts +10 -0
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +71 -22
- package/agent-runtime/localWorkspaceTools.ts +5 -2
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +48 -71
- package/agent-runtime/providerResolution.ts +242 -0
- package/agent-runtime/runtimeToolPolicy.ts +33 -0
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentCommandSupport.ts +4 -0
- package/agentInternalCommandEntries.ts +2 -0
- package/agentListCommands.ts +74 -12
- package/agentListHelpers.ts +56 -1
- package/agentPullCommand.ts +6 -11
- package/agentRecordCommands.ts +87 -2
- package/agentRecordHelpers.ts +132 -9
- package/agentRuntimeLocal.ts +3 -0
- package/ai/tools/toolNameAliases.ts +70 -0
- package/cli/agentCommandSupport.ts +4 -0
- package/cli/agentInternalCommandEntries.ts +2 -0
- package/cli/agentListCommands.ts +74 -12
- package/cli/agentListHelpers.ts +56 -1
- package/cli/agentPullCommand.ts +6 -11
- package/cli/agentRecordCommands.ts +87 -2
- package/cli/agentRecordHelpers.ts +132 -9
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +80 -12
- package/cli/cliSpaceHelpers.ts +39 -0
- package/cli/client/agentRun.ts +1 -0
- package/cli/client/localRuntimeAdapter.ts +44 -7
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorWebSocketTarget.ts +10 -3
- package/cli/dialogCommands.ts +446 -0
- package/cli/dialogInternalCommandEntries.ts +13 -0
- package/cli/globalRecordOperations.ts +258 -0
- package/cli/internalCommandEntries.ts +2 -0
- package/cli/localRuntimeAuthority.ts +182 -0
- package/cli/localRuntimeDb.ts +2 -4
- package/cli/machineCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +29 -12
- package/cli/machineWsSession.ts +2 -0
- package/cli/offlineMarxistsAgentCommand.ts +1 -1
- package/cli/runtimeDoctorCommands.ts +65 -9
- package/cli/tableCommands.ts +1 -1
- package/cli/taskRunCommand.ts +1 -1
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +80 -12
- package/cliSpaceHelpers.ts +39 -0
- package/client/agentRun.ts +1 -0
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +287 -3
- package/client/localRuntimeAdapter.ts +44 -7
- package/commandRegistry.ts +2 -2
- package/connectorWebSocketTarget.ts +10 -3
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/cliAuthorityBrokerClient.ts +227 -0
- package/database/server/cliAuthorityBrokerServer.ts +283 -0
- package/database/server/cliAuthorityBrokerTypes.ts +43 -0
- package/database/server/cliAuthorityStoreDriver.ts +98 -0
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +95 -0
- package/database/tombstones.ts +52 -0
- package/database/userDataMerge.ts +95 -0
- package/dialogCommands.ts +446 -0
- package/dialogInternalCommandEntries.ts +13 -0
- package/globalRecordOperations.ts +258 -0
- package/internalCommandEntries.ts +2 -0
- package/localRuntimeAuthority.ts +182 -0
- package/localRuntimeDb.ts +2 -4
- package/machineCommands.ts +1 -1
- package/machineWsRunDispatch.ts +29 -12
- package/machineWsSession.ts +2 -0
- package/offlineMarxistsAgentCommand.ts +1 -1
- package/package.json +24 -4
- package/runtimeDoctorCommands.ts +65 -9
- package/tableCommands.ts +1 -1
- package/taskRunCommand.ts +1 -1
- package/database/server/db.ts +0 -118
- package/database/server/ensureDbOpen.ts +0 -12
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthorityBatchOperation,
|
|
3
|
+
AuthorityBatchWriter,
|
|
4
|
+
AuthorityIteratorOptions,
|
|
5
|
+
AuthorityStore,
|
|
6
|
+
} from "./authorityStoreTypes";
|
|
7
|
+
import type {
|
|
8
|
+
CliAuthorityBrokerRequest,
|
|
9
|
+
CliAuthorityBrokerResponse,
|
|
10
|
+
} from "./cliAuthorityBrokerTypes";
|
|
11
|
+
import { createConnection } from "node:net";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ITERATOR_PAGE_SIZE = 200;
|
|
14
|
+
|
|
15
|
+
export class CliAuthorityBrokerUnavailableError extends Error {
|
|
16
|
+
readonly reason = "cli_authority_broker_unavailable";
|
|
17
|
+
readonly status = 503;
|
|
18
|
+
readonly endpoint: string;
|
|
19
|
+
|
|
20
|
+
constructor(endpoint: string, cause?: unknown) {
|
|
21
|
+
super(`CLI authority broker unavailable at ${endpoint}. Start the local authority broker and retry.`);
|
|
22
|
+
this.name = "CliAuthorityBrokerUnavailableError";
|
|
23
|
+
this.endpoint = endpoint;
|
|
24
|
+
if (cause !== undefined) {
|
|
25
|
+
(this as Error & { cause?: unknown }).cause = cause;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class CliAuthorityBrokerProtocolError extends Error {
|
|
31
|
+
readonly code: string;
|
|
32
|
+
readonly retryable: boolean;
|
|
33
|
+
|
|
34
|
+
constructor(error: { code: string; message: string; retryable?: boolean }) {
|
|
35
|
+
super(error.message);
|
|
36
|
+
this.name = "CliAuthorityBrokerProtocolError";
|
|
37
|
+
this.code = error.code;
|
|
38
|
+
this.retryable = error.retryable === true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isCliAuthorityBrokerUnavailableError(
|
|
43
|
+
error: unknown
|
|
44
|
+
): error is CliAuthorityBrokerUnavailableError {
|
|
45
|
+
return (
|
|
46
|
+
error instanceof CliAuthorityBrokerUnavailableError ||
|
|
47
|
+
(
|
|
48
|
+
typeof error === "object" &&
|
|
49
|
+
error !== null &&
|
|
50
|
+
(error as any).reason === "cli_authority_broker_unavailable" &&
|
|
51
|
+
(error as any).status === 503
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isCliAuthorityBrokerProtocolError(
|
|
57
|
+
error: unknown
|
|
58
|
+
): error is CliAuthorityBrokerProtocolError {
|
|
59
|
+
return error instanceof CliAuthorityBrokerProtocolError;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type CliAuthorityBrokerRequestInvoker = (
|
|
63
|
+
request: CliAuthorityBrokerRequest
|
|
64
|
+
) => Promise<CliAuthorityBrokerResponse>;
|
|
65
|
+
|
|
66
|
+
export type CliAuthorityBrokerClientOptions = {
|
|
67
|
+
endpoint: string;
|
|
68
|
+
location?: string;
|
|
69
|
+
status?: string;
|
|
70
|
+
invoke: CliAuthorityBrokerRequestInvoker;
|
|
71
|
+
iteratorPageSize?: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type BrokerSocketTarget =
|
|
75
|
+
| { kind: "unix"; path: string }
|
|
76
|
+
| { kind: "tcp"; host: string; port: number };
|
|
77
|
+
|
|
78
|
+
function resolveBrokerSocketTarget(endpoint: string): BrokerSocketTarget {
|
|
79
|
+
if (endpoint.startsWith("unix://")) {
|
|
80
|
+
return { kind: "unix", path: endpoint.slice("unix://".length) };
|
|
81
|
+
}
|
|
82
|
+
if (endpoint.startsWith("tcp://")) {
|
|
83
|
+
const url = new URL(endpoint);
|
|
84
|
+
return {
|
|
85
|
+
kind: "tcp",
|
|
86
|
+
host: url.hostname,
|
|
87
|
+
port: Number(url.port),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Unsupported CLI authority broker endpoint: ${endpoint}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createCliAuthorityBrokerSocketInvoker(options: {
|
|
94
|
+
endpoint: string;
|
|
95
|
+
}): CliAuthorityBrokerRequestInvoker {
|
|
96
|
+
const target = resolveBrokerSocketTarget(options.endpoint);
|
|
97
|
+
return async (request: CliAuthorityBrokerRequest) => {
|
|
98
|
+
return await new Promise<CliAuthorityBrokerResponse>((resolve, reject) => {
|
|
99
|
+
const socket = target.kind === "unix"
|
|
100
|
+
? createConnection(target.path)
|
|
101
|
+
: createConnection({ host: target.host, port: target.port });
|
|
102
|
+
let responseBuffer = "";
|
|
103
|
+
let settled = false;
|
|
104
|
+
|
|
105
|
+
const finish = (fn: () => void) => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
fn();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
socket.setEncoding("utf8");
|
|
112
|
+
socket.on("connect", () => {
|
|
113
|
+
socket.write(`${JSON.stringify(request)}\n`);
|
|
114
|
+
});
|
|
115
|
+
socket.on("data", (chunk) => {
|
|
116
|
+
responseBuffer += chunk;
|
|
117
|
+
});
|
|
118
|
+
socket.on("end", () => {
|
|
119
|
+
finish(() => {
|
|
120
|
+
const payload = responseBuffer.trim();
|
|
121
|
+
if (!payload) {
|
|
122
|
+
reject(new Error(`CLI authority broker at ${options.endpoint} returned no response`));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
resolve(JSON.parse(payload) as CliAuthorityBrokerResponse);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
reject(error);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
socket.on("error", (error) => {
|
|
133
|
+
finish(() => reject(error));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function invokeBrokerRequest(
|
|
140
|
+
options: CliAuthorityBrokerClientOptions,
|
|
141
|
+
request: CliAuthorityBrokerRequest
|
|
142
|
+
) {
|
|
143
|
+
try {
|
|
144
|
+
const response = await options.invoke(request);
|
|
145
|
+
if (response.ok) return response.result;
|
|
146
|
+
if (response.error.code === "BROKER_UNAVAILABLE") {
|
|
147
|
+
throw new CliAuthorityBrokerUnavailableError(options.endpoint, response.error);
|
|
148
|
+
}
|
|
149
|
+
throw new CliAuthorityBrokerProtocolError(response.error);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (isCliAuthorityBrokerUnavailableError(error)) throw error;
|
|
152
|
+
if (isCliAuthorityBrokerProtocolError(error)) throw error;
|
|
153
|
+
throw new CliAuthorityBrokerUnavailableError(options.endpoint, error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createCliAuthorityBrokerClient(
|
|
158
|
+
options: CliAuthorityBrokerClientOptions
|
|
159
|
+
): AuthorityStore {
|
|
160
|
+
const pageSize = options.iteratorPageSize ?? DEFAULT_ITERATOR_PAGE_SIZE;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
get location() {
|
|
164
|
+
return options.location ?? options.endpoint;
|
|
165
|
+
},
|
|
166
|
+
get status() {
|
|
167
|
+
return options.status ?? "broker";
|
|
168
|
+
},
|
|
169
|
+
async open() {
|
|
170
|
+
await invokeBrokerRequest(options, { type: "status" });
|
|
171
|
+
},
|
|
172
|
+
async close() {},
|
|
173
|
+
async get<T = any>(key: string) {
|
|
174
|
+
const result = await invokeBrokerRequest(options, { type: "get", key });
|
|
175
|
+
if (result.type !== "get") {
|
|
176
|
+
throw new Error(`CLI authority broker protocol mismatch for get at ${options.endpoint}`);
|
|
177
|
+
}
|
|
178
|
+
return result.value as T;
|
|
179
|
+
},
|
|
180
|
+
async put(key: string, value: unknown) {
|
|
181
|
+
await invokeBrokerRequest(options, { type: "put", key, value });
|
|
182
|
+
},
|
|
183
|
+
async del(key: string) {
|
|
184
|
+
await invokeBrokerRequest(options, { type: "del", key });
|
|
185
|
+
},
|
|
186
|
+
async batchWrite(ops: AuthorityBatchOperation[]) {
|
|
187
|
+
await invokeBrokerRequest(options, { type: "batchWrite", ops });
|
|
188
|
+
},
|
|
189
|
+
createBatch(): AuthorityBatchWriter {
|
|
190
|
+
const ops: AuthorityBatchOperation[] = [];
|
|
191
|
+
return {
|
|
192
|
+
put(key: string, value: unknown) {
|
|
193
|
+
ops.push({ type: "put", key, value });
|
|
194
|
+
},
|
|
195
|
+
del(key: string) {
|
|
196
|
+
ops.push({ type: "del", key });
|
|
197
|
+
},
|
|
198
|
+
async write() {
|
|
199
|
+
await invokeBrokerRequest(options, { type: "batchWrite", ops });
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
iterator(iteratorOptions: AuthorityIteratorOptions = {}) {
|
|
204
|
+
return (async function* iterate() {
|
|
205
|
+
let cursor: string | null | undefined = undefined;
|
|
206
|
+
while (true) {
|
|
207
|
+
const result = await invokeBrokerRequest(options, {
|
|
208
|
+
type: "iterator",
|
|
209
|
+
options: iteratorOptions,
|
|
210
|
+
cursor,
|
|
211
|
+
limit: pageSize,
|
|
212
|
+
});
|
|
213
|
+
if (result.type !== "iterator") {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`CLI authority broker protocol mismatch for iterator at ${options.endpoint}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
for (const entry of result.page.entries) {
|
|
219
|
+
yield entry;
|
|
220
|
+
}
|
|
221
|
+
if (result.page.done || !result.page.nextCursor) return;
|
|
222
|
+
cursor = result.page.nextCursor;
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createServer, type AddressInfo, type Server, type Socket } from "node:net";
|
|
4
|
+
|
|
5
|
+
import type { AuthorityStore } from "./authorityStoreTypes";
|
|
6
|
+
import type {
|
|
7
|
+
CliAuthorityBrokerIteratorPage,
|
|
8
|
+
CliAuthorityBrokerRequest,
|
|
9
|
+
CliAuthorityBrokerResponse,
|
|
10
|
+
} from "./cliAuthorityBrokerTypes";
|
|
11
|
+
|
|
12
|
+
const brokerRegistry = new Map<string, Promise<CliAuthorityBrokerServerHandle>>();
|
|
13
|
+
const DEFAULT_ITERATOR_LIMIT = 200;
|
|
14
|
+
|
|
15
|
+
export type CliAuthorityBrokerServerOptions = {
|
|
16
|
+
endpoint: string;
|
|
17
|
+
createStore: () => AuthorityStore | Promise<AuthorityStore>;
|
|
18
|
+
metadataPath?: string;
|
|
19
|
+
healthPath?: string;
|
|
20
|
+
now?: () => number;
|
|
21
|
+
transportMode?: "socket" | "inprocess";
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type CliAuthorityBrokerServerHandle = {
|
|
25
|
+
endpoint: string;
|
|
26
|
+
listeningEndpoint: string;
|
|
27
|
+
authorityStore: AuthorityStore;
|
|
28
|
+
request(request: CliAuthorityBrokerRequest): Promise<CliAuthorityBrokerResponse>;
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type BrokerListenTarget =
|
|
33
|
+
| { kind: "unix"; path: string }
|
|
34
|
+
| { kind: "tcp"; host: string; port: number };
|
|
35
|
+
|
|
36
|
+
function resolveBrokerListenTarget(endpoint: string): BrokerListenTarget {
|
|
37
|
+
if (endpoint.startsWith("unix://")) {
|
|
38
|
+
return { kind: "unix", path: endpoint.slice("unix://".length) };
|
|
39
|
+
}
|
|
40
|
+
if (endpoint.startsWith("tcp://")) {
|
|
41
|
+
const url = new URL(endpoint);
|
|
42
|
+
return {
|
|
43
|
+
kind: "tcp",
|
|
44
|
+
host: url.hostname,
|
|
45
|
+
port: Number(url.port),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Unsupported CLI authority broker endpoint: ${endpoint}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildListeningEndpoint(server: Server, target: BrokerListenTarget) {
|
|
52
|
+
if (target.kind === "unix") {
|
|
53
|
+
return `unix://${target.path}`;
|
|
54
|
+
}
|
|
55
|
+
const address = server.address();
|
|
56
|
+
if (!address || typeof address === "string") {
|
|
57
|
+
return `tcp://${target.host}:${target.port}`;
|
|
58
|
+
}
|
|
59
|
+
const info = address as AddressInfo;
|
|
60
|
+
return `tcp://${target.host}:${info.port}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ensureParentDir(filePath: string) {
|
|
64
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeJsonFile(filePath: string | undefined, data: Record<string, unknown>) {
|
|
68
|
+
if (!filePath) return;
|
|
69
|
+
ensureParentDir(filePath);
|
|
70
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function removeFileIfPresent(filePath: string | undefined) {
|
|
74
|
+
if (!filePath) return;
|
|
75
|
+
try {
|
|
76
|
+
fs.rmSync(filePath, { force: true });
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function buildIteratorPage(
|
|
81
|
+
store: AuthorityStore,
|
|
82
|
+
request: Extract<CliAuthorityBrokerRequest, { type: "iterator" }>
|
|
83
|
+
): Promise<CliAuthorityBrokerIteratorPage> {
|
|
84
|
+
const rows: Array<[string, unknown]> = [];
|
|
85
|
+
for await (const entry of store.iterator(request.options ?? {})) {
|
|
86
|
+
rows.push(entry as [string, unknown]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const startIndex = request.cursor
|
|
90
|
+
? rows.findIndex(([key]) => key === request.cursor) + 1
|
|
91
|
+
: 0;
|
|
92
|
+
const limit = request.limit ?? DEFAULT_ITERATOR_LIMIT;
|
|
93
|
+
const entries = rows.slice(startIndex, startIndex + limit);
|
|
94
|
+
const lastEntry = entries.at(-1) ?? null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
entries,
|
|
98
|
+
nextCursor: lastEntry ? lastEntry[0] : null,
|
|
99
|
+
done: startIndex + entries.length >= rows.length,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createErrorResponse(error: unknown): CliAuthorityBrokerResponse {
|
|
104
|
+
if (
|
|
105
|
+
typeof error === "object" &&
|
|
106
|
+
error !== null &&
|
|
107
|
+
typeof (error as any).code === "string" &&
|
|
108
|
+
typeof (error as any).message === "string"
|
|
109
|
+
) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: {
|
|
113
|
+
code: (error as any).code,
|
|
114
|
+
message: (error as any).message,
|
|
115
|
+
retryable: (error as any).retryable === true,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: {
|
|
122
|
+
code: "BROKER_INTERNAL_ERROR",
|
|
123
|
+
message: error instanceof Error ? error.message : String(error),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function dispatchCliAuthorityBrokerRequest(
|
|
129
|
+
store: AuthorityStore,
|
|
130
|
+
request: CliAuthorityBrokerRequest
|
|
131
|
+
): Promise<CliAuthorityBrokerResponse> {
|
|
132
|
+
try {
|
|
133
|
+
switch (request.type) {
|
|
134
|
+
case "status":
|
|
135
|
+
return { ok: true, result: { type: "status" } };
|
|
136
|
+
case "open":
|
|
137
|
+
await store.open();
|
|
138
|
+
return { ok: true, result: { type: "open" } };
|
|
139
|
+
case "close":
|
|
140
|
+
await store.close();
|
|
141
|
+
return { ok: true, result: { type: "close" } };
|
|
142
|
+
case "get":
|
|
143
|
+
return { ok: true, result: { type: "get", value: await store.get(request.key) } };
|
|
144
|
+
case "put":
|
|
145
|
+
await store.put(request.key, request.value);
|
|
146
|
+
return { ok: true, result: { type: "put" } };
|
|
147
|
+
case "del":
|
|
148
|
+
await store.del(request.key);
|
|
149
|
+
return { ok: true, result: { type: "del" } };
|
|
150
|
+
case "batchWrite":
|
|
151
|
+
await store.batchWrite(request.ops);
|
|
152
|
+
return { ok: true, result: { type: "batchWrite" } };
|
|
153
|
+
case "iterator":
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
result: {
|
|
157
|
+
type: "iterator",
|
|
158
|
+
page: await buildIteratorPage(store, request),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return createErrorResponse(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function createBrokerHandle(
|
|
168
|
+
options: CliAuthorityBrokerServerOptions
|
|
169
|
+
): Promise<CliAuthorityBrokerServerHandle> {
|
|
170
|
+
const target = resolveBrokerListenTarget(options.endpoint);
|
|
171
|
+
if (target.kind === "unix") {
|
|
172
|
+
ensureParentDir(target.path);
|
|
173
|
+
removeFileIfPresent(target.path);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const store = await options.createStore();
|
|
177
|
+
await store.open();
|
|
178
|
+
|
|
179
|
+
const server = createServer((socket) => {
|
|
180
|
+
bindSocketRequestHandler({ socket, store });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if ((options.transportMode ?? "socket") === "socket") {
|
|
184
|
+
try {
|
|
185
|
+
await new Promise<void>((resolve, reject) => {
|
|
186
|
+
server.once("error", reject);
|
|
187
|
+
if (target.kind === "unix") {
|
|
188
|
+
server.listen(target.path, () => {
|
|
189
|
+
server.off("error", reject);
|
|
190
|
+
resolve();
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
server.listen(target.port, target.host, () => {
|
|
195
|
+
server.off("error", reject);
|
|
196
|
+
resolve();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
server.unref();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
await store.close().catch(() => {});
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const now = options.now ?? Date.now;
|
|
207
|
+
const listeningEndpoint = (options.transportMode ?? "socket") === "socket"
|
|
208
|
+
? buildListeningEndpoint(server, target)
|
|
209
|
+
: options.endpoint;
|
|
210
|
+
writeJsonFile(options.metadataPath, {
|
|
211
|
+
pid: process.pid,
|
|
212
|
+
endpoint: listeningEndpoint,
|
|
213
|
+
location: store.location ?? listeningEndpoint,
|
|
214
|
+
startedAt: new Date(now()).toISOString(),
|
|
215
|
+
});
|
|
216
|
+
writeJsonFile(options.healthPath, {
|
|
217
|
+
ok: true,
|
|
218
|
+
pid: process.pid,
|
|
219
|
+
endpoint: listeningEndpoint,
|
|
220
|
+
checkedAt: new Date(now()).toISOString(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let closed = false;
|
|
224
|
+
return {
|
|
225
|
+
endpoint: options.endpoint,
|
|
226
|
+
listeningEndpoint,
|
|
227
|
+
authorityStore: store,
|
|
228
|
+
request(request: CliAuthorityBrokerRequest) {
|
|
229
|
+
return dispatchCliAuthorityBrokerRequest(store, request);
|
|
230
|
+
},
|
|
231
|
+
async close() {
|
|
232
|
+
if (closed) return;
|
|
233
|
+
closed = true;
|
|
234
|
+
brokerRegistry.delete(options.endpoint);
|
|
235
|
+
if ((options.transportMode ?? "socket") === "socket") {
|
|
236
|
+
await new Promise<void>((resolve, reject) => {
|
|
237
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
await store.close();
|
|
241
|
+
if (target.kind === "unix") {
|
|
242
|
+
removeFileIfPresent(target.path);
|
|
243
|
+
}
|
|
244
|
+
removeFileIfPresent(options.metadataPath);
|
|
245
|
+
removeFileIfPresent(options.healthPath);
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function bindSocketRequestHandler(args: { socket: Socket; store: AuthorityStore }) {
|
|
251
|
+
let buffer = "";
|
|
252
|
+
args.socket.setEncoding("utf8");
|
|
253
|
+
args.socket.on("data", async (chunk) => {
|
|
254
|
+
buffer += chunk;
|
|
255
|
+
if (!buffer.includes("\n")) return;
|
|
256
|
+
const [rawRequest] = buffer.split("\n");
|
|
257
|
+
buffer = "";
|
|
258
|
+
let response: CliAuthorityBrokerResponse;
|
|
259
|
+
try {
|
|
260
|
+
response = await dispatchCliAuthorityBrokerRequest(
|
|
261
|
+
args.store,
|
|
262
|
+
JSON.parse(rawRequest) as CliAuthorityBrokerRequest
|
|
263
|
+
);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
response = createErrorResponse(error);
|
|
266
|
+
}
|
|
267
|
+
args.socket.end(`${JSON.stringify(response)}\n`);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function getOrCreateCliAuthorityBrokerServer(
|
|
272
|
+
options: CliAuthorityBrokerServerOptions
|
|
273
|
+
): Promise<CliAuthorityBrokerServerHandle> {
|
|
274
|
+
const existing = brokerRegistry.get(options.endpoint);
|
|
275
|
+
if (existing) return existing;
|
|
276
|
+
|
|
277
|
+
const pending = createBrokerHandle(options).catch((error) => {
|
|
278
|
+
brokerRegistry.delete(options.endpoint);
|
|
279
|
+
throw error;
|
|
280
|
+
});
|
|
281
|
+
brokerRegistry.set(options.endpoint, pending);
|
|
282
|
+
return pending;
|
|
283
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthorityBatchOperation,
|
|
3
|
+
AuthorityIteratorOptions,
|
|
4
|
+
} from "./authorityStoreTypes";
|
|
5
|
+
|
|
6
|
+
export type CliAuthorityBrokerRequest =
|
|
7
|
+
| { type: "status" }
|
|
8
|
+
| { type: "open" }
|
|
9
|
+
| { type: "close" }
|
|
10
|
+
| { type: "get"; key: string }
|
|
11
|
+
| { type: "put"; key: string; value: unknown }
|
|
12
|
+
| { type: "del"; key: string }
|
|
13
|
+
| { type: "batchWrite"; ops: AuthorityBatchOperation[] }
|
|
14
|
+
| {
|
|
15
|
+
type: "iterator";
|
|
16
|
+
options?: AuthorityIteratorOptions;
|
|
17
|
+
cursor?: string | null;
|
|
18
|
+
limit?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CliAuthorityBrokerIteratorPage = {
|
|
22
|
+
entries: Array<[string, unknown]>;
|
|
23
|
+
nextCursor: string | null;
|
|
24
|
+
done: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type CliAuthorityBrokerSuccessResponse =
|
|
28
|
+
| { ok: true; result: { type: "status" | "open" | "close" | "put" | "del" | "batchWrite" } }
|
|
29
|
+
| { ok: true; result: { type: "get"; value: unknown } }
|
|
30
|
+
| { ok: true; result: { type: "iterator"; page: CliAuthorityBrokerIteratorPage } };
|
|
31
|
+
|
|
32
|
+
export type CliAuthorityBrokerErrorResponse = {
|
|
33
|
+
ok: false;
|
|
34
|
+
error: {
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
retryable?: boolean;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type CliAuthorityBrokerResponse =
|
|
42
|
+
| CliAuthorityBrokerSuccessResponse
|
|
43
|
+
| CliAuthorityBrokerErrorResponse;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { resolveNoloHome, type ResolveServerDbPathOptions } from "./dbPath";
|
|
4
|
+
|
|
5
|
+
export type CliAuthorityStoreDriver = "broker";
|
|
6
|
+
export type CliAuthorityBrokerTransport = "tcp" | "unix";
|
|
7
|
+
|
|
8
|
+
type EnvLike = Record<string, string | undefined>;
|
|
9
|
+
|
|
10
|
+
type CliAuthorityStoreDriverOptions = ResolveServerDbPathOptions & {
|
|
11
|
+
env?: EnvLike;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type CliAuthorityBrokerOptions = CliAuthorityStoreDriverOptions & {
|
|
15
|
+
transport: CliAuthorityBrokerTransport;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function readCliAuthorityDriver(
|
|
19
|
+
env: EnvLike
|
|
20
|
+
): { driver: CliAuthorityStoreDriver; invalidDriver: string | null } {
|
|
21
|
+
const value = env.NOLO_CLI_AUTHORITY_DRIVER?.trim();
|
|
22
|
+
if (value === "broker") {
|
|
23
|
+
return { driver: "broker", invalidDriver: null };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
driver: "broker",
|
|
27
|
+
invalidDriver: value ? value : null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveCliAuthorityRunDir(options: CliAuthorityStoreDriverOptions = {}) {
|
|
32
|
+
return path.join(resolveNoloHome({
|
|
33
|
+
...options,
|
|
34
|
+
env: options.env ?? {},
|
|
35
|
+
}), "run");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hashString(value: string) {
|
|
39
|
+
let hash = 0;
|
|
40
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
41
|
+
hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0;
|
|
42
|
+
}
|
|
43
|
+
return Math.abs(hash);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveCliAuthorityBrokerPort(
|
|
47
|
+
options: CliAuthorityStoreDriverOptions = {}
|
|
48
|
+
) {
|
|
49
|
+
const envPort = options.env?.NOLO_CLI_AUTHORITY_BROKER_PORT?.trim();
|
|
50
|
+
if (envPort) {
|
|
51
|
+
const port = Number(envPort);
|
|
52
|
+
if (Number.isInteger(port) && port > 0 && port < 65536) return port;
|
|
53
|
+
}
|
|
54
|
+
const noloHome = resolveNoloHome({
|
|
55
|
+
...options,
|
|
56
|
+
env: options.env ?? {},
|
|
57
|
+
});
|
|
58
|
+
return 47000 + (hashString(noloHome) % 2000);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolveCliAuthorityStoreDriver(
|
|
62
|
+
options: CliAuthorityStoreDriverOptions = {}
|
|
63
|
+
): CliAuthorityStoreDriver {
|
|
64
|
+
return readCliAuthorityDriver(options.env ?? {}).driver;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveCliAuthorityStoreDriverConfig(
|
|
68
|
+
options: CliAuthorityStoreDriverOptions = {}
|
|
69
|
+
) {
|
|
70
|
+
return readCliAuthorityDriver(options.env ?? {});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveCliAuthorityBrokerSocketPath(
|
|
74
|
+
options: CliAuthorityBrokerOptions
|
|
75
|
+
) {
|
|
76
|
+
return path.join(resolveCliAuthorityRunDir(options), "authority-store-broker.sock");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function resolveCliAuthorityBrokerEndpoint(
|
|
80
|
+
options: CliAuthorityBrokerOptions
|
|
81
|
+
) {
|
|
82
|
+
if (options.transport === "tcp") {
|
|
83
|
+
return `tcp://127.0.0.1:${resolveCliAuthorityBrokerPort(options)}`;
|
|
84
|
+
}
|
|
85
|
+
return `unix://${resolveCliAuthorityBrokerSocketPath(options)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveCliAuthorityBrokerMetadataPath(
|
|
89
|
+
options: CliAuthorityBrokerOptions
|
|
90
|
+
) {
|
|
91
|
+
return path.join(resolveCliAuthorityRunDir(options), "authority-store-broker.json");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveCliAuthorityBrokerHealthPath(
|
|
95
|
+
options: CliAuthorityBrokerOptions
|
|
96
|
+
) {
|
|
97
|
+
return path.join(resolveCliAuthorityRunDir(options), "authority-store-broker.health.json");
|
|
98
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthorityBatchOperation,
|
|
3
|
+
AuthorityBatchWriter,
|
|
4
|
+
AuthorityIteratorOptions,
|
|
5
|
+
AuthorityStore,
|
|
6
|
+
} from "./authorityStoreTypes";
|
|
7
|
+
|
|
8
|
+
export type LegacyServerDb = {
|
|
9
|
+
readonly location?: string;
|
|
10
|
+
readonly status?: string;
|
|
11
|
+
open(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
get<T = any>(key: string): Promise<T>;
|
|
14
|
+
put(key: string, value: unknown): Promise<void>;
|
|
15
|
+
del(key: string): Promise<void>;
|
|
16
|
+
batch(ops: AuthorityBatchOperation[]): Promise<void>;
|
|
17
|
+
batch(): AuthorityBatchWriter;
|
|
18
|
+
iterator(
|
|
19
|
+
options?: AuthorityIteratorOptions
|
|
20
|
+
): AsyncIterableIterator<[string, unknown]>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createLegacyServerDb(store: AuthorityStore): LegacyServerDb {
|
|
24
|
+
const batch = (ops?: AuthorityBatchOperation[]) => {
|
|
25
|
+
if (Array.isArray(ops)) {
|
|
26
|
+
return store.batchWrite(ops);
|
|
27
|
+
}
|
|
28
|
+
return store.createBatch();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
get location() {
|
|
33
|
+
return store.location;
|
|
34
|
+
},
|
|
35
|
+
get status() {
|
|
36
|
+
return store.status;
|
|
37
|
+
},
|
|
38
|
+
async open() {
|
|
39
|
+
await store.open();
|
|
40
|
+
},
|
|
41
|
+
async close() {
|
|
42
|
+
await store.close();
|
|
43
|
+
},
|
|
44
|
+
async get<T = any>(key: string) {
|
|
45
|
+
return store.get<T>(key);
|
|
46
|
+
},
|
|
47
|
+
async put(key: string, value: unknown) {
|
|
48
|
+
await store.put(key, value);
|
|
49
|
+
},
|
|
50
|
+
async del(key: string) {
|
|
51
|
+
await store.del(key);
|
|
52
|
+
},
|
|
53
|
+
batch: batch as LegacyServerDb["batch"],
|
|
54
|
+
iterator(options: AuthorityIteratorOptions = {}) {
|
|
55
|
+
return store.iterator(options);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|