@koed-labs/server 0.0.0-stage → 0.10.3
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/LICENSE +201 -0
- package/README.md +64 -2
- package/bin/koed.js +7 -0
- package/package.json +26 -4
- package/third-party-notices.json +92 -0
- package/vendor/chunk-2BWTLF7A.js +13883 -0
- package/vendor/chunk-2DA7EFZV.js +924 -0
- package/vendor/chunk-2S3OGPUC.js +58 -0
- package/vendor/chunk-473OA62O.js +26827 -0
- package/vendor/chunk-6JRWGOQG.js +236 -0
- package/vendor/chunk-7NOQSX4T.js +14 -0
- package/vendor/chunk-7NXKPDF3.js +1586 -0
- package/vendor/chunk-A4NKL2PK.js +153 -0
- package/vendor/chunk-A6WMU2GG.js +48 -0
- package/vendor/chunk-AFLKCQHF.js +73 -0
- package/vendor/chunk-BTS4ZTF3.js +43 -0
- package/vendor/chunk-C3N4N5HR.js +108 -0
- package/vendor/chunk-DXTAWHY3.js +25 -0
- package/vendor/chunk-G4IIVVJO.js +56 -0
- package/vendor/chunk-GELDEJJ2.js +4337 -0
- package/vendor/chunk-HFM6ROJL.js +188 -0
- package/vendor/chunk-HK2H3MVE.js +328 -0
- package/vendor/chunk-HZJK5RWC.js +5088 -0
- package/vendor/chunk-J2L726BC.js +2497 -0
- package/vendor/chunk-JBGD4JLR.js +3779 -0
- package/vendor/chunk-JJNLH5GA.js +2470 -0
- package/vendor/chunk-JKOG52PE.js +128 -0
- package/vendor/chunk-K2EIFEQF.js +89 -0
- package/vendor/chunk-KUIWH33N.js +16 -0
- package/vendor/chunk-NEWIOOM6.js +34 -0
- package/vendor/chunk-NM5NXCVM.js +2404 -0
- package/vendor/chunk-PT3AUPGE.js +459 -0
- package/vendor/chunk-PY5LYLV7.js +143 -0
- package/vendor/chunk-QIIKMG3M.js +103 -0
- package/vendor/chunk-QT24SAW6.js +110 -0
- package/vendor/chunk-RVYJKKWA.js +22 -0
- package/vendor/chunk-SKVYP7NY.js +1647 -0
- package/vendor/chunk-TSRPHKAC.js +780 -0
- package/vendor/chunk-UI5RLBKQ.js +30 -0
- package/vendor/chunk-VPC5NLZG.js +259 -0
- package/vendor/chunk-W2HNI6AA.js +75 -0
- package/vendor/chunk-X6M525ZJ.js +20 -0
- package/vendor/chunk-XIZVLPHZ.js +18 -0
- package/vendor/chunk-XNFVSVBS.js +242 -0
- package/vendor/chunk-YCDEMYY6.js +2076 -0
- package/vendor/chunk-YJ6XUZT6.js +292 -0
- package/vendor/chunk-YJWREXN6.js +26020 -0
- package/vendor/chunk-YK4F4VY2.js +173 -0
- package/vendor/chunk-YOFI4PCH.js +36 -0
- package/vendor/chunk-ZUGNCQZE.js +247 -0
- package/vendor/chunk-ZXX4ZNJY.js +21 -0
- package/vendor/cli.js +7 -0
- package/vendor/component-manifest-catalog.js +8 -0
- package/vendor/control-plane.js +23 -0
- package/vendor/package.json +1 -0
- package/vendor/privacy-service-bootstrap.js +6 -0
|
@@ -0,0 +1,2076 @@
|
|
|
1
|
+
import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
collectKoedServerDoctor,
|
|
4
|
+
collectKoedServerStartupStatus,
|
|
5
|
+
collectKoedServerStatus,
|
|
6
|
+
ensurePackagedLocalServiceSecrets,
|
|
7
|
+
koedServerStartupBlockingComponentIds,
|
|
8
|
+
migrateKoedOwnedCodexRegistrationBestEffort,
|
|
9
|
+
pinAndResolvePackagedRuntime,
|
|
10
|
+
provisionDesktopApiToken,
|
|
11
|
+
provisionLocalApiToken,
|
|
12
|
+
removeClaude,
|
|
13
|
+
removeCodexIntegration,
|
|
14
|
+
removePi,
|
|
15
|
+
repairCodexIntegration,
|
|
16
|
+
setupClaude,
|
|
17
|
+
setupCodex,
|
|
18
|
+
setupCore,
|
|
19
|
+
setupPi,
|
|
20
|
+
startLocalEmbeddingRuntime,
|
|
21
|
+
startLocalPostgresRuntime,
|
|
22
|
+
startLocalPrivacyRuntime,
|
|
23
|
+
stopLocalPostgresRuntime
|
|
24
|
+
} from "./chunk-HZJK5RWC.js";
|
|
25
|
+
import {
|
|
26
|
+
resolveActiveIntegrationApiToken,
|
|
27
|
+
resolveLocalApiToken,
|
|
28
|
+
writeLocalAppCredential
|
|
29
|
+
} from "./chunk-QT24SAW6.js";
|
|
30
|
+
import {
|
|
31
|
+
invalidateUpstreamEnrollmentReferences
|
|
32
|
+
} from "./chunk-SKVYP7NY.js";
|
|
33
|
+
import {
|
|
34
|
+
allocateAndPersistLocalPorts
|
|
35
|
+
} from "./chunk-HFM6ROJL.js";
|
|
36
|
+
import {
|
|
37
|
+
calculateRuntimeRequirements,
|
|
38
|
+
resolveEffectiveRuntimeConfig
|
|
39
|
+
} from "./chunk-AFLKCQHF.js";
|
|
40
|
+
import {
|
|
41
|
+
acquireKoedServerSupervisorLock,
|
|
42
|
+
readCurrentRuntimeOwner,
|
|
43
|
+
readSupervisorLock,
|
|
44
|
+
releaseKoedServerSupervisorLock,
|
|
45
|
+
resolveDesktopRuntimeOwner,
|
|
46
|
+
resolveKoedServerConfig,
|
|
47
|
+
resolveProcessIdentity,
|
|
48
|
+
validateDesktopRuntimeCapability
|
|
49
|
+
} from "./chunk-NM5NXCVM.js";
|
|
50
|
+
import {
|
|
51
|
+
isProcessRunning,
|
|
52
|
+
isRuntimeGenerationState
|
|
53
|
+
} from "./chunk-G4IIVVJO.js";
|
|
54
|
+
import {
|
|
55
|
+
maintainSupervisorLog,
|
|
56
|
+
startKoedServerDaemon
|
|
57
|
+
} from "./chunk-JKOG52PE.js";
|
|
58
|
+
import {
|
|
59
|
+
assertKoedAppRuntimeAvailable,
|
|
60
|
+
resolveKoedAppRuntime,
|
|
61
|
+
resolveKoedAppRuntimeExecution,
|
|
62
|
+
resolveKoedRuntimeOwner
|
|
63
|
+
} from "./chunk-A4NKL2PK.js";
|
|
64
|
+
import {
|
|
65
|
+
listUpstreamBackends,
|
|
66
|
+
refreshUpstreamBackendCapabilities
|
|
67
|
+
} from "./chunk-YJWREXN6.js";
|
|
68
|
+
import {
|
|
69
|
+
redeemPairingLink
|
|
70
|
+
} from "./chunk-J2L726BC.js";
|
|
71
|
+
import {
|
|
72
|
+
bundledPdsSecretProviderEnvironment
|
|
73
|
+
} from "./chunk-2S3OGPUC.js";
|
|
74
|
+
import {
|
|
75
|
+
ensureDeviceIdentity,
|
|
76
|
+
inspectDeviceIdentityStatus,
|
|
77
|
+
rotateDeviceIdentity
|
|
78
|
+
} from "./chunk-XNFVSVBS.js";
|
|
79
|
+
import {
|
|
80
|
+
ensureKoedHome,
|
|
81
|
+
resolveKoedServerPaths
|
|
82
|
+
} from "./chunk-K2EIFEQF.js";
|
|
83
|
+
import {
|
|
84
|
+
environmentWithRepoEnv,
|
|
85
|
+
loadRepoEnv,
|
|
86
|
+
resolveApiUrl
|
|
87
|
+
} from "./chunk-W2HNI6AA.js";
|
|
88
|
+
import {
|
|
89
|
+
createPdsApplicationSecretStore,
|
|
90
|
+
isPrivateNetworkIpv4Address
|
|
91
|
+
} from "./chunk-473OA62O.js";
|
|
92
|
+
import {
|
|
93
|
+
resolveLocalModelManifest
|
|
94
|
+
} from "./chunk-ZUGNCQZE.js";
|
|
95
|
+
|
|
96
|
+
// packages/koed-server/dist/personal-device-request.js
|
|
97
|
+
import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID2 } from "node:crypto";
|
|
98
|
+
import { createServer, request as httpRequest } from "node:http";
|
|
99
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, unlinkSync } from "node:fs";
|
|
100
|
+
import { hostname, networkInterfaces } from "node:os";
|
|
101
|
+
import { join } from "node:path";
|
|
102
|
+
|
|
103
|
+
// packages/koed-server/dist/personal-device-request-crypto.js
|
|
104
|
+
import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes, randomUUID } from "node:crypto";
|
|
105
|
+
var isPersonalDevicePairingUuid = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
106
|
+
var PERSONAL_DEVICE_PAIRING_PROTOCOL = "koed/pds-device-request/v1";
|
|
107
|
+
var PERSONAL_DEVICE_PAIRING_MAX_PLAINTEXT_BYTES = 256 * 1024;
|
|
108
|
+
var exactKeys = (value, expected) => {
|
|
109
|
+
const actual = Object.keys(value).sort();
|
|
110
|
+
const sortedExpected = [...expected].sort();
|
|
111
|
+
return actual.length === sortedExpected.length && actual.every((key, index) => key === sortedExpected[index]);
|
|
112
|
+
};
|
|
113
|
+
var strictBase64url = (value, bytes) => {
|
|
114
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
115
|
+
throw new Error("Pairing message is invalid.");
|
|
116
|
+
}
|
|
117
|
+
const decoded = Buffer.from(value, "base64url");
|
|
118
|
+
if (bytes !== void 0 && decoded.length !== bytes || decoded.toString("base64url") !== value) {
|
|
119
|
+
throw new Error("Pairing message is invalid.");
|
|
120
|
+
}
|
|
121
|
+
return decoded;
|
|
122
|
+
};
|
|
123
|
+
var pairingKey = (invitationId, token) => {
|
|
124
|
+
if (!isPersonalDevicePairingUuid(invitationId) || !/^[A-Za-z0-9_-]{43}$/.test(token)) {
|
|
125
|
+
throw new Error("Pairing credentials are invalid.");
|
|
126
|
+
}
|
|
127
|
+
const tokenBytes = strictBase64url(token, 32);
|
|
128
|
+
const salt = createHash("sha256").update(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}\0${invitationId}`, "utf8").digest();
|
|
129
|
+
return Buffer.from(hkdfSync("sha256", tokenBytes, salt, Buffer.from(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}/transport-key`, "utf8"), 32));
|
|
130
|
+
};
|
|
131
|
+
var additionalData = (direction, invitationId, messageId) => Buffer.from(`${PERSONAL_DEVICE_PAIRING_PROTOCOL}/${direction}
|
|
132
|
+
${invitationId}
|
|
133
|
+
${messageId}`, "utf8");
|
|
134
|
+
var parseEnvelope = (value) => {
|
|
135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
136
|
+
throw new Error("Pairing message is invalid.");
|
|
137
|
+
}
|
|
138
|
+
const envelope = value;
|
|
139
|
+
if (!exactKeys(envelope, [
|
|
140
|
+
"protocol",
|
|
141
|
+
"message_id",
|
|
142
|
+
"nonce",
|
|
143
|
+
"ciphertext",
|
|
144
|
+
"tag"
|
|
145
|
+
]) || envelope.protocol !== PERSONAL_DEVICE_PAIRING_PROTOCOL || !isPersonalDevicePairingUuid(envelope.message_id)) {
|
|
146
|
+
throw new Error("Pairing message is invalid.");
|
|
147
|
+
}
|
|
148
|
+
strictBase64url(envelope.nonce, 12);
|
|
149
|
+
strictBase64url(envelope.tag, 16);
|
|
150
|
+
const ciphertext = strictBase64url(envelope.ciphertext);
|
|
151
|
+
if (ciphertext.length > PERSONAL_DEVICE_PAIRING_MAX_PLAINTEXT_BYTES) {
|
|
152
|
+
throw new Error("Pairing message is too large.");
|
|
153
|
+
}
|
|
154
|
+
return envelope;
|
|
155
|
+
};
|
|
156
|
+
var encryptPersonalDevicePairingMessage = (input, options) => {
|
|
157
|
+
const plaintext = Buffer.from(JSON.stringify(input), "utf8");
|
|
158
|
+
if (plaintext.length === 0 || plaintext.length > PERSONAL_DEVICE_PAIRING_MAX_PLAINTEXT_BYTES) {
|
|
159
|
+
throw new Error("Pairing message is too large.");
|
|
160
|
+
}
|
|
161
|
+
const messageId = options.messageId ?? randomUUID();
|
|
162
|
+
const nonce = randomBytes(12);
|
|
163
|
+
const cipher = createCipheriv("aes-256-gcm", pairingKey(options.invitationId, options.token), nonce);
|
|
164
|
+
cipher.setAAD(additionalData(options.direction, options.invitationId, messageId));
|
|
165
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
166
|
+
return {
|
|
167
|
+
protocol: PERSONAL_DEVICE_PAIRING_PROTOCOL,
|
|
168
|
+
message_id: messageId,
|
|
169
|
+
nonce: nonce.toString("base64url"),
|
|
170
|
+
ciphertext: ciphertext.toString("base64url"),
|
|
171
|
+
tag: cipher.getAuthTag().toString("base64url")
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
var decryptPersonalDevicePairingMessage = (input, options) => {
|
|
175
|
+
const envelope = parseEnvelope(input);
|
|
176
|
+
try {
|
|
177
|
+
const decipher = createDecipheriv("aes-256-gcm", pairingKey(options.invitationId, options.token), strictBase64url(envelope.nonce, 12));
|
|
178
|
+
decipher.setAAD(additionalData(options.direction, options.invitationId, envelope.message_id));
|
|
179
|
+
decipher.setAuthTag(strictBase64url(envelope.tag, 16));
|
|
180
|
+
const plaintext = Buffer.concat([
|
|
181
|
+
decipher.update(strictBase64url(envelope.ciphertext)),
|
|
182
|
+
decipher.final()
|
|
183
|
+
]);
|
|
184
|
+
if (plaintext.length === 0 || plaintext.length > PERSONAL_DEVICE_PAIRING_MAX_PLAINTEXT_BYTES) {
|
|
185
|
+
throw new Error("Pairing message is too large.");
|
|
186
|
+
}
|
|
187
|
+
const value = JSON.parse(plaintext.toString("utf8"));
|
|
188
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
189
|
+
throw new Error("Pairing message is invalid.");
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
messageId: envelope.message_id,
|
|
193
|
+
value
|
|
194
|
+
};
|
|
195
|
+
} catch {
|
|
196
|
+
throw new Error("Pairing message authentication failed.");
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// packages/koed-server/dist/personal-device-request.js
|
|
201
|
+
var ttl = 10 * 6e4;
|
|
202
|
+
var maxBytes = 32768;
|
|
203
|
+
var reference = "pds-device-request";
|
|
204
|
+
var uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
205
|
+
var body = async (input) => {
|
|
206
|
+
const chunks = [];
|
|
207
|
+
let size = 0;
|
|
208
|
+
for await (const part of input) {
|
|
209
|
+
const chunk = Buffer.from(part);
|
|
210
|
+
size += chunk.length;
|
|
211
|
+
if (size > maxBytes)
|
|
212
|
+
throw new Error("Request too large.");
|
|
213
|
+
chunks.push(chunk);
|
|
214
|
+
}
|
|
215
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
216
|
+
};
|
|
217
|
+
var reply = (response, status, value) => {
|
|
218
|
+
response.writeHead(status, {
|
|
219
|
+
"content-type": "application/json",
|
|
220
|
+
"cache-control": "no-store",
|
|
221
|
+
"x-content-type-options": "nosniff"
|
|
222
|
+
});
|
|
223
|
+
response.end(JSON.stringify(value));
|
|
224
|
+
};
|
|
225
|
+
var socketPath = (paths) => {
|
|
226
|
+
const direct = join(paths.runDir, "device-request.sock");
|
|
227
|
+
if (Buffer.byteLength(direct) < 100)
|
|
228
|
+
return direct;
|
|
229
|
+
const directory = `/tmp/koed-pairing-${process.getuid?.() ?? "local"}`;
|
|
230
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
231
|
+
const entry = lstatSync(directory);
|
|
232
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || (entry.mode & 63) !== 0 || process.getuid && entry.uid !== process.getuid())
|
|
233
|
+
throw new Error("The private pairing socket directory is unsafe.");
|
|
234
|
+
return join(directory, `${createHash2("sha256").update(paths.koedHome).digest("hex").slice(0, 32)}.sock`);
|
|
235
|
+
};
|
|
236
|
+
var deviceRequestCommand = (paths, command, label) => new Promise((resolve6, reject) => {
|
|
237
|
+
const request = httpRequest({
|
|
238
|
+
agent: false,
|
|
239
|
+
socketPath: socketPath(paths),
|
|
240
|
+
path: "/",
|
|
241
|
+
method: "POST",
|
|
242
|
+
timeout: 1e4,
|
|
243
|
+
headers: { "content-type": "application/json" }
|
|
244
|
+
}, (response) => {
|
|
245
|
+
let raw = "";
|
|
246
|
+
response.on("data", (chunk) => {
|
|
247
|
+
raw += String(chunk);
|
|
248
|
+
if (Buffer.byteLength(raw) > maxBytes)
|
|
249
|
+
response.destroy(new Error("Invalid pairing response."));
|
|
250
|
+
});
|
|
251
|
+
response.on("error", reject);
|
|
252
|
+
response.on("end", () => {
|
|
253
|
+
try {
|
|
254
|
+
const value = JSON.parse(raw);
|
|
255
|
+
if (response.statusCode !== 200)
|
|
256
|
+
throw new Error(value.error ?? "Pairing unavailable.");
|
|
257
|
+
resolve6(value);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
reject(error instanceof Error ? error : new Error("Invalid pairing response."));
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
request.on("timeout", () => request.destroy(new Error("Koed pairing service timed out.")));
|
|
264
|
+
request.on("error", () => reject(new Error("Start Koed before connecting this device.")));
|
|
265
|
+
request.end(JSON.stringify({ command, ...label ? { label } : {} }));
|
|
266
|
+
});
|
|
267
|
+
var resolveDeviceRequestHost = (configuredHost, addresses) => {
|
|
268
|
+
const trimmed = configuredHost?.trim();
|
|
269
|
+
if (trimmed) {
|
|
270
|
+
if (!isPrivateNetworkIpv4Address(trimmed))
|
|
271
|
+
throw new Error("Request service host must be a private IPv4 interface address.");
|
|
272
|
+
return trimmed;
|
|
273
|
+
}
|
|
274
|
+
return [...addresses].filter(isPrivateNetworkIpv4Address).sort()[0];
|
|
275
|
+
};
|
|
276
|
+
var startDeviceRequestService = async (options) => {
|
|
277
|
+
const { paths } = options;
|
|
278
|
+
const store = createPdsApplicationSecretStore({ rootPath: paths.koedHome });
|
|
279
|
+
let pending = null;
|
|
280
|
+
let publicServer = null;
|
|
281
|
+
let running = null;
|
|
282
|
+
let mutation = Promise.resolve();
|
|
283
|
+
let closed = false;
|
|
284
|
+
const enrollmentAbort = new AbortController();
|
|
285
|
+
const persist = () => {
|
|
286
|
+
if (pending)
|
|
287
|
+
store.put(reference, JSON.stringify(pending));
|
|
288
|
+
else
|
|
289
|
+
store.delete(reference);
|
|
290
|
+
};
|
|
291
|
+
const view = (includeLink = false) => {
|
|
292
|
+
if (!pending)
|
|
293
|
+
throw new Error("No pending device request. Create one first.");
|
|
294
|
+
return {
|
|
295
|
+
id: pending.id,
|
|
296
|
+
label: pending.label,
|
|
297
|
+
expiresAt: pending.expiresAt,
|
|
298
|
+
state: pending.state,
|
|
299
|
+
...includeLink && pending.state === "waiting" ? {
|
|
300
|
+
link: `http://${pending.host}:${pending.port}/device-request/${pending.id}#token=${pending.token}`
|
|
301
|
+
} : {}
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
const resume = () => {
|
|
305
|
+
if (!pending?.invitation || running || pending.state !== "connecting")
|
|
306
|
+
return;
|
|
307
|
+
const current = pending;
|
|
308
|
+
running = options.redeem(current.invitation, current.label, enrollmentAbort.signal).then(() => {
|
|
309
|
+
if (closed)
|
|
310
|
+
return;
|
|
311
|
+
current.state = "connected";
|
|
312
|
+
delete current.invitation;
|
|
313
|
+
current.token = "";
|
|
314
|
+
persist();
|
|
315
|
+
}).catch(() => {
|
|
316
|
+
if (closed)
|
|
317
|
+
return;
|
|
318
|
+
current.state = "failed";
|
|
319
|
+
delete current.invitation;
|
|
320
|
+
current.token = "";
|
|
321
|
+
persist();
|
|
322
|
+
}).finally(() => {
|
|
323
|
+
running = null;
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
const listen = async () => {
|
|
327
|
+
if (!pending)
|
|
328
|
+
return;
|
|
329
|
+
const current = pending;
|
|
330
|
+
const server = createServer((request, response) => {
|
|
331
|
+
void (async () => {
|
|
332
|
+
if (request.method !== "POST" || request.url !== `/device-request/${current.id}` || request.headers.host !== `${current.host}:${current.port}` || request.headers.origin)
|
|
333
|
+
throw new Error("Invalid request.");
|
|
334
|
+
const raw = await body(request);
|
|
335
|
+
const transaction = mutation.then(() => {
|
|
336
|
+
if (closed || Date.now() >= Date.parse(current.expiresAt) || !current.token || current.used.length >= 64)
|
|
337
|
+
throw new Error("Expired.");
|
|
338
|
+
const decoded = decryptPersonalDevicePairingMessage(raw, {
|
|
339
|
+
invitationId: current.id,
|
|
340
|
+
token: current.token,
|
|
341
|
+
direction: "request"
|
|
342
|
+
});
|
|
343
|
+
if (current.used.includes(decoded.messageId))
|
|
344
|
+
throw new Error("Replay.");
|
|
345
|
+
const operation = decoded.value.operation;
|
|
346
|
+
if (operation !== "inspect" && operation !== "accept")
|
|
347
|
+
throw new Error("Invalid operation.");
|
|
348
|
+
const expected = operation === "inspect" ? ["operation"] : ["invitation", "operation"];
|
|
349
|
+
if (Object.keys(decoded.value).sort().join() !== expected.join())
|
|
350
|
+
throw new Error("Invalid request.");
|
|
351
|
+
if (operation === "accept") {
|
|
352
|
+
if (typeof decoded.value.invitation !== "string" || decoded.value.invitation.length > 4096)
|
|
353
|
+
throw new Error("Invalid invitation.");
|
|
354
|
+
if (current.state !== "waiting" && !(current.state === "connecting" && current.invitation === decoded.value.invitation))
|
|
355
|
+
throw new Error("Already accepted.");
|
|
356
|
+
current.invitation = decoded.value.invitation;
|
|
357
|
+
current.state = "connecting";
|
|
358
|
+
}
|
|
359
|
+
current.used.push(decoded.messageId);
|
|
360
|
+
persist();
|
|
361
|
+
const result = encryptPersonalDevicePairingMessage({
|
|
362
|
+
id: current.id,
|
|
363
|
+
label: current.label,
|
|
364
|
+
expiresAt: current.expiresAt,
|
|
365
|
+
state: current.state
|
|
366
|
+
}, {
|
|
367
|
+
invitationId: current.id,
|
|
368
|
+
token: current.token,
|
|
369
|
+
direction: "response",
|
|
370
|
+
messageId: decoded.messageId
|
|
371
|
+
});
|
|
372
|
+
reply(response, 200, result);
|
|
373
|
+
if (operation === "accept")
|
|
374
|
+
resume();
|
|
375
|
+
});
|
|
376
|
+
mutation = transaction.catch(() => void 0);
|
|
377
|
+
await transaction;
|
|
378
|
+
})().catch(() => reply(response, 400, { error: "Device request unavailable." }));
|
|
379
|
+
});
|
|
380
|
+
server.requestTimeout = 1e4;
|
|
381
|
+
server.headersTimeout = 1e4;
|
|
382
|
+
server.maxConnections = 16;
|
|
383
|
+
await new Promise((resolve6, reject) => {
|
|
384
|
+
server.once("error", reject);
|
|
385
|
+
server.listen(current.port, current.host, () => {
|
|
386
|
+
server.off("error", reject);
|
|
387
|
+
resolve6();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
const address = server.address();
|
|
391
|
+
if (!address || typeof address === "string")
|
|
392
|
+
throw new Error("Pairing listener unavailable.");
|
|
393
|
+
current.port = address.port;
|
|
394
|
+
publicServer = server;
|
|
395
|
+
persist();
|
|
396
|
+
};
|
|
397
|
+
const closePublic = async () => {
|
|
398
|
+
const server = publicServer;
|
|
399
|
+
publicServer = null;
|
|
400
|
+
if (server) {
|
|
401
|
+
server.closeAllConnections();
|
|
402
|
+
await new Promise((resolve6) => server.close(() => resolve6()));
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const saved = store.get(reference);
|
|
406
|
+
if (saved) {
|
|
407
|
+
try {
|
|
408
|
+
const value = JSON.parse(saved);
|
|
409
|
+
if (uuid.test(value.id) && isPrivateNetworkIpv4Address(value.host) && Number.isInteger(value.port) && value.port > 0 && value.port <= 65535 && typeof value.label === "string" && value.label.length <= 80 && Array.isArray(value.used) && value.used.length <= 64 && value.used.every((id) => typeof id === "string" && uuid.test(id)) && Number.isFinite(Date.parse(value.expiresAt)) && Date.parse(value.expiresAt) <= Date.now() + ttl && typeof value.token === "string" && (["waiting", "connecting"].includes(value.state) && /^[A-Za-z0-9_-]{43}$/.test(value.token) || ["connected", "failed", "cancelled", "expired"].includes(value.state) && value.token === "" && !value.invitation)) {
|
|
410
|
+
pending = value;
|
|
411
|
+
if (!["waiting", "connecting"].includes(value.state)) {
|
|
412
|
+
} else if (Date.now() < Date.parse(value.expiresAt)) {
|
|
413
|
+
await listen();
|
|
414
|
+
resume();
|
|
415
|
+
} else {
|
|
416
|
+
pending.state = "expired";
|
|
417
|
+
pending.token = "";
|
|
418
|
+
delete pending.invitation;
|
|
419
|
+
persist();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} catch {
|
|
423
|
+
pending = null;
|
|
424
|
+
store.delete(reference);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const local = createServer((request, response) => {
|
|
428
|
+
void (async () => {
|
|
429
|
+
if (request.method !== "POST" || request.url !== "/")
|
|
430
|
+
throw new Error("Invalid command.");
|
|
431
|
+
const input = await body(request);
|
|
432
|
+
const transaction = mutation.then(async () => {
|
|
433
|
+
if (closed)
|
|
434
|
+
throw new Error("Pairing service stopped.");
|
|
435
|
+
if (input.command === "create") {
|
|
436
|
+
if (pending?.state !== "connecting" && await options.enrolled())
|
|
437
|
+
throw new Error("This installation already belongs to a Personal Device Group.");
|
|
438
|
+
if (!pending || !["waiting", "connecting"].includes(pending.state)) {
|
|
439
|
+
await closePublic();
|
|
440
|
+
const availableAddresses = options.addresses?.() ?? Object.values(networkInterfaces()).flatMap((entries) => entries ?? []).filter((entry) => entry.family === "IPv4" && !entry.internal).map((entry) => entry.address);
|
|
441
|
+
const host = resolveDeviceRequestHost(options.host, availableAddresses);
|
|
442
|
+
if (!host)
|
|
443
|
+
throw new Error("Connect to a private LAN or Tailscale network before pairing.");
|
|
444
|
+
const label = typeof input.label === "string" ? input.label.trim() : hostname();
|
|
445
|
+
if (!label || label.length > 80 || Array.from(label).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
|
|
446
|
+
throw new Error("Device name must contain 1\u201380 printable characters.");
|
|
447
|
+
pending = {
|
|
448
|
+
id: randomUUID2(),
|
|
449
|
+
label,
|
|
450
|
+
host,
|
|
451
|
+
port: 0,
|
|
452
|
+
token: randomBytes2(32).toString("base64url"),
|
|
453
|
+
expiresAt: new Date(Date.now() + ttl).toISOString(),
|
|
454
|
+
state: "waiting",
|
|
455
|
+
used: []
|
|
456
|
+
};
|
|
457
|
+
await listen();
|
|
458
|
+
}
|
|
459
|
+
return view(true);
|
|
460
|
+
}
|
|
461
|
+
if (input.command === "cancel") {
|
|
462
|
+
if (pending?.state === "connecting" || pending?.state === "connected")
|
|
463
|
+
throw new Error("Enrollment has started. Check its result and use device removal to revoke membership.");
|
|
464
|
+
if (pending?.state === "waiting") {
|
|
465
|
+
pending.state = "cancelled";
|
|
466
|
+
pending.token = "";
|
|
467
|
+
delete pending.invitation;
|
|
468
|
+
persist();
|
|
469
|
+
await closePublic();
|
|
470
|
+
}
|
|
471
|
+
} else if (input.command !== "status")
|
|
472
|
+
throw new Error("Invalid command.");
|
|
473
|
+
return view();
|
|
474
|
+
});
|
|
475
|
+
mutation = transaction.then(() => void 0, () => void 0);
|
|
476
|
+
reply(response, 200, await transaction);
|
|
477
|
+
})().catch((error) => reply(response, 400, {
|
|
478
|
+
error: error instanceof Error ? error.message : "Pairing unavailable."
|
|
479
|
+
}));
|
|
480
|
+
});
|
|
481
|
+
const path = socketPath(paths);
|
|
482
|
+
if (existsSync(path)) {
|
|
483
|
+
if (!lstatSync(path).isSocket())
|
|
484
|
+
throw new Error("Unsafe pairing socket path.");
|
|
485
|
+
unlinkSync(path);
|
|
486
|
+
}
|
|
487
|
+
await new Promise((resolve6, reject) => {
|
|
488
|
+
local.once("error", reject);
|
|
489
|
+
local.listen(path, () => {
|
|
490
|
+
chmodSync(path, 384);
|
|
491
|
+
resolve6();
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
const timer = setInterval(() => {
|
|
495
|
+
mutation = mutation.then(async () => {
|
|
496
|
+
if (closed || !pending || pending.state !== "waiting" || Date.now() < Date.parse(pending.expiresAt))
|
|
497
|
+
return;
|
|
498
|
+
pending.state = "expired";
|
|
499
|
+
pending.token = "";
|
|
500
|
+
persist();
|
|
501
|
+
await closePublic();
|
|
502
|
+
}).catch(() => void 0);
|
|
503
|
+
}, 1e3);
|
|
504
|
+
timer.unref();
|
|
505
|
+
return {
|
|
506
|
+
close: async () => {
|
|
507
|
+
closed = true;
|
|
508
|
+
enrollmentAbort.abort();
|
|
509
|
+
clearInterval(timer);
|
|
510
|
+
await mutation;
|
|
511
|
+
await closePublic();
|
|
512
|
+
local.closeAllConnections();
|
|
513
|
+
await new Promise((resolve6) => local.close(() => resolve6()));
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// packages/koed-server/dist/restart.js
|
|
519
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
520
|
+
|
|
521
|
+
// packages/koed-server/dist/stop.js
|
|
522
|
+
import { existsSync as nodeExistsSync, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
523
|
+
import { spawnSync as nodeSpawnSync } from "node:child_process";
|
|
524
|
+
import { resolve as resolve2 } from "node:path";
|
|
525
|
+
|
|
526
|
+
// packages/koed-server/dist/supervisor-exit-request.js
|
|
527
|
+
import { mkdirSync as mkdirSync2, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
528
|
+
import { resolve } from "node:path";
|
|
529
|
+
var supervisorExitRequestPath = (paths) => resolve(paths.runDir, "koed-server.stop.json");
|
|
530
|
+
var requestSupervisorExit = (paths, identity) => {
|
|
531
|
+
mkdirSync2(paths.runDir, { recursive: true, mode: 448 });
|
|
532
|
+
writeFileSync(supervisorExitRequestPath(paths), `${JSON.stringify(identity, null, 2)}
|
|
533
|
+
`, { mode: 384 });
|
|
534
|
+
};
|
|
535
|
+
var readExitRequest = (path) => {
|
|
536
|
+
try {
|
|
537
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
538
|
+
return Number.isInteger(value.pid) && Number(value.pid) > 0 && typeof value.startedAt === "string" ? { pid: Number(value.pid), startedAt: value.startedAt } : null;
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
var monitorSupervisorExitRequest = (paths, identity, options = {}) => {
|
|
544
|
+
const path = supervisorExitRequestPath(paths);
|
|
545
|
+
let handled = false;
|
|
546
|
+
const check = () => {
|
|
547
|
+
if (handled)
|
|
548
|
+
return;
|
|
549
|
+
const request = readExitRequest(path);
|
|
550
|
+
if (request?.pid !== identity.pid || request.startedAt !== identity.startedAt) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
handled = true;
|
|
554
|
+
rmSync(path, { force: true });
|
|
555
|
+
(options.onExit ?? (() => process.exit(0)))();
|
|
556
|
+
};
|
|
557
|
+
const timer = (options.setInterval ?? setInterval)(check, options.intervalMs ?? 100);
|
|
558
|
+
timer.unref?.();
|
|
559
|
+
check();
|
|
560
|
+
return () => (options.clearInterval ?? clearInterval)(timer);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// packages/koed-server/dist/stop.js
|
|
564
|
+
var APP_PROCESS_ORDER = [
|
|
565
|
+
{
|
|
566
|
+
processName: "localAiRuntime",
|
|
567
|
+
serviceName: "local-ai-runtime"
|
|
568
|
+
},
|
|
569
|
+
{ processName: "worker", serviceName: "worker" },
|
|
570
|
+
{ processName: "api", serviceName: "api" }
|
|
571
|
+
];
|
|
572
|
+
var readRuntimeState = (path, deps) => {
|
|
573
|
+
if (!deps.existsSync(path)) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
return JSON.parse(String(deps.readFileSync(path, "utf8")));
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
var readRuntimeGeneration = (path, deps) => {
|
|
583
|
+
if (!deps.existsSync(path))
|
|
584
|
+
return null;
|
|
585
|
+
try {
|
|
586
|
+
const parsed = JSON.parse(String(deps.readFileSync(path, "utf8")));
|
|
587
|
+
return isRuntimeGenerationState(parsed) ? parsed : null;
|
|
588
|
+
} catch {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
var expectedRuntimeOwner = (capability, koedHome) => capability ? resolveDesktopRuntimeOwner(capability, koedHome) : resolveKoedRuntimeOwner();
|
|
593
|
+
var ownersMatch = (left, right) => left.kind === right.kind && left.installationId === right.installationId;
|
|
594
|
+
var defaultCheckPid = (pid) => {
|
|
595
|
+
try {
|
|
596
|
+
process.kill(pid, 0);
|
|
597
|
+
return true;
|
|
598
|
+
} catch {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var errorCode = (error) => typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
603
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
604
|
+
var defaultSleepSync = (ms) => {
|
|
605
|
+
if (ms <= 0)
|
|
606
|
+
return;
|
|
607
|
+
const buffer = new SharedArrayBuffer(4);
|
|
608
|
+
const view = new Int32Array(buffer);
|
|
609
|
+
Atomics.wait(view, 0, 0, ms);
|
|
610
|
+
};
|
|
611
|
+
var waitForPidExit = (pid, deps) => {
|
|
612
|
+
const deadline = Date.now() + deps.waitForExitMs;
|
|
613
|
+
while (deps.checkPid(pid) && Date.now() < deadline) {
|
|
614
|
+
deps.sleepSync(Math.min(deps.pollIntervalMs, Math.max(0, deadline - Date.now())));
|
|
615
|
+
}
|
|
616
|
+
return !deps.checkPid(pid);
|
|
617
|
+
};
|
|
618
|
+
var stopPid = (label, pid, deps) => {
|
|
619
|
+
if (!pid || pid <= 0) {
|
|
620
|
+
return { stoppedPids: [], missingPids: [], errors: [] };
|
|
621
|
+
}
|
|
622
|
+
const identityMatches = () => !deps.expectedProcessIdentity || deps.resolveProcessIdentity?.(pid) === deps.expectedProcessIdentity;
|
|
623
|
+
const identityError = () => ({
|
|
624
|
+
stoppedPids: [],
|
|
625
|
+
missingPids: [],
|
|
626
|
+
errors: [
|
|
627
|
+
{
|
|
628
|
+
target: `${label} (${pid})`,
|
|
629
|
+
error: "Process identity changed before signaling"
|
|
630
|
+
}
|
|
631
|
+
]
|
|
632
|
+
});
|
|
633
|
+
if (deps.expectedProcessIdentity && !deps.checkPid(pid))
|
|
634
|
+
return { stoppedPids: [], missingPids: [pid], errors: [] };
|
|
635
|
+
if (!identityMatches())
|
|
636
|
+
return identityError();
|
|
637
|
+
try {
|
|
638
|
+
deps.kill(pid, "SIGTERM");
|
|
639
|
+
if (waitForPidExit(pid, deps)) {
|
|
640
|
+
return { stoppedPids: [pid], missingPids: [], errors: [] };
|
|
641
|
+
}
|
|
642
|
+
if (!identityMatches())
|
|
643
|
+
return identityError();
|
|
644
|
+
deps.kill(pid, "SIGKILL");
|
|
645
|
+
if (waitForPidExit(pid, deps)) {
|
|
646
|
+
return { stoppedPids: [pid], missingPids: [], errors: [] };
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
stoppedPids: [pid],
|
|
650
|
+
missingPids: [],
|
|
651
|
+
errors: [
|
|
652
|
+
{
|
|
653
|
+
target: `${label} (${pid})`,
|
|
654
|
+
error: "Timed out waiting for process to stop"
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
};
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (errorCode(error) === "ESRCH" || !deps.checkPid(pid)) {
|
|
660
|
+
return { stoppedPids: [], missingPids: [pid], errors: [] };
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
stoppedPids: [],
|
|
664
|
+
missingPids: [],
|
|
665
|
+
errors: [{ target: `${label} (${pid})`, error: errorMessage(error) }]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
var shouldStopNativePostgres = (runtime) => runtime.dependencyMode === "bundled-local" && runtime.services.includes("postgres-native");
|
|
670
|
+
var stopKoedServer = ({ environment = process.env, existsSync: pathExists = nodeExistsSync, readFileSync: readFile = readFileSync2, rmSync: remove = rmSync2, spawnSync = nodeSpawnSync, kill = (pid, signal) => {
|
|
671
|
+
process.kill(pid, signal);
|
|
672
|
+
}, checkPid = defaultCheckPid, waitForExitMs = 5e3, pollIntervalMs = 100, sleepSync = defaultSleepSync, readSupervisorLock: readLock = readSupervisorLock, resolveProcessIdentity: resolveProcessIdentity2 = resolveProcessIdentity, desktopRuntimeCapability } = {}) => {
|
|
673
|
+
const paths = resolveKoedServerPaths(environment);
|
|
674
|
+
const runtimeEnvironment = environmentWithRepoEnv(paths.repoRoot, environment);
|
|
675
|
+
const runtime = readRuntimeState(paths.runtimeStatePath, {
|
|
676
|
+
existsSync: pathExists,
|
|
677
|
+
readFileSync: readFile
|
|
678
|
+
});
|
|
679
|
+
if (!runtime) {
|
|
680
|
+
return {
|
|
681
|
+
ok: true,
|
|
682
|
+
state: "not_configured",
|
|
683
|
+
koedHome: paths.koedHome,
|
|
684
|
+
message: "No koed runtime state was found.",
|
|
685
|
+
stoppedPids: [],
|
|
686
|
+
missingPids: [],
|
|
687
|
+
stoppedServices: [],
|
|
688
|
+
missingServices: []
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
const stoppedPids = [];
|
|
692
|
+
const missingPids = [];
|
|
693
|
+
const stoppedServices = [];
|
|
694
|
+
const missingServices = [];
|
|
695
|
+
const errors = [];
|
|
696
|
+
let supervisorStoppedGracefully = false;
|
|
697
|
+
let supervisorLockMatches = true;
|
|
698
|
+
let expectedSupervisorIdentity;
|
|
699
|
+
const supervisorLockPath = resolve2(paths.runDir, "koed-server.lock");
|
|
700
|
+
const owner = expectedRuntimeOwner(desktopRuntimeCapability, paths.koedHome);
|
|
701
|
+
const ownerPinRequired = desktopRuntimeCapability !== void 0 || resolveKoedAppRuntimeExecution() === "packaged";
|
|
702
|
+
const isExpectedSupervisorRunning = (identity) => {
|
|
703
|
+
const lock2 = readLock(supervisorLockPath);
|
|
704
|
+
return lock2?.pid === runtime.pid && lock2.processIdentity === identity && checkPid(runtime.pid) && resolveProcessIdentity2(runtime.pid) === identity;
|
|
705
|
+
};
|
|
706
|
+
const markSupervisorIdentityUnverified = () => {
|
|
707
|
+
if (!supervisorLockMatches)
|
|
708
|
+
return;
|
|
709
|
+
supervisorLockMatches = false;
|
|
710
|
+
errors.push({
|
|
711
|
+
target: `supervisor (${runtime.pid})`,
|
|
712
|
+
error: "Supervisor identity changed during stop; refusing fallback signals"
|
|
713
|
+
});
|
|
714
|
+
};
|
|
715
|
+
const canSignalFallbackProcesses = () => {
|
|
716
|
+
if (!supervisorLockMatches)
|
|
717
|
+
return false;
|
|
718
|
+
if (expectedSupervisorIdentity && !isExpectedSupervisorRunning(expectedSupervisorIdentity)) {
|
|
719
|
+
markSupervisorIdentityUnverified();
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
return true;
|
|
723
|
+
};
|
|
724
|
+
const pinPath = paths.generationStatePath ?? resolve2(paths.runDir, "runtime-generation.json");
|
|
725
|
+
const pinExists = pathExists(pinPath);
|
|
726
|
+
const pin = readRuntimeGeneration(pinPath, {
|
|
727
|
+
existsSync: pathExists,
|
|
728
|
+
readFileSync: readFile
|
|
729
|
+
});
|
|
730
|
+
const lock = readLock(supervisorLockPath);
|
|
731
|
+
const supervisorRunning = runtime.pid > 0 && checkPid(runtime.pid);
|
|
732
|
+
const processIdentity = supervisorRunning ? resolveProcessIdentity2(runtime.pid) : null;
|
|
733
|
+
if (ownerPinRequired && !pin || pinExists && !pin || pin !== null && (pin.pid !== runtime.pid || !ownersMatch(pin.owner, owner) || lock?.pid !== runtime.pid || !lock.processIdentity || pin.processIdentity !== lock.processIdentity || supervisorRunning && processIdentity !== pin.processIdentity)) {
|
|
734
|
+
supervisorLockMatches = false;
|
|
735
|
+
errors.push({
|
|
736
|
+
target: `supervisor (${runtime.pid})`,
|
|
737
|
+
error: "Runtime owner and process identity could not be verified; refusing to stop"
|
|
738
|
+
});
|
|
739
|
+
} else if (pin) {
|
|
740
|
+
expectedSupervisorIdentity = pin.processIdentity;
|
|
741
|
+
}
|
|
742
|
+
if (runtime.pid > 0 && runtime.pid !== process.pid && checkPid(runtime.pid)) {
|
|
743
|
+
const lock2 = readLock(supervisorLockPath);
|
|
744
|
+
if (!supervisorLockMatches) {
|
|
745
|
+
} else if (lock2?.pid !== runtime.pid) {
|
|
746
|
+
supervisorLockMatches = false;
|
|
747
|
+
errors.push({
|
|
748
|
+
target: `supervisor (${runtime.pid})`,
|
|
749
|
+
error: "Runtime state does not match the active supervisor lock"
|
|
750
|
+
});
|
|
751
|
+
} else if (!lock2.processIdentity) {
|
|
752
|
+
supervisorLockMatches = false;
|
|
753
|
+
errors.push({
|
|
754
|
+
target: `supervisor (${runtime.pid})`,
|
|
755
|
+
error: "Supervisor process identity is missing from its lock"
|
|
756
|
+
});
|
|
757
|
+
} else if (resolveProcessIdentity2(runtime.pid) !== lock2.processIdentity) {
|
|
758
|
+
supervisorLockMatches = false;
|
|
759
|
+
errors.push({
|
|
760
|
+
target: `supervisor (${runtime.pid})`,
|
|
761
|
+
error: "Supervisor process identity does not match its lock"
|
|
762
|
+
});
|
|
763
|
+
} else {
|
|
764
|
+
expectedSupervisorIdentity = lock2.processIdentity;
|
|
765
|
+
requestSupervisorExit(paths, {
|
|
766
|
+
pid: runtime.pid,
|
|
767
|
+
startedAt: runtime.startedAt
|
|
768
|
+
});
|
|
769
|
+
supervisorStoppedGracefully = waitForPidExit(runtime.pid, {
|
|
770
|
+
checkPid,
|
|
771
|
+
waitForExitMs,
|
|
772
|
+
pollIntervalMs,
|
|
773
|
+
sleepSync
|
|
774
|
+
});
|
|
775
|
+
if (supervisorStoppedGracefully) {
|
|
776
|
+
stoppedPids.push(...Object.values(runtime.processes ?? {}).filter((pid) => typeof pid === "number" && pid > 0), runtime.pid);
|
|
777
|
+
stoppedServices.push(...runtime.services, "supervisor");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (!supervisorStoppedGracefully)
|
|
782
|
+
canSignalFallbackProcesses();
|
|
783
|
+
if (!supervisorStoppedGracefully && supervisorLockMatches) {
|
|
784
|
+
for (const { processName, serviceName } of APP_PROCESS_ORDER) {
|
|
785
|
+
if (!canSignalFallbackProcesses())
|
|
786
|
+
break;
|
|
787
|
+
const pid = runtime.processes?.[processName];
|
|
788
|
+
if (!pid || pid <= 0) {
|
|
789
|
+
if (runtime.services.includes(serviceName)) {
|
|
790
|
+
missingServices.push(serviceName);
|
|
791
|
+
}
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const stopped = stopPid(serviceName, pid, {
|
|
795
|
+
kill,
|
|
796
|
+
checkPid,
|
|
797
|
+
waitForExitMs,
|
|
798
|
+
pollIntervalMs,
|
|
799
|
+
sleepSync
|
|
800
|
+
});
|
|
801
|
+
stoppedPids.push(...stopped.stoppedPids);
|
|
802
|
+
missingPids.push(...stopped.missingPids);
|
|
803
|
+
if (stopped.missingPids.length > 0) {
|
|
804
|
+
missingServices.push(serviceName);
|
|
805
|
+
} else if (stopped.stoppedPids.length > 0) {
|
|
806
|
+
stoppedServices.push(serviceName);
|
|
807
|
+
}
|
|
808
|
+
errors.push(...stopped.errors);
|
|
809
|
+
}
|
|
810
|
+
if (runtime.dependencyMode === "bundled-local") {
|
|
811
|
+
const embeddingPid = runtime.processes?.embeddingService;
|
|
812
|
+
if (canSignalFallbackProcesses() && runtime.services.includes("embedding-service-native")) {
|
|
813
|
+
if (!embeddingPid || embeddingPid <= 0) {
|
|
814
|
+
missingServices.push("embedding-service-native");
|
|
815
|
+
} else {
|
|
816
|
+
const stopped = stopPid("embedding-service-native", embeddingPid, {
|
|
817
|
+
kill,
|
|
818
|
+
checkPid,
|
|
819
|
+
waitForExitMs,
|
|
820
|
+
pollIntervalMs,
|
|
821
|
+
sleepSync
|
|
822
|
+
});
|
|
823
|
+
stoppedPids.push(...stopped.stoppedPids);
|
|
824
|
+
missingPids.push(...stopped.missingPids);
|
|
825
|
+
errors.push(...stopped.errors);
|
|
826
|
+
if (stopped.missingPids.length > 0) {
|
|
827
|
+
missingServices.push("embedding-service-native");
|
|
828
|
+
} else if (stopped.stoppedPids.length > 0) {
|
|
829
|
+
stoppedServices.push("embedding-service-native");
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (canSignalFallbackProcesses() && shouldStopNativePostgres(runtime)) {
|
|
834
|
+
const stopped = stopLocalPostgresRuntime(paths, runtimeEnvironment, {
|
|
835
|
+
existsSync: pathExists,
|
|
836
|
+
spawnSync
|
|
837
|
+
});
|
|
838
|
+
if (stopped.ok) {
|
|
839
|
+
stoppedServices.push("postgres-native");
|
|
840
|
+
} else {
|
|
841
|
+
errors.push({
|
|
842
|
+
target: "postgres-native",
|
|
843
|
+
error: stopped.error ?? stopped.message
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (!supervisorStoppedGracefully && canSignalFallbackProcesses() && runtime.pid > 0 && runtime.pid !== process.pid && checkPid(runtime.pid)) {
|
|
850
|
+
const stopped = stopPid("supervisor", runtime.pid, {
|
|
851
|
+
kill,
|
|
852
|
+
checkPid,
|
|
853
|
+
waitForExitMs,
|
|
854
|
+
pollIntervalMs,
|
|
855
|
+
sleepSync,
|
|
856
|
+
expectedProcessIdentity: expectedSupervisorIdentity,
|
|
857
|
+
resolveProcessIdentity: resolveProcessIdentity2
|
|
858
|
+
});
|
|
859
|
+
stoppedPids.push(...stopped.stoppedPids);
|
|
860
|
+
missingPids.push(...stopped.missingPids);
|
|
861
|
+
errors.push(...stopped.errors);
|
|
862
|
+
if (stopped.missingPids.length > 0) {
|
|
863
|
+
missingServices.push("supervisor");
|
|
864
|
+
} else if (stopped.stoppedPids.length > 0) {
|
|
865
|
+
stoppedServices.push("supervisor");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (errors.length === 0) {
|
|
869
|
+
const currentRuntime = readRuntimeState(paths.runtimeStatePath, {
|
|
870
|
+
existsSync: pathExists,
|
|
871
|
+
readFileSync: readFile
|
|
872
|
+
});
|
|
873
|
+
if (currentRuntime && (currentRuntime.pid !== runtime.pid || currentRuntime.startedAt !== runtime.startedAt)) {
|
|
874
|
+
errors.push({
|
|
875
|
+
target: "runtime-state",
|
|
876
|
+
error: "Runtime state changed while stop was in progress"
|
|
877
|
+
});
|
|
878
|
+
} else {
|
|
879
|
+
remove(paths.runtimeStatePath, { force: true });
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
ok: errors.length === 0,
|
|
884
|
+
state: errors.length === 0 ? "healthy" : "needs_attention",
|
|
885
|
+
koedHome: paths.koedHome,
|
|
886
|
+
message: errors.length === 0 ? "Koed server stop completed." : "Koed server stop encountered errors.",
|
|
887
|
+
stoppedPids,
|
|
888
|
+
missingPids,
|
|
889
|
+
stoppedServices,
|
|
890
|
+
missingServices,
|
|
891
|
+
...errors.length > 0 ? { errors } : {}
|
|
892
|
+
};
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// packages/koed-server/dist/restart.js
|
|
896
|
+
var defaultCheckPid2 = (pid) => {
|
|
897
|
+
try {
|
|
898
|
+
process.kill(pid, 0);
|
|
899
|
+
return true;
|
|
900
|
+
} catch {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
var startDetached = ({ environment = process.env, spawn = nodeSpawn, startCommand = process.argv[1], cwd }) => {
|
|
905
|
+
if (!startCommand) {
|
|
906
|
+
throw new Error("Could not resolve koed-server CLI path for restart.");
|
|
907
|
+
}
|
|
908
|
+
const child = spawn(process.execPath, [startCommand, "start"], {
|
|
909
|
+
cwd: cwd ?? environment.KOED_REPO_ROOT ?? process.cwd(),
|
|
910
|
+
detached: true,
|
|
911
|
+
env: environment,
|
|
912
|
+
stdio: "ignore"
|
|
913
|
+
});
|
|
914
|
+
if (!child.pid) {
|
|
915
|
+
throw new Error("Could not start koed restart child process.");
|
|
916
|
+
}
|
|
917
|
+
child.unref();
|
|
918
|
+
return child.pid;
|
|
919
|
+
};
|
|
920
|
+
var waitForPidsToExit = async (pids, { checkPid = defaultCheckPid2, waitForExitMs = 1e4, pollIntervalMs = 250, sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)) }) => {
|
|
921
|
+
const uniquePids = [...new Set(pids)].filter((pid) => pid > 0);
|
|
922
|
+
const deadline = Date.now() + waitForExitMs;
|
|
923
|
+
let running = uniquePids.filter(checkPid);
|
|
924
|
+
while (running.length > 0 && Date.now() < deadline) {
|
|
925
|
+
await sleep(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())));
|
|
926
|
+
running = uniquePids.filter(checkPid);
|
|
927
|
+
}
|
|
928
|
+
return running;
|
|
929
|
+
};
|
|
930
|
+
var restartKoedServer = async (options = {}) => {
|
|
931
|
+
const stop = stopKoedServer(options);
|
|
932
|
+
if (!stop.ok) {
|
|
933
|
+
return {
|
|
934
|
+
ok: false,
|
|
935
|
+
state: "needs_attention",
|
|
936
|
+
koedHome: stop.koedHome,
|
|
937
|
+
message: stop.message,
|
|
938
|
+
stoppedPids: stop.stoppedPids,
|
|
939
|
+
missingPids: stop.missingPids,
|
|
940
|
+
stoppedServices: stop.stoppedServices,
|
|
941
|
+
missingServices: stop.missingServices
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const stillRunning = await waitForPidsToExit(stop.stoppedPids, options);
|
|
945
|
+
if (stillRunning.length > 0) {
|
|
946
|
+
const kill = options.kill ?? ((pid, signal) => {
|
|
947
|
+
process.kill(pid, signal);
|
|
948
|
+
});
|
|
949
|
+
for (const pid of stillRunning) {
|
|
950
|
+
kill(pid, "SIGKILL");
|
|
951
|
+
}
|
|
952
|
+
const runningAfterKill = await waitForPidsToExit(stillRunning, options);
|
|
953
|
+
if (runningAfterKill.length > 0) {
|
|
954
|
+
return {
|
|
955
|
+
ok: false,
|
|
956
|
+
state: "needs_attention",
|
|
957
|
+
koedHome: stop.koedHome,
|
|
958
|
+
message: "Timed out waiting for koed-server processes to stop.",
|
|
959
|
+
stoppedPids: stop.stoppedPids,
|
|
960
|
+
missingPids: [...stop.missingPids, ...runningAfterKill],
|
|
961
|
+
stoppedServices: stop.stoppedServices,
|
|
962
|
+
missingServices: stop.missingServices
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
let startedPid;
|
|
967
|
+
if (options.start) {
|
|
968
|
+
await options.start(options);
|
|
969
|
+
} else {
|
|
970
|
+
startedPid = startDetached(options);
|
|
971
|
+
}
|
|
972
|
+
return {
|
|
973
|
+
ok: true,
|
|
974
|
+
state: "starting",
|
|
975
|
+
koedHome: stop.koedHome,
|
|
976
|
+
message: "Koed server restarted.",
|
|
977
|
+
...startedPid ? { startedPid } : {},
|
|
978
|
+
stoppedPids: stop.stoppedPids,
|
|
979
|
+
missingPids: stop.missingPids,
|
|
980
|
+
stoppedServices: stop.stoppedServices,
|
|
981
|
+
missingServices: stop.missingServices
|
|
982
|
+
};
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// packages/koed-server/dist/upstream-capability-maintenance.js
|
|
986
|
+
var maintainUpstreamCapabilities = (paths, options = {}) => {
|
|
987
|
+
const list = options.list ?? listUpstreamBackends;
|
|
988
|
+
const refresh = options.refresh ?? refreshUpstreamBackendCapabilities;
|
|
989
|
+
const controller = new AbortController();
|
|
990
|
+
let running = false;
|
|
991
|
+
const tick = async () => {
|
|
992
|
+
if (running || controller.signal.aborted)
|
|
993
|
+
return;
|
|
994
|
+
running = true;
|
|
995
|
+
try {
|
|
996
|
+
const now = (options.now ?? Date.now)();
|
|
997
|
+
for (const backend of list(paths).backends ?? []) {
|
|
998
|
+
if (controller.signal.aborted)
|
|
999
|
+
break;
|
|
1000
|
+
if (backend.credential.status === "revoked")
|
|
1001
|
+
continue;
|
|
1002
|
+
if (!Object.values(backend.routePolicy).includes("enabled"))
|
|
1003
|
+
continue;
|
|
1004
|
+
const expiry = Date.parse(backend.capabilities.expiresAt ?? "");
|
|
1005
|
+
if (backend.capabilities.state === "validated" && Number.isFinite(expiry) && expiry > now + (options.leadMs ?? 6e4))
|
|
1006
|
+
continue;
|
|
1007
|
+
await refresh(paths, backend.id, { signal: controller.signal });
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
if (!controller.signal.aborted)
|
|
1011
|
+
options.onError?.();
|
|
1012
|
+
} finally {
|
|
1013
|
+
running = false;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
const timer = setInterval(() => void tick(), options.intervalMs ?? 3e4);
|
|
1017
|
+
timer.unref?.();
|
|
1018
|
+
void tick();
|
|
1019
|
+
return () => {
|
|
1020
|
+
clearInterval(timer);
|
|
1021
|
+
controller.abort();
|
|
1022
|
+
};
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// packages/koed-server/dist/start.js
|
|
1026
|
+
import { spawn as nodeSpawn2, spawnSync as nodeSpawnSync2 } from "node:child_process";
|
|
1027
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
1028
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1029
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1030
|
+
import { dirname, resolve as resolve4 } from "node:path";
|
|
1031
|
+
|
|
1032
|
+
// packages/koed-server/dist/runtime-lifecycle.js
|
|
1033
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
1034
|
+
import { resolve as resolve3 } from "node:path";
|
|
1035
|
+
var readJson = (path) => {
|
|
1036
|
+
try {
|
|
1037
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
1038
|
+
} catch {
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
function observeRuntimeLifecycle(paths, { resolveProcessIdentity: resolveProcessIdentity2 = resolveProcessIdentity, isProcessRunning: isProcessRunning2 = isProcessRunning } = {}) {
|
|
1043
|
+
const lockPath = resolve3(paths.runDir, "koed-server.lock");
|
|
1044
|
+
if (!existsSync2(lockPath))
|
|
1045
|
+
return {
|
|
1046
|
+
state: "stopped",
|
|
1047
|
+
epoch: "stopped",
|
|
1048
|
+
message: "Koed is not running."
|
|
1049
|
+
};
|
|
1050
|
+
const lock = readSupervisorLock(lockPath);
|
|
1051
|
+
if (!lock)
|
|
1052
|
+
return {
|
|
1053
|
+
state: "unavailable",
|
|
1054
|
+
epoch: "unavailable",
|
|
1055
|
+
message: "Koed supervisor lock is unreadable."
|
|
1056
|
+
};
|
|
1057
|
+
const identity = resolveProcessIdentity2(lock.pid);
|
|
1058
|
+
if (!identity) {
|
|
1059
|
+
return isProcessRunning2(lock.pid) ? {
|
|
1060
|
+
state: "unavailable",
|
|
1061
|
+
epoch: "unavailable",
|
|
1062
|
+
message: "Koed supervisor identity could not be verified.",
|
|
1063
|
+
pid: lock.pid
|
|
1064
|
+
} : { state: "stopped", epoch: "stopped", message: "Koed is not running." };
|
|
1065
|
+
}
|
|
1066
|
+
if (lock.processIdentity !== identity)
|
|
1067
|
+
return {
|
|
1068
|
+
state: "stopped",
|
|
1069
|
+
epoch: "stopped",
|
|
1070
|
+
message: "Koed is not running."
|
|
1071
|
+
};
|
|
1072
|
+
const epoch = `${lock.pid}:${identity}`;
|
|
1073
|
+
const pin = readJson(paths.generationStatePath ?? resolve3(paths.runDir, "runtime-generation.json"));
|
|
1074
|
+
const pinned = isRuntimeGenerationState(pin) && pin.pid === lock.pid && pin.processIdentity === identity ? pin : null;
|
|
1075
|
+
const runtime = readJson(paths.runtimeStatePath);
|
|
1076
|
+
const running = pinned !== null && runtime?.pid === lock.pid;
|
|
1077
|
+
return {
|
|
1078
|
+
state: running ? "running" : "starting",
|
|
1079
|
+
epoch,
|
|
1080
|
+
message: running ? "Koed is running." : "Koed is starting.",
|
|
1081
|
+
pid: lock.pid,
|
|
1082
|
+
processIdentity: identity,
|
|
1083
|
+
owner: pinned ? { ...pinned.owner } : null,
|
|
1084
|
+
...pinned ? { generationId: pinned.generationId } : {}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
var runtimeOwnedBy = (observation, owner) => Boolean(observation.owner && observation.owner.kind === owner.kind && observation.owner.installationId === owner.installationId);
|
|
1088
|
+
|
|
1089
|
+
// packages/koed-server/dist/start.js
|
|
1090
|
+
var localRuntimeFailureDetails = (details) => {
|
|
1091
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) {
|
|
1092
|
+
return "";
|
|
1093
|
+
}
|
|
1094
|
+
const result = details;
|
|
1095
|
+
const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
|
|
1096
|
+
const stderr = typeof result.stderr === "string" ? result.stderr.trim().slice(0, 2e3) : "";
|
|
1097
|
+
const parts = [
|
|
1098
|
+
exitCode === null ? null : `exit code ${exitCode}`,
|
|
1099
|
+
stderr || null
|
|
1100
|
+
].filter((part) => Boolean(part));
|
|
1101
|
+
return parts.length ? ` (${parts.join(": ")})` : "";
|
|
1102
|
+
};
|
|
1103
|
+
var startupComponentLabels = {
|
|
1104
|
+
api: "API",
|
|
1105
|
+
database: "database",
|
|
1106
|
+
redis: "Redis",
|
|
1107
|
+
workerQueues: "Worker/queue",
|
|
1108
|
+
embeddingService: "Embedding Service",
|
|
1109
|
+
privacyService: "Privacy Filter Service",
|
|
1110
|
+
localAiRuntime: "Local AI Runtime",
|
|
1111
|
+
apiToken: "Personal API Token"
|
|
1112
|
+
};
|
|
1113
|
+
var startupBlockingSummary = (status, componentIds = koedServerStartupBlockingComponentIds(status.runtimeMode)) => {
|
|
1114
|
+
const blockers = componentIds.flatMap((componentId) => {
|
|
1115
|
+
const component = status[componentId];
|
|
1116
|
+
return component.state === "healthy" ? [] : [`${startupComponentLabels[componentId]} (${component.state})`];
|
|
1117
|
+
});
|
|
1118
|
+
return blockers.length > 0 ? blockers.join(", ") : "unknown";
|
|
1119
|
+
};
|
|
1120
|
+
var runCommand = (paths, label, command, args, environment, spawnSync, cwd = paths.repoRoot) => {
|
|
1121
|
+
console.log(`> ${label}`);
|
|
1122
|
+
const result = spawnSync(command, args, {
|
|
1123
|
+
cwd,
|
|
1124
|
+
env: environment,
|
|
1125
|
+
stdio: "inherit"
|
|
1126
|
+
});
|
|
1127
|
+
if (result.error) {
|
|
1128
|
+
throw result.error;
|
|
1129
|
+
}
|
|
1130
|
+
if (result.status !== 0) {
|
|
1131
|
+
throw new Error(`${label} failed with exit code ${result.status ?? 1}.`);
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
var createOpaqueSecret = (prefix) => `${prefix}_${randomBytes3(32).toString("base64url")}`;
|
|
1135
|
+
var appProcessEnvironment = (environment) => {
|
|
1136
|
+
const output = { ...environment };
|
|
1137
|
+
delete output.PRIVACY_RUNTIME_CONTROL_TOKEN;
|
|
1138
|
+
return output;
|
|
1139
|
+
};
|
|
1140
|
+
var spawnManagedProcess = (paths, label, command, args, environment, spawn, cwd = paths.repoRoot) => {
|
|
1141
|
+
console.log(`> Start ${label}`);
|
|
1142
|
+
const child = spawn(command, args, {
|
|
1143
|
+
cwd,
|
|
1144
|
+
env: environment,
|
|
1145
|
+
stdio: "inherit"
|
|
1146
|
+
});
|
|
1147
|
+
child.on("exit", (code) => {
|
|
1148
|
+
console.log(`${label} exited with code ${code ?? "signal"}.`);
|
|
1149
|
+
});
|
|
1150
|
+
return child;
|
|
1151
|
+
};
|
|
1152
|
+
var waitForHealthyOrReady = async ({ environment, timeoutMs, pollIntervalMs, collectStatus, signal, isReady = (status) => status.ok }) => {
|
|
1153
|
+
const startedAt = Date.now();
|
|
1154
|
+
signal.throwIfAborted();
|
|
1155
|
+
let lastStatus = await collectStatus(environment);
|
|
1156
|
+
while (true) {
|
|
1157
|
+
signal.throwIfAborted();
|
|
1158
|
+
if (isReady(lastStatus)) {
|
|
1159
|
+
return lastStatus;
|
|
1160
|
+
}
|
|
1161
|
+
if (Date.now() - startedAt >= timeoutMs)
|
|
1162
|
+
return lastStatus;
|
|
1163
|
+
await delay(pollIntervalMs, void 0, { signal });
|
|
1164
|
+
lastStatus = await collectStatus(environment);
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
var resolveWorkQueueBackend = (value) => value?.trim() === "local" ? "local" : "bullmq";
|
|
1168
|
+
var prefixedApiEnv = (environment, repoEnv, name) => environment[`API_${name}`] ?? environment[name] ?? repoEnv[`API_${name}`] ?? repoEnv[name];
|
|
1169
|
+
var resolveEffectiveWorkQueueBackend = (config, environment, repoEnv) => {
|
|
1170
|
+
if (environment.WORK_QUEUE_BACKEND) {
|
|
1171
|
+
return resolveWorkQueueBackend(environment.WORK_QUEUE_BACKEND);
|
|
1172
|
+
}
|
|
1173
|
+
if (config.dependencyMode === "bundled-local") {
|
|
1174
|
+
return "local";
|
|
1175
|
+
}
|
|
1176
|
+
return resolveWorkQueueBackend(repoEnv.WORK_QUEUE_BACKEND);
|
|
1177
|
+
};
|
|
1178
|
+
var koedServerConfigEnvironment = (environment, repoEnv) => ({
|
|
1179
|
+
...environment,
|
|
1180
|
+
KOED_RUNTIME_MODE: environment.KOED_RUNTIME_MODE ?? repoEnv.KOED_RUNTIME_MODE,
|
|
1181
|
+
KOED_DEPENDENCY_MODE: environment.KOED_DEPENDENCY_MODE ?? repoEnv.KOED_DEPENDENCY_MODE,
|
|
1182
|
+
KOED_EXTERNAL_DATABASE_URL: environment.KOED_EXTERNAL_DATABASE_URL ?? repoEnv.KOED_EXTERNAL_DATABASE_URL,
|
|
1183
|
+
KOED_EXTERNAL_REDIS_URL: environment.KOED_EXTERNAL_REDIS_URL ?? repoEnv.KOED_EXTERNAL_REDIS_URL,
|
|
1184
|
+
KOED_EXTERNAL_EMBEDDING_SERVICE_URL: environment.KOED_EXTERNAL_EMBEDDING_SERVICE_URL ?? repoEnv.KOED_EXTERNAL_EMBEDDING_SERVICE_URL,
|
|
1185
|
+
MEMORY_CODEX_TRANSCRIPT_WATCHER_ENABLED: environment.MEMORY_CODEX_TRANSCRIPT_WATCHER_ENABLED ?? repoEnv.MEMORY_CODEX_TRANSCRIPT_WATCHER_ENABLED,
|
|
1186
|
+
MEMORY_CLAUDE_TRANSCRIPT_WATCHER_ENABLED: environment.MEMORY_CLAUDE_TRANSCRIPT_WATCHER_ENABLED ?? repoEnv.MEMORY_CLAUDE_TRANSCRIPT_WATCHER_ENABLED,
|
|
1187
|
+
KOED_HARDWARE_ACCELERATION: environment.KOED_HARDWARE_ACCELERATION ?? repoEnv.KOED_HARDWARE_ACCELERATION
|
|
1188
|
+
});
|
|
1189
|
+
var bundledLocalDatabaseUrl = (environment, repoEnv) => {
|
|
1190
|
+
const user = environment.POSTGRES_USER ?? repoEnv.POSTGRES_USER ?? "koed";
|
|
1191
|
+
const password = environment.POSTGRES_PASSWORD ?? repoEnv.POSTGRES_PASSWORD ?? environment.KOED_BUNDLED_POSTGRES_PASSWORD ?? "koed-local-postgres";
|
|
1192
|
+
const database = environment.POSTGRES_DB ?? repoEnv.POSTGRES_DB ?? "koed";
|
|
1193
|
+
const host = environment.KOED_POSTGRES_HOST ?? "127.0.0.1";
|
|
1194
|
+
const port = environment.KOED_POSTGRES_PORT ?? environment.POSTGRES_HOST_PORT ?? repoEnv.POSTGRES_HOST_PORT ?? "15432";
|
|
1195
|
+
return `postgres://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(database)}`;
|
|
1196
|
+
};
|
|
1197
|
+
var corsOrigins = (environment, repoEnv) => {
|
|
1198
|
+
const configured = [
|
|
1199
|
+
environment.API_CORS_ORIGINS,
|
|
1200
|
+
repoEnv.API_CORS_ORIGINS,
|
|
1201
|
+
environment.CORS_ORIGINS,
|
|
1202
|
+
repoEnv.CORS_ORIGINS
|
|
1203
|
+
].flatMap((value) => value?.split(",") ?? []).map((value) => value.trim()).filter(Boolean);
|
|
1204
|
+
configured.push("koed://app");
|
|
1205
|
+
return Array.from(new Set(configured)).join(",");
|
|
1206
|
+
};
|
|
1207
|
+
var localServiceEnv = (environment, repoEnv, apiToken, paths) => {
|
|
1208
|
+
const apiPort = environment.API_HOST_PORT ?? repoEnv.API_HOST_PORT ?? "3300";
|
|
1209
|
+
const embeddingPort = environment.EMBEDDING_SERVICE_HOST_PORT ?? repoEnv.EMBEDDING_SERVICE_HOST_PORT ?? "3800";
|
|
1210
|
+
const embeddingServiceUrl = `http://localhost:${embeddingPort}`;
|
|
1211
|
+
const privacyPort = environment.PRIVACY_SERVICE_PORT ?? repoEnv.PRIVACY_SERVICE_PORT ?? "8092";
|
|
1212
|
+
const privacyServiceUrl = `http://localhost:${privacyPort}`;
|
|
1213
|
+
const serverConfig = resolveKoedServerConfig(paths, koedServerConfigEnvironment(environment, repoEnv));
|
|
1214
|
+
const queueBackend = resolveEffectiveWorkQueueBackend(serverConfig, environment, repoEnv);
|
|
1215
|
+
const modelEnvironment = { ...repoEnv, ...environment };
|
|
1216
|
+
const embeddingModel = resolveLocalModelManifest(paths, "embedding", modelEnvironment);
|
|
1217
|
+
const installedModelPaths = [embeddingModel.modelPath].filter((modelPath) => existsSync3(modelPath));
|
|
1218
|
+
const localEmbeddingModelPath = existsSync3(embeddingModel.modelPath) ? embeddingModel.modelPath : void 0;
|
|
1219
|
+
const modelsDir = installedModelPaths[0] ? dirname(installedModelPaths[0]) : paths.modelsDir;
|
|
1220
|
+
return {
|
|
1221
|
+
...bundledPdsSecretProviderEnvironment({
|
|
1222
|
+
...process.env,
|
|
1223
|
+
...repoEnv,
|
|
1224
|
+
...environment
|
|
1225
|
+
}),
|
|
1226
|
+
KOED_HOME: paths.koedHome,
|
|
1227
|
+
KOED_RUNTIME_MODE: serverConfig.runtimeMode,
|
|
1228
|
+
KOED_DEPENDENCY_MODE: serverConfig.dependencyMode,
|
|
1229
|
+
PDS_RUNTIME_SECRET_REF: environment.PDS_RUNTIME_SECRET_REF?.trim() || "pds-runtime",
|
|
1230
|
+
...apiToken ? { MEMORY_API_TOKEN: apiToken.token } : {},
|
|
1231
|
+
NODE_ENV: environment.API_NODE_ENV ?? repoEnv.API_NODE_ENV ?? environment.NODE_ENV ?? "production",
|
|
1232
|
+
LOG_LEVEL: environment.API_LOG_LEVEL ?? repoEnv.API_LOG_LEVEL ?? environment.LOG_LEVEL,
|
|
1233
|
+
API_HOST: environment.API_HOST ?? repoEnv.API_HOST ?? "127.0.0.1",
|
|
1234
|
+
WORK_QUEUE_BACKEND: queueBackend,
|
|
1235
|
+
KOED_MODELS_DIR: modelsDir,
|
|
1236
|
+
KOED_EMBEDDING_ACCELERATION: environment.KOED_EMBEDDING_ACCELERATION ?? repoEnv.KOED_EMBEDDING_ACCELERATION ?? serverConfig.hardwareAcceleration,
|
|
1237
|
+
KOED_HARDWARE_ACCELERATION: environment.KOED_HARDWARE_ACCELERATION ?? repoEnv.KOED_HARDWARE_ACCELERATION ?? serverConfig.hardwareAcceleration,
|
|
1238
|
+
PRIVACY_RUNTIME_PROVIDER: environment.PRIVACY_RUNTIME_PROVIDER ?? repoEnv.PRIVACY_RUNTIME_PROVIDER ?? environment.KOED_HARDWARE_ACCELERATION ?? repoEnv.KOED_HARDWARE_ACCELERATION ?? serverConfig.hardwareAcceleration,
|
|
1239
|
+
WORKER_LOG_LEVEL: repoEnv.WORKER_LOG_LEVEL ?? environment.WORKER_LOG_LEVEL,
|
|
1240
|
+
KOED_EMBEDDING_POOL_KEY: environment.WORKER_KOED_EMBEDDING_POOL_KEY ?? repoEnv.WORKER_KOED_EMBEDDING_POOL_KEY ?? environment.KOED_EMBEDDING_POOL_KEY ?? repoEnv.KOED_EMBEDDING_POOL_KEY,
|
|
1241
|
+
API_PORT: apiPort,
|
|
1242
|
+
DATABASE_URL: serverConfig.dependencyMode === "external" ? serverConfig.external?.databaseUrl ?? environment.DATABASE_URL ?? repoEnv.DATABASE_URL : bundledLocalDatabaseUrl(environment, repoEnv),
|
|
1243
|
+
REDIS_URL: serverConfig.external?.redisUrl ?? environment.REDIS_URL ?? repoEnv.REDIS_URL,
|
|
1244
|
+
RATE_LIMIT_STORE: prefixedApiEnv(environment, repoEnv, "RATE_LIMIT_STORE"),
|
|
1245
|
+
RATE_LIMIT_REDIS_URL: prefixedApiEnv(environment, repoEnv, "RATE_LIMIT_REDIS_URL") ?? "",
|
|
1246
|
+
AUTH_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "AUTH_RATE_LIMIT_WINDOW_MS"),
|
|
1247
|
+
AUTH_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "AUTH_RATE_LIMIT_MAX"),
|
|
1248
|
+
MEMORY_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "MEMORY_RATE_LIMIT_WINDOW_MS"),
|
|
1249
|
+
MEMORY_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "MEMORY_RATE_LIMIT_MAX"),
|
|
1250
|
+
MEMORY_READ_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "MEMORY_READ_RATE_LIMIT_WINDOW_MS"),
|
|
1251
|
+
MEMORY_READ_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "MEMORY_READ_RATE_LIMIT_MAX"),
|
|
1252
|
+
MEMORY_WRITE_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "MEMORY_WRITE_RATE_LIMIT_WINDOW_MS"),
|
|
1253
|
+
MEMORY_WRITE_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "MEMORY_WRITE_RATE_LIMIT_MAX"),
|
|
1254
|
+
MEMORY_RECALL_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "MEMORY_RECALL_RATE_LIMIT_WINDOW_MS"),
|
|
1255
|
+
MEMORY_RECALL_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "MEMORY_RECALL_RATE_LIMIT_MAX"),
|
|
1256
|
+
SOURCE_JOURNAL_RATE_LIMIT_WINDOW_MS: prefixedApiEnv(environment, repoEnv, "SOURCE_JOURNAL_RATE_LIMIT_WINDOW_MS"),
|
|
1257
|
+
SOURCE_JOURNAL_RATE_LIMIT_MAX: prefixedApiEnv(environment, repoEnv, "SOURCE_JOURNAL_RATE_LIMIT_MAX"),
|
|
1258
|
+
CACHE_STORE: prefixedApiEnv(environment, repoEnv, "CACHE_STORE"),
|
|
1259
|
+
CACHE_REDIS_URL: prefixedApiEnv(environment, repoEnv, "CACHE_REDIS_URL") ?? "",
|
|
1260
|
+
GRAPH_CACHE_TTL_SECONDS: prefixedApiEnv(environment, repoEnv, "GRAPH_CACHE_TTL_SECONDS"),
|
|
1261
|
+
GRAPH_UPDATE_DEBOUNCE_MS: prefixedApiEnv(environment, repoEnv, "GRAPH_UPDATE_DEBOUNCE_MS"),
|
|
1262
|
+
MEMORY_EVENT_GRAPH_UPDATE_DEBOUNCE_MS: prefixedApiEnv(environment, repoEnv, "MEMORY_EVENT_GRAPH_UPDATE_DEBOUNCE_MS"),
|
|
1263
|
+
DATA_ENCRYPTION_KEY: environment.API_DATA_ENCRYPTION_KEY ?? repoEnv.API_DATA_ENCRYPTION_KEY ?? environment.DATA_ENCRYPTION_KEY ?? repoEnv.DATA_ENCRYPTION_KEY,
|
|
1264
|
+
OWNER_PRIVATE_REPLICA_DATA_ENCRYPTION_KEY: environment.OWNER_PRIVATE_REPLICA_DATA_ENCRYPTION_KEY ?? repoEnv.OWNER_PRIVATE_REPLICA_DATA_ENCRYPTION_KEY,
|
|
1265
|
+
TEAM_MEMORY_DATA_ENCRYPTION_KEY: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_DATA_ENCRYPTION_KEY"),
|
|
1266
|
+
TEAM_MEMORY_ENVELOPE_ENCRYPTION_PROVIDER: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_ENVELOPE_ENCRYPTION_PROVIDER"),
|
|
1267
|
+
TEAM_MEMORY_MANAGED_KMS_KEY_ID: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_MANAGED_KMS_KEY_ID"),
|
|
1268
|
+
TEAM_MEMORY_MANAGED_KMS_KEY_VERSION: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_MANAGED_KMS_KEY_VERSION"),
|
|
1269
|
+
TEAM_MEMORY_MANAGED_KMS_ENDPOINT_URL: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_MANAGED_KMS_ENDPOINT_URL"),
|
|
1270
|
+
TEAM_MEMORY_MANAGED_KMS_AUTH_TOKEN: prefixedApiEnv(environment, repoEnv, "TEAM_MEMORY_MANAGED_KMS_AUTH_TOKEN"),
|
|
1271
|
+
OWNER_PRIVATE_REPLICA_ENVELOPE_ENCRYPTION_PROVIDER: environment.OWNER_PRIVATE_REPLICA_ENVELOPE_ENCRYPTION_PROVIDER ?? repoEnv.OWNER_PRIVATE_REPLICA_ENVELOPE_ENCRYPTION_PROVIDER,
|
|
1272
|
+
OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_ID: environment.OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_ID ?? repoEnv.OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_ID,
|
|
1273
|
+
OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_VERSION: environment.OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_VERSION ?? repoEnv.OWNER_PRIVATE_REPLICA_MANAGED_KMS_KEY_VERSION,
|
|
1274
|
+
OWNER_PRIVATE_REPLICA_MANAGED_KMS_ENDPOINT_URL: environment.OWNER_PRIVATE_REPLICA_MANAGED_KMS_ENDPOINT_URL ?? repoEnv.OWNER_PRIVATE_REPLICA_MANAGED_KMS_ENDPOINT_URL,
|
|
1275
|
+
OWNER_PRIVATE_REPLICA_MANAGED_KMS_AUTH_TOKEN: environment.OWNER_PRIVATE_REPLICA_MANAGED_KMS_AUTH_TOKEN ?? repoEnv.OWNER_PRIVATE_REPLICA_MANAGED_KMS_AUTH_TOKEN,
|
|
1276
|
+
API_TOKEN_PEPPER: environment.API_TOKEN_PEPPER ?? repoEnv.API_TOKEN_PEPPER,
|
|
1277
|
+
COLLABORATION_LOCAL_BROKER_SECRET: prefixedApiEnv(environment, repoEnv, "COLLABORATION_LOCAL_BROKER_SECRET"),
|
|
1278
|
+
COLLABORATION_REALTIME_CURSOR_SECRET: prefixedApiEnv(environment, repoEnv, "COLLABORATION_REALTIME_CURSOR_SECRET"),
|
|
1279
|
+
COLLABORATION_REALTIME_STREAM_MAX_CLIENTS: prefixedApiEnv(environment, repoEnv, "COLLABORATION_REALTIME_STREAM_MAX_CLIENTS"),
|
|
1280
|
+
COLLABORATION_REALTIME_STREAM_MAX_CLIENTS_PER_PRINCIPAL: prefixedApiEnv(environment, repoEnv, "COLLABORATION_REALTIME_STREAM_MAX_CLIENTS_PER_PRINCIPAL"),
|
|
1281
|
+
EMBEDDING_SERVICE_URL: serverConfig.dependencyMode === "external" ? serverConfig.external?.embeddingServiceUrl ?? environment.EMBEDDING_SERVICE_URL ?? repoEnv.EMBEDDING_SERVICE_URL : environment.EMBEDDING_SERVICE_URL ?? repoEnv.EMBEDDING_SERVICE_URL ?? embeddingServiceUrl,
|
|
1282
|
+
EMBEDDING_SERVICE_TOKEN: environment.EMBEDDING_SERVICE_TOKEN ?? repoEnv.EMBEDDING_SERVICE_TOKEN,
|
|
1283
|
+
PRIVACY_SERVICE_URL: serverConfig.dependencyMode === "external" ? serverConfig.external?.privacyServiceUrl ?? environment.PRIVACY_SERVICE_URL ?? repoEnv.PRIVACY_SERVICE_URL ?? process.env.PRIVACY_SERVICE_URL : environment.PRIVACY_SERVICE_URL ?? repoEnv.PRIVACY_SERVICE_URL ?? privacyServiceUrl,
|
|
1284
|
+
PRIVACY_SERVICE_TOKEN: serverConfig.dependencyMode === "external" ? environment.PRIVACY_SERVICE_TOKEN ?? repoEnv.PRIVACY_SERVICE_TOKEN ?? process.env.PRIVACY_SERVICE_TOKEN : environment.PRIVACY_SERVICE_TOKEN ?? repoEnv.PRIVACY_SERVICE_TOKEN ?? process.env.PRIVACY_SERVICE_TOKEN ?? createOpaqueSecret("privacy"),
|
|
1285
|
+
PRIVACY_RUNTIME_CONTROL_TOKEN: serverConfig.dependencyMode === "external" ? environment.PRIVACY_RUNTIME_CONTROL_TOKEN ?? repoEnv.PRIVACY_RUNTIME_CONTROL_TOKEN ?? process.env.PRIVACY_RUNTIME_CONTROL_TOKEN : environment.PRIVACY_RUNTIME_CONTROL_TOKEN ?? repoEnv.PRIVACY_RUNTIME_CONTROL_TOKEN ?? process.env.PRIVACY_RUNTIME_CONTROL_TOKEN ?? createOpaqueSecret("privacy-control"),
|
|
1286
|
+
EMBEDDING_MODEL: environment.EMBEDDING_MODEL_KEY ?? environment.EMBEDDING_MODEL ?? repoEnv.EMBEDDING_MODEL_KEY ?? embeddingModel.key,
|
|
1287
|
+
MODEL_KEY: environment.EMBEDDING_MODEL_KEY ?? environment.MODEL_KEY ?? environment.EMBEDDING_MODEL ?? repoEnv.EMBEDDING_MODEL_KEY ?? embeddingModel.key,
|
|
1288
|
+
EMBEDDING_MODEL_PATH: serverConfig.dependencyMode === "bundled-local" ? localEmbeddingModelPath : environment.EMBEDDING_MODEL_PATH,
|
|
1289
|
+
MODEL_PATH: serverConfig.dependencyMode === "bundled-local" ? localEmbeddingModelPath : environment.EMBEDDING_MODEL_PATH ?? environment.MODEL_PATH,
|
|
1290
|
+
LLAMA_SERVER_BINARY: environment.LLAMA_SERVER_BINARY ?? repoEnv.EMBEDDING_LLAMA_SERVER_BINARY ?? environment.EMBEDDING_LLAMA_SERVER_BINARY,
|
|
1291
|
+
LLAMA_N_CTX: environment.EMBEDDING_LLAMA_N_CTX ?? environment.LLAMA_N_CTX ?? repoEnv.EMBEDDING_LLAMA_N_CTX,
|
|
1292
|
+
LLAMA_N_THREADS: environment.EMBEDDING_LLAMA_N_THREADS ?? environment.LLAMA_N_THREADS ?? repoEnv.EMBEDDING_LLAMA_N_THREADS,
|
|
1293
|
+
LLAMA_N_BATCH: environment.EMBEDDING_LLAMA_N_BATCH ?? environment.LLAMA_N_BATCH ?? repoEnv.EMBEDDING_LLAMA_N_BATCH,
|
|
1294
|
+
LLAMA_BATCH_TOKEN_HEADROOM: environment.EMBEDDING_LLAMA_BATCH_TOKEN_HEADROOM ?? environment.LLAMA_BATCH_TOKEN_HEADROOM ?? repoEnv.EMBEDDING_LLAMA_BATCH_TOKEN_HEADROOM,
|
|
1295
|
+
LLAMA_N_UBATCH: environment.EMBEDDING_LLAMA_N_UBATCH ?? environment.LLAMA_N_UBATCH ?? repoEnv.EMBEDDING_LLAMA_N_UBATCH,
|
|
1296
|
+
LLAMA_PARALLEL: environment.EMBEDDING_LLAMA_PARALLEL ?? environment.LLAMA_PARALLEL ?? repoEnv.EMBEDDING_LLAMA_PARALLEL,
|
|
1297
|
+
LLAMA_SERVER_STARTUP_TIMEOUT_SECONDS: environment.EMBEDDING_LLAMA_SERVER_STARTUP_TIMEOUT_SECONDS ?? environment.LLAMA_SERVER_STARTUP_TIMEOUT_SECONDS ?? repoEnv.EMBEDDING_LLAMA_SERVER_STARTUP_TIMEOUT_SECONDS,
|
|
1298
|
+
LLAMA_EMBEDDING_SERVER_PORT: environment.EMBEDDING_LLAMA_EMBEDDING_SERVER_PORT ?? environment.LLAMA_EMBEDDING_SERVER_PORT ?? repoEnv.EMBEDDING_LLAMA_EMBEDDING_SERVER_PORT,
|
|
1299
|
+
CORS_ORIGINS: corsOrigins(environment, repoEnv),
|
|
1300
|
+
COOKIE_SECURE: prefixedApiEnv(environment, repoEnv, "COOKIE_SECURE"),
|
|
1301
|
+
BROWSER_PUBLIC_URL: prefixedApiEnv(environment, repoEnv, "BROWSER_PUBLIC_URL"),
|
|
1302
|
+
MEMORY_API_URL: resolveApiUrl(environment, repoEnv)
|
|
1303
|
+
};
|
|
1304
|
+
};
|
|
1305
|
+
var waitForChildExit = (child, timeoutMs) => new Promise((resolveExit) => {
|
|
1306
|
+
if (child.exitCode != null || child.signalCode != null) {
|
|
1307
|
+
resolveExit(true);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
let settled = false;
|
|
1311
|
+
const finish = (exited) => {
|
|
1312
|
+
if (settled)
|
|
1313
|
+
return;
|
|
1314
|
+
settled = true;
|
|
1315
|
+
clearTimeout(timeout);
|
|
1316
|
+
child.off("exit", onExit);
|
|
1317
|
+
resolveExit(exited);
|
|
1318
|
+
};
|
|
1319
|
+
const onExit = () => finish(true);
|
|
1320
|
+
const timeout = setTimeout(() => finish(false), timeoutMs);
|
|
1321
|
+
child.once("exit", onExit);
|
|
1322
|
+
});
|
|
1323
|
+
var stopChildProcess = async (child, timeoutMs = 5e3, label = "managed child") => {
|
|
1324
|
+
if (!child?.pid || child.exitCode != null || child.signalCode != null)
|
|
1325
|
+
return;
|
|
1326
|
+
child.kill("SIGTERM");
|
|
1327
|
+
if (await waitForChildExit(child, timeoutMs))
|
|
1328
|
+
return;
|
|
1329
|
+
child.kill("SIGKILL");
|
|
1330
|
+
if (!await waitForChildExit(child, timeoutMs)) {
|
|
1331
|
+
throw new Error(`Timed out stopping ${label} process ${child.pid}.`);
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
var createManagedProcessMonitor = (options = {}) => {
|
|
1335
|
+
const expectedSignals = new Set(options.expectedSignals ?? ["SIGINT", "SIGTERM"]);
|
|
1336
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1337
|
+
let resolveResult;
|
|
1338
|
+
let rejectResult;
|
|
1339
|
+
const result = new Promise((resolvePromise, rejectPromise) => {
|
|
1340
|
+
resolveResult = resolvePromise;
|
|
1341
|
+
rejectResult = rejectPromise;
|
|
1342
|
+
});
|
|
1343
|
+
let settled = false;
|
|
1344
|
+
const dispose = () => {
|
|
1345
|
+
for (const [child, listener] of listeners) {
|
|
1346
|
+
child.off("exit", listener.exit);
|
|
1347
|
+
child.off("error", listener.error);
|
|
1348
|
+
}
|
|
1349
|
+
listeners.clear();
|
|
1350
|
+
};
|
|
1351
|
+
const settle = (error) => {
|
|
1352
|
+
if (settled)
|
|
1353
|
+
return;
|
|
1354
|
+
settled = true;
|
|
1355
|
+
dispose();
|
|
1356
|
+
if (error)
|
|
1357
|
+
rejectResult(error);
|
|
1358
|
+
else
|
|
1359
|
+
resolveResult();
|
|
1360
|
+
};
|
|
1361
|
+
const recordExit = (name, code, signal) => {
|
|
1362
|
+
if (signal && expectedSignals.has(signal)) {
|
|
1363
|
+
settle();
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
settle(new Error(`Essential managed child ${name} exited unexpectedly with ${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`}.`));
|
|
1367
|
+
};
|
|
1368
|
+
return {
|
|
1369
|
+
watch: (name, child) => {
|
|
1370
|
+
if (settled)
|
|
1371
|
+
return;
|
|
1372
|
+
if (child.exitCode != null || child.signalCode != null) {
|
|
1373
|
+
recordExit(name, child.exitCode, child.signalCode);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
const listener = {
|
|
1377
|
+
exit: (code, signal) => recordExit(name, code, signal),
|
|
1378
|
+
error: (error) => settle(new Error(`Essential managed child ${name} failed: ${error.message}`, {
|
|
1379
|
+
cause: error
|
|
1380
|
+
}))
|
|
1381
|
+
};
|
|
1382
|
+
listeners.set(child, listener);
|
|
1383
|
+
child.once("exit", listener.exit);
|
|
1384
|
+
child.once("error", listener.error);
|
|
1385
|
+
},
|
|
1386
|
+
result,
|
|
1387
|
+
dispose
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
var startKoedServer = async ({ environment = process.env, pollIntervalMs = 500, timeoutMs = 18e4, spawnSync = nodeSpawnSync2, spawn = nodeSpawn2, collectStatus = collectKoedServerStartupStatus, provisionLocalApiToken: provisionLocalApiTokenDependency = provisionLocalApiToken, signal, desktopRuntimeCapability, onReady } = {}) => {
|
|
1391
|
+
if (desktopRuntimeCapability !== void 0 && !validateDesktopRuntimeCapability(desktopRuntimeCapability)) {
|
|
1392
|
+
throw new Error("validated private Desktop runtime capability is required");
|
|
1393
|
+
}
|
|
1394
|
+
const requestedEnvironment = environment;
|
|
1395
|
+
const startupId = randomBytes3(12).toString("hex");
|
|
1396
|
+
const startupStartedAt = process.hrtime.bigint();
|
|
1397
|
+
const emitStartupMilestone = (milestone) => {
|
|
1398
|
+
console.log(JSON.stringify({
|
|
1399
|
+
event: "koed.supervisor.startup",
|
|
1400
|
+
startupId,
|
|
1401
|
+
milestone,
|
|
1402
|
+
elapsedMs: Number(process.hrtime.bigint() - startupStartedAt) / 1e6
|
|
1403
|
+
}));
|
|
1404
|
+
};
|
|
1405
|
+
emitStartupMilestone("supervisor_entry");
|
|
1406
|
+
const execution = resolveKoedAppRuntimeExecution();
|
|
1407
|
+
const bootstrapPaths = resolveKoedServerPaths(environment);
|
|
1408
|
+
const bootstrapEnvironment = execution === "source" ? environmentWithRepoEnv(bootstrapPaths.repoRoot, environment) : environment;
|
|
1409
|
+
const paths = resolveKoedServerPaths(bootstrapEnvironment);
|
|
1410
|
+
const owner = desktopRuntimeCapability === void 0 ? resolveKoedRuntimeOwner() : resolveDesktopRuntimeOwner(desktopRuntimeCapability, paths.koedHome);
|
|
1411
|
+
const ownerMismatchMessage = desktopRuntimeCapability === void 0 ? "Packaged runtime owner does not match this standalone control plane." : "Packaged runtime owner does not match this Desktop installation.";
|
|
1412
|
+
const persistedOwner = await readCurrentRuntimeOwner(paths);
|
|
1413
|
+
if (persistedOwner && (persistedOwner.kind !== owner.kind || persistedOwner.installationId !== owner.installationId))
|
|
1414
|
+
throw new Error(ownerMismatchMessage);
|
|
1415
|
+
const assertObservedOwner = (existing) => {
|
|
1416
|
+
if (existing.state === "stopped")
|
|
1417
|
+
return;
|
|
1418
|
+
if (!existing.owner)
|
|
1419
|
+
throw new Error("Runtime owner could not be verified; refusing to share this KOED_HOME.");
|
|
1420
|
+
if (!runtimeOwnedBy(existing, owner))
|
|
1421
|
+
throw new Error(ownerMismatchMessage);
|
|
1422
|
+
};
|
|
1423
|
+
assertObservedOwner(observeRuntimeLifecycle(paths));
|
|
1424
|
+
ensureKoedHome(paths);
|
|
1425
|
+
const supervisorLock = acquireKoedServerSupervisorLock(paths);
|
|
1426
|
+
if (!supervisorLock.acquired) {
|
|
1427
|
+
assertObservedOwner(observeRuntimeLifecycle(paths));
|
|
1428
|
+
console.log(JSON.stringify({
|
|
1429
|
+
ok: true,
|
|
1430
|
+
state: "already_running",
|
|
1431
|
+
koedHome: paths.koedHome,
|
|
1432
|
+
message: "A koed-server supervisor already owns this KOED_HOME.",
|
|
1433
|
+
...supervisorLock.ownerPid ? { supervisorPid: supervisorLock.ownerPid } : {}
|
|
1434
|
+
}, null, 2));
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
let supervisorLockReleased = false;
|
|
1438
|
+
let cleanupStarted = false;
|
|
1439
|
+
let startupPhase = "post-lock initialization";
|
|
1440
|
+
const releaseSupervisorLock = () => {
|
|
1441
|
+
if (supervisorLockReleased)
|
|
1442
|
+
return;
|
|
1443
|
+
supervisorLockReleased = true;
|
|
1444
|
+
releaseKoedServerSupervisorLock(supervisorLock);
|
|
1445
|
+
};
|
|
1446
|
+
try {
|
|
1447
|
+
const initializeUnderSupervisorLock = async (initialize) => {
|
|
1448
|
+
try {
|
|
1449
|
+
return await initialize();
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
releaseSupervisorLock();
|
|
1452
|
+
throw error;
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
startupPhase = "device identity initialization";
|
|
1456
|
+
await initializeUnderSupervisorLock(() => ensureDeviceIdentity(paths, { environment }));
|
|
1457
|
+
const supervisorStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1458
|
+
startupPhase = "effective runtime configuration";
|
|
1459
|
+
const effectiveRuntimeConfig = await initializeUnderSupervisorLock(() => resolveEffectiveRuntimeConfig(paths, requestedEnvironment, execution));
|
|
1460
|
+
environment = effectiveRuntimeConfig.environment;
|
|
1461
|
+
startupPhase = "runtime requirements";
|
|
1462
|
+
const requiredRuntime = await initializeUnderSupervisorLock(() => calculateRuntimeRequirements(effectiveRuntimeConfig));
|
|
1463
|
+
environment = {
|
|
1464
|
+
...environment,
|
|
1465
|
+
WORK_QUEUE_BACKEND: requiredRuntime.queue
|
|
1466
|
+
};
|
|
1467
|
+
const desktopManagedLocal = environment.KOED_AUTO_PORTS === "1";
|
|
1468
|
+
const startupConfig = effectiveRuntimeConfig.config;
|
|
1469
|
+
startupPhase = "packaged local service secrets";
|
|
1470
|
+
environment = await initializeUnderSupervisorLock(() => ensurePackagedLocalServiceSecrets(paths, execution === "packaged" || startupConfig.runtimeMode === "local-personal" && startupConfig.dependencyMode === "bundled-local", environment));
|
|
1471
|
+
mkdirSync3(paths.logsDir, { recursive: true, mode: 448 });
|
|
1472
|
+
if (startupConfig.runtimeMode === "local-personal" && startupConfig.dependencyMode === "bundled-local" && environment.KOED_AUTO_PORTS === void 0) {
|
|
1473
|
+
environment = { ...environment, KOED_AUTO_PORTS: "1" };
|
|
1474
|
+
}
|
|
1475
|
+
const portAllocationEnvironment = { ...environment };
|
|
1476
|
+
if (environment.KOED_AUTO_PORTS === "1") {
|
|
1477
|
+
for (const key of [
|
|
1478
|
+
"API_HOST_PORT",
|
|
1479
|
+
"POSTGRES_HOST_PORT",
|
|
1480
|
+
"EMBEDDING_SERVICE_HOST_PORT",
|
|
1481
|
+
"PRIVACY_SERVICE_PORT",
|
|
1482
|
+
"EMBEDDING_LLAMA_EMBEDDING_SERVER_PORT"
|
|
1483
|
+
]) {
|
|
1484
|
+
if (!requestedEnvironment[key]?.trim())
|
|
1485
|
+
delete portAllocationEnvironment[key];
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const allocatedPortEnvironmentForStart = environment.KOED_ENV_PATH?.trim() ? {
|
|
1489
|
+
...environmentWithRepoEnv(paths.repoRoot, environment),
|
|
1490
|
+
...portAllocationEnvironment
|
|
1491
|
+
} : portAllocationEnvironment;
|
|
1492
|
+
startupPhase = "local port allocation";
|
|
1493
|
+
const allocatedPortEnvironment = await initializeUnderSupervisorLock(() => allocateAndPersistLocalPorts(paths, allocatedPortEnvironmentForStart));
|
|
1494
|
+
environment = {
|
|
1495
|
+
...environment,
|
|
1496
|
+
API_HOST_PORT: allocatedPortEnvironment.API_HOST_PORT,
|
|
1497
|
+
POSTGRES_HOST_PORT: allocatedPortEnvironment.POSTGRES_HOST_PORT,
|
|
1498
|
+
EMBEDDING_SERVICE_HOST_PORT: allocatedPortEnvironment.EMBEDDING_SERVICE_HOST_PORT,
|
|
1499
|
+
PRIVACY_SERVICE_PORT: allocatedPortEnvironment.PRIVACY_SERVICE_PORT,
|
|
1500
|
+
EMBEDDING_LLAMA_EMBEDDING_SERVER_PORT: allocatedPortEnvironment.EMBEDDING_LLAMA_EMBEDDING_SERVER_PORT
|
|
1501
|
+
};
|
|
1502
|
+
startupPhase = "repository environment and credentials";
|
|
1503
|
+
const repoEnv = execution === "source" || environment.KOED_ENV_PATH?.trim() ? loadRepoEnv(paths.repoRoot, environment) : {};
|
|
1504
|
+
const migration = migrateKoedOwnedCodexRegistrationBestEffort({
|
|
1505
|
+
environment: { ...repoEnv, ...environment, KOED_HOME: paths.koedHome }
|
|
1506
|
+
});
|
|
1507
|
+
if (migration.diagnostic) {
|
|
1508
|
+
console.warn(migration.diagnostic);
|
|
1509
|
+
}
|
|
1510
|
+
const apiToken = desktopManagedLocal ? null : resolveLocalApiToken(environment, repoEnv);
|
|
1511
|
+
if (apiToken) {
|
|
1512
|
+
writeLocalAppCredential(paths, {
|
|
1513
|
+
apiToken: apiToken.token,
|
|
1514
|
+
provisionedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1515
|
+
source: apiToken.source
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
const config = startupConfig;
|
|
1519
|
+
const initialServiceEnv = localServiceEnv(environment, repoEnv, apiToken, paths);
|
|
1520
|
+
const useBundledLocalDependencies = config.dependencyMode === "bundled-local";
|
|
1521
|
+
const teamCollaborationEnabled = effectiveRuntimeConfig.teamEnabled;
|
|
1522
|
+
const localAiRuntimeEnabled = config.runtimeMode !== "external";
|
|
1523
|
+
const runtimeServices = useBundledLocalDependencies ? [
|
|
1524
|
+
"postgres-native",
|
|
1525
|
+
"embedding-service-native",
|
|
1526
|
+
...teamCollaborationEnabled ? ["privacy-service-native"] : []
|
|
1527
|
+
] : [];
|
|
1528
|
+
const appServices = [
|
|
1529
|
+
"api",
|
|
1530
|
+
"worker",
|
|
1531
|
+
...localAiRuntimeEnabled ? ["local-ai-runtime"] : []
|
|
1532
|
+
];
|
|
1533
|
+
const childEnv = initialServiceEnv;
|
|
1534
|
+
if (execution === "source") {
|
|
1535
|
+
startupPhase = "source environment preparation";
|
|
1536
|
+
runCommand(paths, "Prepare Koed environment", process.execPath, [resolve4(paths.repoRoot, "scripts/setup-env.mjs")], childEnv, spawnSync);
|
|
1537
|
+
}
|
|
1538
|
+
startupPhase = "refreshed repository environment and credentials";
|
|
1539
|
+
const refreshedRepoEnv = await initializeUnderSupervisorLock(() => execution === "source" || environment.KOED_ENV_PATH?.trim() ? loadRepoEnv(paths.repoRoot, environment) : {});
|
|
1540
|
+
const refreshedApiToken = desktopManagedLocal ? null : resolveLocalApiToken(environment, refreshedRepoEnv);
|
|
1541
|
+
if (refreshedApiToken) {
|
|
1542
|
+
writeLocalAppCredential(paths, {
|
|
1543
|
+
apiToken: refreshedApiToken.token,
|
|
1544
|
+
provisionedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1545
|
+
source: refreshedApiToken.source
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
const refreshedEnv = localServiceEnv(environment, refreshedRepoEnv, refreshedApiToken, paths);
|
|
1549
|
+
refreshedEnv.WORK_QUEUE_BACKEND = requiredRuntime.queue;
|
|
1550
|
+
const apiUrlEnvironment = { ...environment };
|
|
1551
|
+
if (requestedEnvironment.API_HOST_PORT?.trim() && !requestedEnvironment.MEMORY_API_URL?.trim()) {
|
|
1552
|
+
delete apiUrlEnvironment.MEMORY_API_URL;
|
|
1553
|
+
}
|
|
1554
|
+
const apiUrl = resolveApiUrl(apiUrlEnvironment, refreshedRepoEnv);
|
|
1555
|
+
let appRuntime;
|
|
1556
|
+
let generationPin;
|
|
1557
|
+
let startedNativePostgres = false;
|
|
1558
|
+
let nativeEmbeddingProcess;
|
|
1559
|
+
let nativePrivacyProcess;
|
|
1560
|
+
const managedChildren = {};
|
|
1561
|
+
const managedProcessMonitor = createManagedProcessMonitor({
|
|
1562
|
+
expectedSignals: []
|
|
1563
|
+
});
|
|
1564
|
+
const managedProcessOutcome = managedProcessMonitor.result.then(() => ({ error: null }), (error) => ({ error }));
|
|
1565
|
+
const manageChild = (name, child) => {
|
|
1566
|
+
managedChildren[name] = child;
|
|
1567
|
+
managedProcessMonitor.watch(name, child);
|
|
1568
|
+
return child;
|
|
1569
|
+
};
|
|
1570
|
+
let runtimeStateWritten = false;
|
|
1571
|
+
let stopSupervisorExitMonitor = () => void 0;
|
|
1572
|
+
let stopCapabilityMaintenance = () => void 0;
|
|
1573
|
+
const runtimeStateOwnedByCurrentProcess = () => {
|
|
1574
|
+
try {
|
|
1575
|
+
const runtime = JSON.parse(readFileSync4(paths.runtimeStatePath, "utf8"));
|
|
1576
|
+
return runtime.pid === process.pid && runtime.startedAt === supervisorStartedAt;
|
|
1577
|
+
} catch {
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
let deviceRequestService;
|
|
1582
|
+
let cleanupPromise;
|
|
1583
|
+
let cleanupCompleted = false;
|
|
1584
|
+
const cleanupStartedResources = () => {
|
|
1585
|
+
cleanupStarted = true;
|
|
1586
|
+
if (cleanupPromise)
|
|
1587
|
+
return cleanupPromise;
|
|
1588
|
+
cleanupPromise = (async () => {
|
|
1589
|
+
const cleanupErrors = [];
|
|
1590
|
+
try {
|
|
1591
|
+
await deviceRequestService?.close();
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
cleanupErrors.push(error instanceof Error ? error.message : String(error));
|
|
1594
|
+
}
|
|
1595
|
+
const shutdownOrder = [
|
|
1596
|
+
"localAiRuntime",
|
|
1597
|
+
"worker",
|
|
1598
|
+
"api",
|
|
1599
|
+
"privacyService",
|
|
1600
|
+
"embeddingService"
|
|
1601
|
+
];
|
|
1602
|
+
for (const name of shutdownOrder) {
|
|
1603
|
+
try {
|
|
1604
|
+
await stopChildProcess(managedChildren[name], 5e3, name);
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
cleanupErrors.push(error instanceof Error ? error.message : String(error));
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (nativeEmbeddingProcess && !Object.values(managedChildren).includes(nativeEmbeddingProcess)) {
|
|
1610
|
+
try {
|
|
1611
|
+
await stopChildProcess(nativeEmbeddingProcess, 5e3, "embeddingService");
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
cleanupErrors.push(error instanceof Error ? error.message : String(error));
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
if (startedNativePostgres) {
|
|
1617
|
+
const stopped = stopLocalPostgresRuntime(paths, refreshedEnv, {
|
|
1618
|
+
spawnSync
|
|
1619
|
+
});
|
|
1620
|
+
startedNativePostgres = false;
|
|
1621
|
+
if (!stopped.ok) {
|
|
1622
|
+
cleanupErrors.push(stopped.error ?? stopped.message);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (cleanupErrors.length > 0) {
|
|
1626
|
+
throw new Error(cleanupErrors.join("; "));
|
|
1627
|
+
}
|
|
1628
|
+
cleanupCompleted = true;
|
|
1629
|
+
})();
|
|
1630
|
+
return cleanupPromise;
|
|
1631
|
+
};
|
|
1632
|
+
let requestShutdown = () => void 0;
|
|
1633
|
+
const shutdownRequested = new Promise((resolveShutdown) => {
|
|
1634
|
+
requestShutdown = resolveShutdown;
|
|
1635
|
+
});
|
|
1636
|
+
const waitForStartupReadiness = async (isReady) => {
|
|
1637
|
+
const controller = new AbortController();
|
|
1638
|
+
const interrupted = Promise.race([
|
|
1639
|
+
managedProcessOutcome.then(({ error }) => {
|
|
1640
|
+
throw error ?? new Error("Managed processes stopped during startup.");
|
|
1641
|
+
}),
|
|
1642
|
+
shutdownRequested.then(() => {
|
|
1643
|
+
throw new Error("Koed startup was stopped before readiness.");
|
|
1644
|
+
})
|
|
1645
|
+
]);
|
|
1646
|
+
try {
|
|
1647
|
+
return await Promise.race([
|
|
1648
|
+
interrupted,
|
|
1649
|
+
waitForHealthyOrReady({
|
|
1650
|
+
environment: refreshedEnv,
|
|
1651
|
+
timeoutMs,
|
|
1652
|
+
pollIntervalMs,
|
|
1653
|
+
signal: controller.signal,
|
|
1654
|
+
collectStatus: (env) => collectStatus(env, {
|
|
1655
|
+
desktopRuntimeCapability,
|
|
1656
|
+
appRuntime,
|
|
1657
|
+
fetch: (url, init) => fetch(url, {
|
|
1658
|
+
...init,
|
|
1659
|
+
signal: init?.signal ? AbortSignal.any([controller.signal, init.signal]) : controller.signal
|
|
1660
|
+
})
|
|
1661
|
+
}),
|
|
1662
|
+
...isReady ? { isReady } : {}
|
|
1663
|
+
})
|
|
1664
|
+
]);
|
|
1665
|
+
} finally {
|
|
1666
|
+
controller.abort();
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
const shutdown = () => {
|
|
1670
|
+
stopCapabilityMaintenance();
|
|
1671
|
+
requestShutdown();
|
|
1672
|
+
};
|
|
1673
|
+
if (signal?.aborted)
|
|
1674
|
+
requestShutdown();
|
|
1675
|
+
else
|
|
1676
|
+
signal?.addEventListener("abort", shutdown, { once: true });
|
|
1677
|
+
process.on("SIGINT", shutdown);
|
|
1678
|
+
process.on("SIGTERM", shutdown);
|
|
1679
|
+
const stopSupervisorLogMaintenance = maintainSupervisorLog(refreshedEnv);
|
|
1680
|
+
let sourceRuntimeLeaseAcquired = false;
|
|
1681
|
+
let startupReady = false;
|
|
1682
|
+
try {
|
|
1683
|
+
startupPhase = "authenticated runtime selection";
|
|
1684
|
+
if (execution === "packaged") {
|
|
1685
|
+
const selected = await pinAndResolvePackagedRuntime(paths, environment, requiredRuntime, existsSync3, desktopRuntimeCapability);
|
|
1686
|
+
generationPin = selected.pin;
|
|
1687
|
+
appRuntime = selected.runtime;
|
|
1688
|
+
} else {
|
|
1689
|
+
appRuntime = resolveKoedAppRuntime(paths, environment, existsSync3, void 0, requiredRuntime);
|
|
1690
|
+
assertKoedAppRuntimeAvailable(appRuntime, paths);
|
|
1691
|
+
}
|
|
1692
|
+
if (appRuntime.kind === "source") {
|
|
1693
|
+
runCommand(paths, "Check source runtime artifacts", process.execPath, [
|
|
1694
|
+
resolve4(paths.repoRoot, "scripts/source-runtime-build.mjs"),
|
|
1695
|
+
"check",
|
|
1696
|
+
"--lease-pid",
|
|
1697
|
+
String(process.pid)
|
|
1698
|
+
], refreshedEnv, spawnSync);
|
|
1699
|
+
sourceRuntimeLeaseAcquired = true;
|
|
1700
|
+
emitStartupMilestone("source_runtime_verification_complete");
|
|
1701
|
+
}
|
|
1702
|
+
startupPhase = "service dependency validation and startup";
|
|
1703
|
+
if (config.dependencyMode === "external") {
|
|
1704
|
+
const queueBackend = requiredRuntime.queue;
|
|
1705
|
+
const requiredExternalServices = [
|
|
1706
|
+
["DATABASE_URL", refreshedEnv.DATABASE_URL],
|
|
1707
|
+
...queueBackend === "bullmq" ? [
|
|
1708
|
+
["REDIS_URL", refreshedEnv.REDIS_URL]
|
|
1709
|
+
] : [],
|
|
1710
|
+
["EMBEDDING_SERVICE_URL", refreshedEnv.EMBEDDING_SERVICE_URL]
|
|
1711
|
+
];
|
|
1712
|
+
if (teamCollaborationEnabled) {
|
|
1713
|
+
requiredExternalServices.push(["PRIVACY_SERVICE_URL", refreshedEnv.PRIVACY_SERVICE_URL], ["PRIVACY_SERVICE_TOKEN", refreshedEnv.PRIVACY_SERVICE_TOKEN], [
|
|
1714
|
+
"PRIVACY_RUNTIME_CONTROL_TOKEN",
|
|
1715
|
+
refreshedEnv.PRIVACY_RUNTIME_CONTROL_TOKEN
|
|
1716
|
+
]);
|
|
1717
|
+
}
|
|
1718
|
+
const missing = requiredExternalServices.flatMap(([name, value]) => value?.trim() ? [] : [name]);
|
|
1719
|
+
if (missing.length > 0) {
|
|
1720
|
+
throw new Error(`External dependency mode requires Operator-managed service configuration: ${missing.join(", ")}. Set values in KOED_HOME/config/server.json or environment.`);
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
const queueBackend = requiredRuntime.queue;
|
|
1724
|
+
if (queueBackend === "bullmq" && !refreshedEnv.REDIS_URL?.trim()) {
|
|
1725
|
+
throw new Error("Bundled-local mode with WORK_QUEUE_BACKEND=bullmq requires an Operator-managed Redis URL. Set REDIS_URL or use WORK_QUEUE_BACKEND=local.");
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (useBundledLocalDependencies) {
|
|
1729
|
+
const result = startLocalPostgresRuntime(paths, refreshedEnv, {
|
|
1730
|
+
spawnSync
|
|
1731
|
+
});
|
|
1732
|
+
Object.assign(refreshedEnv, result.env);
|
|
1733
|
+
startedNativePostgres = result.started;
|
|
1734
|
+
if (!result.ok) {
|
|
1735
|
+
throw new Error(`Bundled-local native Postgres could not start: ${result.status.message ?? result.status.state}${localRuntimeFailureDetails(result.status.details)}${result.status.action ? ` ${result.status.action}` : ""}`);
|
|
1736
|
+
}
|
|
1737
|
+
emitStartupMilestone("local_postgres_start_complete");
|
|
1738
|
+
}
|
|
1739
|
+
if (useBundledLocalDependencies && teamCollaborationEnabled) {
|
|
1740
|
+
const result = await startLocalPrivacyRuntime(paths, refreshedEnv, {
|
|
1741
|
+
spawn,
|
|
1742
|
+
appRuntime
|
|
1743
|
+
});
|
|
1744
|
+
Object.assign(refreshedEnv, result.env);
|
|
1745
|
+
nativePrivacyProcess = result.process;
|
|
1746
|
+
if (nativePrivacyProcess) {
|
|
1747
|
+
manageChild("privacyService", nativePrivacyProcess);
|
|
1748
|
+
}
|
|
1749
|
+
if (!result.ok) {
|
|
1750
|
+
throw new Error(`Bundled-local Privacy Filter Service could not start: ${result.status.message ?? result.status.state}${result.status.action ? ` ${result.status.action}` : ""}`);
|
|
1751
|
+
}
|
|
1752
|
+
emitStartupMilestone("privacy_filter_process_spawned");
|
|
1753
|
+
}
|
|
1754
|
+
if (useBundledLocalDependencies) {
|
|
1755
|
+
const result = startLocalEmbeddingRuntime(paths, appProcessEnvironment(refreshedEnv), { spawn, appRuntime });
|
|
1756
|
+
Object.assign(refreshedEnv, result.env);
|
|
1757
|
+
nativeEmbeddingProcess = result.process;
|
|
1758
|
+
if (nativeEmbeddingProcess) {
|
|
1759
|
+
manageChild("embeddingService", nativeEmbeddingProcess);
|
|
1760
|
+
}
|
|
1761
|
+
if (!result.ok) {
|
|
1762
|
+
throw new Error(`Bundled-local native Embedding Service could not start: ${result.status.message ?? result.status.state}${result.status.action ? ` ${result.status.action}` : ""}`);
|
|
1763
|
+
}
|
|
1764
|
+
emitStartupMilestone("embedding_service_process_spawned");
|
|
1765
|
+
}
|
|
1766
|
+
const api = appRuntime.kind === "packaged" ? spawnManagedProcess(paths, "API", process.execPath, [appRuntime.apiEntry], appProcessEnvironment(refreshedEnv), spawn, resolve4(appRuntime.root, "api")) : spawnManagedProcess(paths, "API", process.execPath, [resolve4(paths.repoRoot, "apps/api/dist/index.js")], appProcessEnvironment(refreshedEnv), spawn, resolve4(paths.repoRoot, "apps/api"));
|
|
1767
|
+
manageChild("api", api);
|
|
1768
|
+
emitStartupMilestone("api_process_spawned");
|
|
1769
|
+
const runtime = {
|
|
1770
|
+
pid: process.pid,
|
|
1771
|
+
startedAt: supervisorStartedAt,
|
|
1772
|
+
repoRoot: paths.repoRoot,
|
|
1773
|
+
apiUrl,
|
|
1774
|
+
runtimeMode: config.runtimeMode,
|
|
1775
|
+
dependencyMode: config.dependencyMode,
|
|
1776
|
+
automaticPorts: environment.KOED_AUTO_PORTS === "1",
|
|
1777
|
+
codexTranscriptWatcherEnabled: config.codexTranscriptWatcherEnabled,
|
|
1778
|
+
claudeTranscriptWatcherEnabled: config.claudeTranscriptWatcherEnabled,
|
|
1779
|
+
piTranscriptWatcherEnabled: config.piTranscriptWatcherEnabled,
|
|
1780
|
+
services: [...runtimeServices, "api"],
|
|
1781
|
+
processes: {
|
|
1782
|
+
...nativeEmbeddingProcess ? { embeddingService: nativeEmbeddingProcess.pid ?? 0 } : {},
|
|
1783
|
+
...nativePrivacyProcess ? { privacyService: nativePrivacyProcess.pid ?? 0 } : {},
|
|
1784
|
+
api: api.pid ?? 0
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
const persistRuntime = () => {
|
|
1788
|
+
writeFileSync2(paths.runtimeStatePath, `${JSON.stringify(runtime, null, 2)}
|
|
1789
|
+
`, {
|
|
1790
|
+
mode: 384
|
|
1791
|
+
});
|
|
1792
|
+
runtimeStateWritten = true;
|
|
1793
|
+
};
|
|
1794
|
+
persistRuntime();
|
|
1795
|
+
stopSupervisorExitMonitor = monitorSupervisorExitRequest(paths, {
|
|
1796
|
+
pid: process.pid,
|
|
1797
|
+
startedAt: supervisorStartedAt
|
|
1798
|
+
}, {
|
|
1799
|
+
onExit: requestShutdown
|
|
1800
|
+
});
|
|
1801
|
+
let status = await waitForStartupReadiness((candidate) => candidate.api.state === "healthy" && candidate.database.state === "healthy");
|
|
1802
|
+
if (status.api.state !== "healthy" || status.database.state !== "healthy") {
|
|
1803
|
+
throw new Error(`API and database did not become ready before local credential provisioning. Blocking checks: ${startupBlockingSummary(status, ["api", "database"])}. Inspect /ready and koed status --json for details.`);
|
|
1804
|
+
}
|
|
1805
|
+
emitStartupMilestone("api_and_database_ready");
|
|
1806
|
+
if (desktopManagedLocal) {
|
|
1807
|
+
const desktopApiToken = await provisionDesktopApiToken(paths, appRuntime, refreshedEnv);
|
|
1808
|
+
if (desktopApiToken) {
|
|
1809
|
+
Object.assign(refreshedEnv, {
|
|
1810
|
+
MEMORY_API_TOKEN: desktopApiToken
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
emitStartupMilestone("desktop_credential_provisioned");
|
|
1814
|
+
} else if (useBundledLocalDependencies && config.runtimeMode === "local-personal" && !resolveActiveIntegrationApiToken(paths, refreshedEnv, refreshedRepoEnv)) {
|
|
1815
|
+
const provisioned = await provisionLocalApiTokenDependency(paths, appRuntime, refreshedEnv, refreshedRepoEnv);
|
|
1816
|
+
Object.assign(refreshedEnv, { MEMORY_API_TOKEN: provisioned.token });
|
|
1817
|
+
emitStartupMilestone("packaged_personal_credential_provisioned");
|
|
1818
|
+
}
|
|
1819
|
+
const finalApiToken = resolveActiveIntegrationApiToken(paths, refreshedEnv, refreshedRepoEnv)?.token;
|
|
1820
|
+
if (finalApiToken) {
|
|
1821
|
+
Object.assign(refreshedEnv, { MEMORY_API_TOKEN: finalApiToken });
|
|
1822
|
+
}
|
|
1823
|
+
let localAiRuntime;
|
|
1824
|
+
if (localAiRuntimeEnabled) {
|
|
1825
|
+
if (!finalApiToken) {
|
|
1826
|
+
throw new Error("A Personal API Token is required to start the local AI runtime.");
|
|
1827
|
+
}
|
|
1828
|
+
localAiRuntime = spawnManagedProcess(paths, "Local AI Runtime", process.execPath, [appRuntime.localAiRuntime], {
|
|
1829
|
+
...appProcessEnvironment(refreshedEnv),
|
|
1830
|
+
KOED_HOME: paths.koedHome,
|
|
1831
|
+
MEMORY_API_URL: apiUrl,
|
|
1832
|
+
MEMORY_API_TOKEN: finalApiToken,
|
|
1833
|
+
MEMORY_CODEX_TRANSCRIPT_WATCHER_ENABLED: String(config.codexTranscriptWatcherEnabled),
|
|
1834
|
+
MEMORY_CLAUDE_TRANSCRIPT_WATCHER_ENABLED: String(config.claudeTranscriptWatcherEnabled),
|
|
1835
|
+
MEMORY_PI_TRANSCRIPT_WATCHER_ENABLED: String(config.piTranscriptWatcherEnabled)
|
|
1836
|
+
}, spawn, appRuntime.kind === "packaged" ? resolve4(appRuntime.root, "mcp-server") : resolve4(paths.repoRoot, "packages", "mcp-server"));
|
|
1837
|
+
manageChild("localAiRuntime", localAiRuntime);
|
|
1838
|
+
emitStartupMilestone("local_ai_runtime_process_spawned");
|
|
1839
|
+
}
|
|
1840
|
+
const worker = appRuntime.kind === "packaged" ? spawnManagedProcess(paths, "Worker", process.execPath, [appRuntime.workerEntry], appProcessEnvironment(refreshedEnv), spawn, resolve4(appRuntime.root, "worker")) : spawnManagedProcess(paths, "Worker", process.execPath, [resolve4(paths.repoRoot, "apps/worker/dist/index.js")], appProcessEnvironment(refreshedEnv), spawn, resolve4(paths.repoRoot, "apps/worker"));
|
|
1841
|
+
manageChild("worker", worker);
|
|
1842
|
+
emitStartupMilestone("worker_process_spawned");
|
|
1843
|
+
if (config.runtimeMode === "local-personal") {
|
|
1844
|
+
deviceRequestService = await startDeviceRequestService({
|
|
1845
|
+
paths,
|
|
1846
|
+
host: environment.KOED_PDS_REQUEST_HOST?.trim() || void 0,
|
|
1847
|
+
enrolled: () => Boolean(createPdsApplicationSecretStore({ rootPath: paths.koedHome }).get("pds-runtime")),
|
|
1848
|
+
redeem: async (link, label, signal2) => {
|
|
1849
|
+
const fetchWithShutdown = (input, init) => fetch(input, {
|
|
1850
|
+
...init,
|
|
1851
|
+
signal: init?.signal ? AbortSignal.any([signal2, init.signal]) : signal2
|
|
1852
|
+
});
|
|
1853
|
+
const result = await redeemPairingLink(link, label, paths, { ...refreshedEnv, PDS_LOCAL_CONTROL_URL: apiUrl }, { fetch: fetchWithShutdown });
|
|
1854
|
+
if (!result.ok)
|
|
1855
|
+
throw new Error("Enrollment did not complete.");
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
runtime.services = [...runtimeServices, ...appServices];
|
|
1860
|
+
runtime.processes = {
|
|
1861
|
+
...runtime.processes,
|
|
1862
|
+
...localAiRuntime ? { localAiRuntime: localAiRuntime.pid ?? 0 } : {},
|
|
1863
|
+
worker: worker.pid ?? 0
|
|
1864
|
+
};
|
|
1865
|
+
persistRuntime();
|
|
1866
|
+
console.log(JSON.stringify({
|
|
1867
|
+
ok: true,
|
|
1868
|
+
state: "starting",
|
|
1869
|
+
koedHome: paths.koedHome,
|
|
1870
|
+
apiUrl,
|
|
1871
|
+
services: runtime.services
|
|
1872
|
+
}, null, 2));
|
|
1873
|
+
status = await waitForStartupReadiness();
|
|
1874
|
+
if (!status.ok) {
|
|
1875
|
+
throw new Error(`Core services did not become ready before timeout. Blocking checks: ${startupBlockingSummary(status)}. Inspect /ready and koed status --json for details.`);
|
|
1876
|
+
}
|
|
1877
|
+
if (localAiRuntimeEnabled) {
|
|
1878
|
+
stopCapabilityMaintenance = maintainUpstreamCapabilities(paths, {
|
|
1879
|
+
onError: () => console.warn("Upstream capability maintenance will retry.")
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
emitStartupMilestone("core_services_ready");
|
|
1883
|
+
startupReady = true;
|
|
1884
|
+
onReady?.();
|
|
1885
|
+
console.log(JSON.stringify(status, null, 2));
|
|
1886
|
+
emitStartupMilestone("final_supervisor_status_emitted");
|
|
1887
|
+
console.log("Koed server supervisor is running. Press Ctrl-C to stop local app processes.");
|
|
1888
|
+
await Promise.race([
|
|
1889
|
+
shutdownRequested,
|
|
1890
|
+
managedProcessOutcome.then(({ error }) => {
|
|
1891
|
+
if (error)
|
|
1892
|
+
throw error;
|
|
1893
|
+
})
|
|
1894
|
+
]);
|
|
1895
|
+
await cleanupStartedResources();
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
if (!startupReady)
|
|
1898
|
+
emitStartupMilestone("startup_failed");
|
|
1899
|
+
try {
|
|
1900
|
+
await cleanupStartedResources();
|
|
1901
|
+
if (runtimeStateOwnedByCurrentProcess()) {
|
|
1902
|
+
rmSync3(paths.runtimeStatePath, { force: true });
|
|
1903
|
+
}
|
|
1904
|
+
} catch (cleanupError) {
|
|
1905
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)} Cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
|
|
1906
|
+
}
|
|
1907
|
+
throw error;
|
|
1908
|
+
} finally {
|
|
1909
|
+
stopCapabilityMaintenance();
|
|
1910
|
+
stopSupervisorExitMonitor();
|
|
1911
|
+
stopSupervisorLogMaintenance();
|
|
1912
|
+
process.off("SIGINT", shutdown);
|
|
1913
|
+
process.off("SIGTERM", shutdown);
|
|
1914
|
+
signal?.removeEventListener("abort", shutdown);
|
|
1915
|
+
managedProcessMonitor.dispose();
|
|
1916
|
+
if (runtimeStateWritten && runtimeStateOwnedByCurrentProcess()) {
|
|
1917
|
+
rmSync3(paths.runtimeStatePath, { force: true });
|
|
1918
|
+
}
|
|
1919
|
+
if (sourceRuntimeLeaseAcquired) {
|
|
1920
|
+
try {
|
|
1921
|
+
const releaseResult = spawnSync(process.execPath, [
|
|
1922
|
+
resolve4(paths.repoRoot, "scripts/source-runtime-build.mjs"),
|
|
1923
|
+
"release-lease",
|
|
1924
|
+
"--lease-pid",
|
|
1925
|
+
String(process.pid)
|
|
1926
|
+
], {
|
|
1927
|
+
cwd: paths.repoRoot,
|
|
1928
|
+
env: refreshedEnv,
|
|
1929
|
+
stdio: "inherit"
|
|
1930
|
+
});
|
|
1931
|
+
if (releaseResult.error || releaseResult.status !== 0) {
|
|
1932
|
+
console.warn(`Could not release the source-runtime lease: ${releaseResult.error?.message ?? `exit code ${releaseResult.status ?? 1}`}.`);
|
|
1933
|
+
}
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
console.warn(`Could not release the source-runtime lease: ${error instanceof Error ? error.message : String(error)}.`);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
if (cleanupCompleted) {
|
|
1939
|
+
try {
|
|
1940
|
+
await generationPin?.release();
|
|
1941
|
+
} finally {
|
|
1942
|
+
releaseSupervisorLock();
|
|
1943
|
+
}
|
|
1944
|
+
} else {
|
|
1945
|
+
console.error("Supervisor cleanup could not confirm all managed children exited; retaining runtime generation pin and supervisor lock.");
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
throw new Error(`${startupPhase} failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1950
|
+
} finally {
|
|
1951
|
+
if (!cleanupStarted)
|
|
1952
|
+
releaseSupervisorLock();
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
// packages/koed-server/dist/cli-service-context.js
|
|
1957
|
+
var createCliServiceContext = ({ collectStatus = collectKoedServerStatus, collectStartupStatus = collectKoedServerStartupStatus, collectDoctor = collectKoedServerDoctor, inspectDeviceIdentity = inspectDeviceIdentityStatus, rotateDeviceIdentity: rotateIdentity = rotateDeviceIdentity, invalidateUpstreamEnrollmentReferences: invalidateEnrollmentReferences = invalidateUpstreamEnrollmentReferences, start = startKoedServer, startDaemon = startKoedServerDaemon, stop = stopKoedServer, restart = restartKoedServer, setupCore: setupCoreIntegration = setupCore, setupCodex: setup = setupCodex, setupClaude: setupClaudeIntegration = setupClaude, setupPi: setupPiIntegration = setupPi, removeClaude: removeClaudeIntegration = removeClaude, removePi: removePiIntegration = removePi, removeCodex: removeCodexIntegrationFn = removeCodexIntegration, repairCodex = repairCodexIntegration, resolvePaths = resolveKoedServerPaths, stdout = process.stdout, stderr = process.stderr, desktopRuntimeCapability, supervisorInvocation } = {}) => {
|
|
1958
|
+
if (desktopRuntimeCapability) {
|
|
1959
|
+
const authority = { desktopRuntimeCapability };
|
|
1960
|
+
const [status, startupStatus, doctor, foreground, restartServer, stopServer] = [
|
|
1961
|
+
collectStatus,
|
|
1962
|
+
collectStartupStatus,
|
|
1963
|
+
collectDoctor,
|
|
1964
|
+
start,
|
|
1965
|
+
restart,
|
|
1966
|
+
stop
|
|
1967
|
+
];
|
|
1968
|
+
const [core, codex, claude, pi, codexRepair] = [
|
|
1969
|
+
setupCoreIntegration,
|
|
1970
|
+
setup,
|
|
1971
|
+
setupClaudeIntegration,
|
|
1972
|
+
setupPiIntegration,
|
|
1973
|
+
repairCodex
|
|
1974
|
+
];
|
|
1975
|
+
const daemon = startDaemon;
|
|
1976
|
+
collectStatus = (environment, dependencies, options) => status(environment, { ...dependencies, ...authority }, options);
|
|
1977
|
+
collectStartupStatus = (environment, dependencies) => startupStatus(environment, { ...dependencies, ...authority });
|
|
1978
|
+
collectDoctor = (environment, dependencies) => doctor(environment, { ...dependencies, ...authority });
|
|
1979
|
+
start = (options) => foreground({ ...options, ...authority });
|
|
1980
|
+
stop = (options = {}) => stopServer({ ...options, ...authority });
|
|
1981
|
+
restart = (options) => restartServer({ ...options, ...authority, ...supervisorStart() });
|
|
1982
|
+
startDaemon = (options) => daemon({ ...options, ...supervisorStart() });
|
|
1983
|
+
setupCoreIntegration = (options) => core({ ...options, ...authority });
|
|
1984
|
+
setup = (options) => codex({ ...options, ...authority });
|
|
1985
|
+
repairCodex = (options) => codexRepair({ ...options, ...authority });
|
|
1986
|
+
setupClaudeIntegration = (environment, spawnSync, options) => claude(environment, spawnSync, { ...options, ...authority });
|
|
1987
|
+
setupPiIntegration = (environment, spawnSync, options) => pi(environment, spawnSync, { ...options, ...authority });
|
|
1988
|
+
}
|
|
1989
|
+
function supervisorStart() {
|
|
1990
|
+
if (!supervisorInvocation)
|
|
1991
|
+
throw new Error("Verified Desktop supervisor command is unavailable.");
|
|
1992
|
+
return supervisorInvocation;
|
|
1993
|
+
}
|
|
1994
|
+
return {
|
|
1995
|
+
collectStatus,
|
|
1996
|
+
collectStartupStatus,
|
|
1997
|
+
collectDoctor,
|
|
1998
|
+
inspectDeviceIdentity,
|
|
1999
|
+
rotateIdentity,
|
|
2000
|
+
invalidateEnrollmentReferences,
|
|
2001
|
+
start,
|
|
2002
|
+
startDaemon,
|
|
2003
|
+
stop,
|
|
2004
|
+
restart,
|
|
2005
|
+
setupCoreIntegration,
|
|
2006
|
+
setup,
|
|
2007
|
+
setupClaudeIntegration,
|
|
2008
|
+
setupPiIntegration,
|
|
2009
|
+
removeClaudeIntegration,
|
|
2010
|
+
removePiIntegration,
|
|
2011
|
+
removeCodexIntegrationFn,
|
|
2012
|
+
repairCodex,
|
|
2013
|
+
resolvePaths,
|
|
2014
|
+
stdout,
|
|
2015
|
+
stderr,
|
|
2016
|
+
desktopRuntimeCapability
|
|
2017
|
+
};
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
// packages/koed-server/dist/cli-service-output.js
|
|
2021
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2022
|
+
import { resolve as resolve5 } from "node:path";
|
|
2023
|
+
var safeLifecycle = (paths) => {
|
|
2024
|
+
try {
|
|
2025
|
+
return observeRuntimeLifecycle(paths);
|
|
2026
|
+
} catch {
|
|
2027
|
+
return {
|
|
2028
|
+
state: "unavailable",
|
|
2029
|
+
epoch: "unavailable",
|
|
2030
|
+
message: "Koed state could not be read."
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
var printJson = (stdout, value) => {
|
|
2035
|
+
stdout.write(`${JSON.stringify(value, null, 2)}
|
|
2036
|
+
`);
|
|
2037
|
+
};
|
|
2038
|
+
var persistDoctorVerification = (paths, doctor) => {
|
|
2039
|
+
mkdirSync4(resolve5(paths.lastVerificationPath, ".."), {
|
|
2040
|
+
recursive: true,
|
|
2041
|
+
mode: 448
|
|
2042
|
+
});
|
|
2043
|
+
writeFileSync3(paths.lastVerificationPath, `${JSON.stringify({
|
|
2044
|
+
ok: doctor.ok,
|
|
2045
|
+
checkedAt: doctor.generatedAt,
|
|
2046
|
+
message: doctor.summary
|
|
2047
|
+
}, null, 2)}
|
|
2048
|
+
`, { mode: 384 });
|
|
2049
|
+
};
|
|
2050
|
+
var checkCoreSetupNeeded = (paths) => {
|
|
2051
|
+
if (!existsSync4(paths.localAppCredentialPath)) {
|
|
2052
|
+
return `Core setup is required. Run:
|
|
2053
|
+
|
|
2054
|
+
koed setup core
|
|
2055
|
+
|
|
2056
|
+
This will provision your local API Token and prepare Koed services.`;
|
|
2057
|
+
}
|
|
2058
|
+
return null;
|
|
2059
|
+
};
|
|
2060
|
+
var getNoAgentsWarning = (status) => {
|
|
2061
|
+
const anyConfigured = status.codex?.configured || status.claudeCode?.configured || status.pi?.configured;
|
|
2062
|
+
if (!anyConfigured) {
|
|
2063
|
+
return "No AI Clients configured. Run: koed setup codex, setup claude, or setup pi";
|
|
2064
|
+
}
|
|
2065
|
+
return null;
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
export {
|
|
2069
|
+
deviceRequestCommand,
|
|
2070
|
+
createCliServiceContext,
|
|
2071
|
+
safeLifecycle,
|
|
2072
|
+
printJson,
|
|
2073
|
+
persistDoctorVerification,
|
|
2074
|
+
checkCoreSetupNeeded,
|
|
2075
|
+
getNoAgentsWarning
|
|
2076
|
+
};
|