negotium 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/canonical-mcp-bridge.js +2 -3
- package/dist/canonical-mcp-bridge.js.map +1 -1
- package/dist/{chunk-gz12qcf0.js → chunk-a9y2rwxf.js} +2 -2
- package/dist/{chunk-gz12qcf0.js.map → chunk-a9y2rwxf.js.map} +3 -3
- package/dist/{chunk-j0kjscb7.js → chunk-s2gez3wg.js} +20 -2
- package/dist/{chunk-j0kjscb7.js.map → chunk-s2gez3wg.js.map} +2 -2
- package/dist/cron.js +292 -256
- package/dist/cron.js.map +23 -22
- package/dist/hosted-agent.js +3012 -17
- package/dist/hosted-agent.js.map +30 -3
- package/dist/main.js +31323 -36
- package/dist/main.js.map +198 -3
- package/dist/prompts.js +12 -9
- package/dist/prompts.js.map +5 -5
- package/dist/registry.js +10 -6
- package/dist/registry.js.map +3 -3
- package/dist/rollout.js +1 -1
- package/dist/runtime/cron/store.ts +24 -1
- package/dist/runtime/src/application/submit-user-message.ts +3 -0
- package/dist/runtime/src/index.ts +7 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +3 -0
- package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
- package/dist/runtime/src/platform/config.ts +2 -2
- package/dist/runtime/src/platform/mcp-config.ts +13 -0
- package/dist/runtime/src/prompts/builders.ts +20 -6
- package/dist/runtime/src/query/active-rooms.ts +2 -0
- package/dist/runtime/src/runtime/delivery-ack.ts +112 -0
- package/dist/runtime/src/runtime/errors.ts +12 -12
- package/dist/runtime/src/runtime/turn-runner.ts +12 -0
- package/dist/runtime/src/storage/api-messages.ts +7 -5
- package/dist/runtime/src/storage/api-topics.ts +5 -2
- package/dist/runtime/src/storage/forum-db.ts +4 -1
- package/dist/runtime/src/storage/runtime-turn-requests.ts +1 -0
- package/dist/runtime/src/storage/session-asks.ts +13 -5
- package/dist/runtime/src/storage/storage-contract.ts +54 -0
- package/dist/runtime/src/storage/storage-host.ts +89 -36
- package/dist/runtime/src/storage/storage-public-host.ts +16 -0
- package/dist/runtime/src/storage/storage-public.ts +21 -5
- package/dist/runtime/src/storage/token-stats.ts +1 -1
- package/dist/runtime/src/types/api.ts +2 -0
- package/dist/runtime/src/types.ts +2 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +29 -18
- package/dist/runtime-helpers.js.map +8 -7
- package/dist/storage.js +260 -202
- package/dist/storage.js.map +11 -9
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +2 -0
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +4 -0
- package/dist/types/packages/core/src/prompts/builders.d.ts +2 -0
- package/dist/types/packages/core/src/storage/api-messages.d.ts +6 -6
- package/dist/types/packages/core/src/storage/api-topics.d.ts +20 -2
- package/dist/types/packages/core/src/storage/forum-db.d.ts +2 -1
- package/dist/types/packages/core/src/storage/session-asks.d.ts +3 -2
- package/dist/types/packages/core/src/storage/storage-contract.d.ts +46 -0
- package/dist/types/packages/core/src/storage/storage-host.d.ts +12 -19
- package/dist/types/packages/core/src/storage/storage-public-host.d.ts +7 -0
- package/dist/types/packages/core/src/storage/storage-public.d.ts +4 -2
- package/dist/types/packages/core/src/types/api.d.ts +2 -0
- package/dist/types/packages/core/src/types.d.ts +2 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/types/packages/module-cron/src/store.d.ts +22 -2
- package/dist/vault.js +2 -2
- package/dist/vault.js.map +3 -3
- package/package.json +1 -1
- package/dist/chunk-01xcc856.js +0 -149
- package/dist/chunk-01xcc856.js.map +0 -10
- package/dist/chunk-1a6kv9vt.js +0 -118
- package/dist/chunk-1a6kv9vt.js.map +0 -10
- package/dist/chunk-1b62d730.js +0 -117
- package/dist/chunk-1b62d730.js.map +0 -10
- package/dist/chunk-1k7t7bnx.js +0 -97
- package/dist/chunk-1k7t7bnx.js.map +0 -10
- package/dist/chunk-28apmz4r.js +0 -431
- package/dist/chunk-28apmz4r.js.map +0 -11
- package/dist/chunk-2fvydfkx.js +0 -141
- package/dist/chunk-2fvydfkx.js.map +0 -10
- package/dist/chunk-41ygvg5k.js +0 -2387
- package/dist/chunk-41ygvg5k.js.map +0 -26
- package/dist/chunk-4q966ttx.js +0 -371
- package/dist/chunk-4q966ttx.js.map +0 -11
- package/dist/chunk-51rcsavw.js +0 -1712
- package/dist/chunk-51rcsavw.js.map +0 -18
- package/dist/chunk-59d0shyj.js +0 -103
- package/dist/chunk-59d0shyj.js.map +0 -10
- package/dist/chunk-5fzgvn79.js +0 -3536
- package/dist/chunk-5fzgvn79.js.map +0 -20
- package/dist/chunk-61px0717.js +0 -153
- package/dist/chunk-61px0717.js.map +0 -11
- package/dist/chunk-66y0hr8c.js +0 -2136
- package/dist/chunk-66y0hr8c.js.map +0 -29
- package/dist/chunk-69k45zg8.js +0 -250
- package/dist/chunk-69k45zg8.js.map +0 -11
- package/dist/chunk-6ah5farg.js +0 -130
- package/dist/chunk-6ah5farg.js.map +0 -10
- package/dist/chunk-6d4fmea5.js +0 -117
- package/dist/chunk-6d4fmea5.js.map +0 -12
- package/dist/chunk-8cahx7xc.js +0 -46
- package/dist/chunk-8cahx7xc.js.map +0 -10
- package/dist/chunk-8f20fk4n.js +0 -252
- package/dist/chunk-8f20fk4n.js.map +0 -10
- package/dist/chunk-91ckrvk8.js +0 -318
- package/dist/chunk-91ckrvk8.js.map +0 -10
- package/dist/chunk-93krf6ng.js +0 -3069
- package/dist/chunk-93krf6ng.js.map +0 -32
- package/dist/chunk-9qx1t77a.js +0 -214
- package/dist/chunk-9qx1t77a.js.map +0 -10
- package/dist/chunk-beqhdr9w.js +0 -589
- package/dist/chunk-beqhdr9w.js.map +0 -11
- package/dist/chunk-bftftsgq.js +0 -1637
- package/dist/chunk-bftftsgq.js.map +0 -15
- package/dist/chunk-c500xe9z.js +0 -33
- package/dist/chunk-c500xe9z.js.map +0 -10
- package/dist/chunk-cjsnkvq2.js +0 -23
- package/dist/chunk-cjsnkvq2.js.map +0 -10
- package/dist/chunk-cxa7yghg.js +0 -577
- package/dist/chunk-cxa7yghg.js.map +0 -14
- package/dist/chunk-edb07sjp.js +0 -567
- package/dist/chunk-edb07sjp.js.map +0 -14
- package/dist/chunk-f361dwba.js +0 -589
- package/dist/chunk-f361dwba.js.map +0 -11
- package/dist/chunk-f5xgprg1.js +0 -59
- package/dist/chunk-f5xgprg1.js.map +0 -10
- package/dist/chunk-ffefbd3r.js +0 -17
- package/dist/chunk-ffefbd3r.js.map +0 -10
- package/dist/chunk-fswhzdf5.js +0 -64
- package/dist/chunk-fswhzdf5.js.map +0 -10
- package/dist/chunk-g144wdrs.js +0 -2907
- package/dist/chunk-g144wdrs.js.map +0 -24
- package/dist/chunk-h980hb2n.js +0 -512
- package/dist/chunk-h980hb2n.js.map +0 -13
- package/dist/chunk-h9vbhhgr.js +0 -1337
- package/dist/chunk-h9vbhhgr.js.map +0 -16
- package/dist/chunk-hwy3n31w.js +0 -21
- package/dist/chunk-hwy3n31w.js.map +0 -9
- package/dist/chunk-jtrk0khr.js +0 -108
- package/dist/chunk-jtrk0khr.js.map +0 -10
- package/dist/chunk-jzz91jwh.js +0 -70
- package/dist/chunk-jzz91jwh.js.map +0 -10
- package/dist/chunk-m9e3tbr8.js +0 -708
- package/dist/chunk-m9e3tbr8.js.map +0 -14
- package/dist/chunk-mh5mexsf.js +0 -147
- package/dist/chunk-mh5mexsf.js.map +0 -11
- package/dist/chunk-npd8n03v.js +0 -2152
- package/dist/chunk-npd8n03v.js.map +0 -15
- package/dist/chunk-pg1azh6n.js +0 -2
- package/dist/chunk-pg1azh6n.js.map +0 -9
- package/dist/chunk-qwy57177.js +0 -237
- package/dist/chunk-qwy57177.js.map +0 -11
- package/dist/chunk-r31q21h5.js +0 -53
- package/dist/chunk-r31q21h5.js.map +0 -10
- package/dist/chunk-rbvctv1n.js +0 -366
- package/dist/chunk-rbvctv1n.js.map +0 -10
- package/dist/chunk-sa874j5c.js +0 -9
- package/dist/chunk-sa874j5c.js.map +0 -10
- package/dist/chunk-sctcs5zb.js +0 -281
- package/dist/chunk-sctcs5zb.js.map +0 -10
- package/dist/chunk-tsstrcfc.js +0 -122
- package/dist/chunk-tsstrcfc.js.map +0 -10
- package/dist/chunk-wk2qf31s.js +0 -58
- package/dist/chunk-wk2qf31s.js.map +0 -10
- package/dist/chunk-wx7ag4dd.js +0 -2936
- package/dist/chunk-wx7ag4dd.js.map +0 -20
- package/dist/chunk-znx1fpfd.js +0 -61
- package/dist/chunk-znx1fpfd.js.map +0 -10
package/dist/chunk-8f20fk4n.js
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertSecureCentralUrl,
|
|
3
|
-
assertSecureRelayUrl,
|
|
4
|
-
isJoinPersisted,
|
|
5
|
-
joinCredentialDigest,
|
|
6
|
-
saveJoinWhileLocked,
|
|
7
|
-
withJoinCredentialLock
|
|
8
|
-
} from "./chunk-4q966ttx.js";
|
|
9
|
-
import {
|
|
10
|
-
DATA_DIR
|
|
11
|
-
} from "./chunk-qwy57177.js";
|
|
12
|
-
|
|
13
|
-
// ../../adapters/otium/src/enrollment.ts
|
|
14
|
-
import {
|
|
15
|
-
createDecipheriv,
|
|
16
|
-
createPrivateKey,
|
|
17
|
-
createPublicKey,
|
|
18
|
-
diffieHellman,
|
|
19
|
-
generateKeyPairSync,
|
|
20
|
-
hkdfSync,
|
|
21
|
-
randomUUID
|
|
22
|
-
} from "crypto";
|
|
23
|
-
import {
|
|
24
|
-
chmodSync,
|
|
25
|
-
closeSync,
|
|
26
|
-
existsSync,
|
|
27
|
-
fsyncSync,
|
|
28
|
-
linkSync,
|
|
29
|
-
mkdirSync,
|
|
30
|
-
openSync,
|
|
31
|
-
readFileSync,
|
|
32
|
-
renameSync,
|
|
33
|
-
unlinkSync,
|
|
34
|
-
writeFileSync
|
|
35
|
-
} from "fs";
|
|
36
|
-
import { dirname, resolve } from "path";
|
|
37
|
-
var INFO = Buffer.from("otium-node-enrollment-v1", "utf8");
|
|
38
|
-
function parseEnrollmentInvite(code) {
|
|
39
|
-
let parsed;
|
|
40
|
-
try {
|
|
41
|
-
parsed = JSON.parse(Buffer.from(code.trim(), "base64url").toString("utf8"));
|
|
42
|
-
} catch {
|
|
43
|
-
throw new Error("production invite must be base64url JSON");
|
|
44
|
-
}
|
|
45
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46
|
-
throw new Error("production invite must decode to an object");
|
|
47
|
-
}
|
|
48
|
-
const raw = parsed;
|
|
49
|
-
const central = typeof raw.central === "string" ? raw.central.trim().replace(/\/+$/, "") : "";
|
|
50
|
-
const token = typeof raw.token === "string" ? raw.token.trim() : "";
|
|
51
|
-
if (raw.v !== 2 || !central || !token.startsWith("nei_")) {
|
|
52
|
-
throw new Error("production invite requires {v:2, central:http(s), token:nei_\u2026}");
|
|
53
|
-
}
|
|
54
|
-
assertSecureCentralUrl(central);
|
|
55
|
-
return { v: 2, central, token };
|
|
56
|
-
}
|
|
57
|
-
function pendingEnrollmentPath() {
|
|
58
|
-
return resolve(DATA_DIR, "otium-enrollment-pending.json");
|
|
59
|
-
}
|
|
60
|
-
function isEnrollmentPending(invite) {
|
|
61
|
-
const path = pendingEnrollmentPath();
|
|
62
|
-
if (!existsSync(path))
|
|
63
|
-
return false;
|
|
64
|
-
try {
|
|
65
|
-
const saved = JSON.parse(readFileSync(path, "utf8"));
|
|
66
|
-
return saved.central === invite.central && saved.token === invite.token;
|
|
67
|
-
} catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function loadOrCreatePending(invite, nodeName) {
|
|
72
|
-
const path = pendingEnrollmentPath();
|
|
73
|
-
if (existsSync(path)) {
|
|
74
|
-
const saved = JSON.parse(readFileSync(path, "utf8"));
|
|
75
|
-
if (saved.central === invite.central && saved.token === invite.token)
|
|
76
|
-
return saved;
|
|
77
|
-
throw new Error(`another Otium enrollment is pending at ${path}`);
|
|
78
|
-
}
|
|
79
|
-
const pair = generateKeyPairSync("x25519");
|
|
80
|
-
const pending = {
|
|
81
|
-
...invite,
|
|
82
|
-
idempotencyKey: randomUUID(),
|
|
83
|
-
privateKey: pair.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64url"),
|
|
84
|
-
publicKey: pair.publicKey.export({ format: "der", type: "spki" }).toString("base64url"),
|
|
85
|
-
...nodeName ? { nodeName } : {}
|
|
86
|
-
};
|
|
87
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
88
|
-
const temporaryPath = resolve(dirname(path), `.otium-enrollment-pending.json.${process.pid}.${randomUUID()}.tmp`);
|
|
89
|
-
let fd;
|
|
90
|
-
try {
|
|
91
|
-
fd = openSync(temporaryPath, "wx", 384);
|
|
92
|
-
writeFileSync(fd, `${JSON.stringify(pending, null, 2)}
|
|
93
|
-
`, "utf8");
|
|
94
|
-
fsyncSync(fd);
|
|
95
|
-
closeSync(fd);
|
|
96
|
-
fd = undefined;
|
|
97
|
-
linkSync(temporaryPath, path);
|
|
98
|
-
unlinkSync(temporaryPath);
|
|
99
|
-
chmodSync(path, 384);
|
|
100
|
-
const directoryFd = openSync(dirname(path), "r");
|
|
101
|
-
try {
|
|
102
|
-
fsyncSync(directoryFd);
|
|
103
|
-
} finally {
|
|
104
|
-
closeSync(directoryFd);
|
|
105
|
-
}
|
|
106
|
-
} catch (error) {
|
|
107
|
-
if (fd !== undefined)
|
|
108
|
-
closeSync(fd);
|
|
109
|
-
if (existsSync(temporaryPath))
|
|
110
|
-
unlinkSync(temporaryPath);
|
|
111
|
-
if (error.code === "EEXIST" && existsSync(path)) {
|
|
112
|
-
const saved = JSON.parse(readFileSync(path, "utf8"));
|
|
113
|
-
if (saved.central === invite.central && saved.token === invite.token)
|
|
114
|
-
return saved;
|
|
115
|
-
}
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
return pending;
|
|
119
|
-
}
|
|
120
|
-
function replacePendingEnrollment(pending) {
|
|
121
|
-
const path = pendingEnrollmentPath();
|
|
122
|
-
const directory = dirname(path);
|
|
123
|
-
const temporaryPath = resolve(directory, `.otium-enrollment-pending.json.${process.pid}.${randomUUID()}.tmp`);
|
|
124
|
-
let fd;
|
|
125
|
-
try {
|
|
126
|
-
fd = openSync(temporaryPath, "wx", 384);
|
|
127
|
-
writeFileSync(fd, `${JSON.stringify(pending, null, 2)}
|
|
128
|
-
`, "utf8");
|
|
129
|
-
fsyncSync(fd);
|
|
130
|
-
closeSync(fd);
|
|
131
|
-
fd = undefined;
|
|
132
|
-
renameSync(temporaryPath, path);
|
|
133
|
-
const directoryFd = openSync(directory, "r");
|
|
134
|
-
try {
|
|
135
|
-
fsyncSync(directoryFd);
|
|
136
|
-
} finally {
|
|
137
|
-
closeSync(directoryFd);
|
|
138
|
-
}
|
|
139
|
-
} catch (error) {
|
|
140
|
-
if (fd !== undefined)
|
|
141
|
-
closeSync(fd);
|
|
142
|
-
if (existsSync(temporaryPath))
|
|
143
|
-
unlinkSync(temporaryPath);
|
|
144
|
-
throw error;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function recordClaimedCredential(pending, join) {
|
|
148
|
-
withJoinCredentialLock(() => {
|
|
149
|
-
const current = JSON.parse(readFileSync(pendingEnrollmentPath(), "utf8"));
|
|
150
|
-
if (current.central !== pending.central || current.token !== pending.token || current.idempotencyKey !== pending.idempotencyKey || current.publicKey !== pending.publicKey) {
|
|
151
|
-
throw new Error("pending Otium enrollment changed while its claim was in flight");
|
|
152
|
-
}
|
|
153
|
-
const claimed = { digest: joinCredentialDigest(join), cellId: join.cellId };
|
|
154
|
-
if (current.claimed && (current.claimed.digest !== claimed.digest || current.claimed.cellId !== claimed.cellId)) {
|
|
155
|
-
throw new Error("central returned different credentials for an idempotent enrollment claim");
|
|
156
|
-
}
|
|
157
|
-
replacePendingEnrollment({ ...current, claimed });
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
function openCredential(envelope, privateKeyDer) {
|
|
161
|
-
if (envelope.v !== 1 || envelope.algorithm !== "X25519-HKDF-SHA256-AES-256-GCM") {
|
|
162
|
-
throw new Error("unsupported enrollment credential envelope");
|
|
163
|
-
}
|
|
164
|
-
const privateKey = createPrivateKey({
|
|
165
|
-
key: Buffer.from(privateKeyDer, "base64url"),
|
|
166
|
-
format: "der",
|
|
167
|
-
type: "pkcs8"
|
|
168
|
-
});
|
|
169
|
-
const ephemeral = createPublicKey({
|
|
170
|
-
key: Buffer.from(envelope.ephemeralPublicKey, "base64url"),
|
|
171
|
-
format: "der",
|
|
172
|
-
type: "spki"
|
|
173
|
-
});
|
|
174
|
-
const shared = diffieHellman({ privateKey, publicKey: ephemeral });
|
|
175
|
-
const key = Buffer.from(hkdfSync("sha256", shared, Buffer.from(envelope.salt, "base64url"), INFO, 32));
|
|
176
|
-
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(envelope.nonce, "base64url"));
|
|
177
|
-
decipher.setAAD(INFO);
|
|
178
|
-
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
179
|
-
return Buffer.concat([
|
|
180
|
-
decipher.update(Buffer.from(envelope.ciphertext, "base64url")),
|
|
181
|
-
decipher.final()
|
|
182
|
-
]).toString("utf8");
|
|
183
|
-
}
|
|
184
|
-
async function postJson(url, body, headers) {
|
|
185
|
-
const response = await fetch(url, {
|
|
186
|
-
method: "POST",
|
|
187
|
-
headers: { "content-type": "application/json", ...headers },
|
|
188
|
-
body: JSON.stringify(body)
|
|
189
|
-
});
|
|
190
|
-
const result = await response.json();
|
|
191
|
-
if (!response.ok)
|
|
192
|
-
throw new Error(String(result.error || `request failed (${response.status})`));
|
|
193
|
-
return result;
|
|
194
|
-
}
|
|
195
|
-
async function previewEnrollment(invite) {
|
|
196
|
-
return postJson(`${invite.central}/api/v1/node-enrollments/preview`, {
|
|
197
|
-
token: invite.token
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
async function claimEnrollment(invite, nodeName) {
|
|
201
|
-
const pending = loadOrCreatePending(invite, nodeName);
|
|
202
|
-
const response = await postJson(`${invite.central}/api/v1/node-enrollments/claim`, {
|
|
203
|
-
token: invite.token,
|
|
204
|
-
credentialPublicKey: pending.publicKey,
|
|
205
|
-
...pending.nodeName ? { nodeName: pending.nodeName } : {}
|
|
206
|
-
}, { "idempotency-key": pending.idempotencyKey });
|
|
207
|
-
const credential = response.credential;
|
|
208
|
-
const secret = openCredential(credential, pending.privateKey);
|
|
209
|
-
if (!secret.startsWith("rcs_"))
|
|
210
|
-
throw new Error("central returned an invalid runtime credential");
|
|
211
|
-
const join = {
|
|
212
|
-
v: 2,
|
|
213
|
-
central: invite.central,
|
|
214
|
-
relay: String(response.relayUrl),
|
|
215
|
-
cellId: String(response.cell?.id),
|
|
216
|
-
secret
|
|
217
|
-
};
|
|
218
|
-
if (!join.relay || !join.cellId || join.cellId === "undefined") {
|
|
219
|
-
throw new Error("central returned an incomplete enrollment response");
|
|
220
|
-
}
|
|
221
|
-
assertSecureRelayUrl(join.relay);
|
|
222
|
-
recordClaimedCredential(pending, join);
|
|
223
|
-
return join;
|
|
224
|
-
}
|
|
225
|
-
function commitEnrollment(join, options = {}) {
|
|
226
|
-
return withJoinCredentialLock(() => {
|
|
227
|
-
const pendingPath = pendingEnrollmentPath();
|
|
228
|
-
const pending = existsSync(pendingPath) ? JSON.parse(readFileSync(pendingPath, "utf8")) : null;
|
|
229
|
-
const digest = joinCredentialDigest(join);
|
|
230
|
-
if (pending && (!pending.claimed || pending.claimed.digest !== digest || pending.claimed.cellId !== join.cellId)) {
|
|
231
|
-
throw new Error(`pending Otium enrollment at ${pendingPath} does not match these credentials`);
|
|
232
|
-
}
|
|
233
|
-
const path = saveJoinWhileLocked(join, options);
|
|
234
|
-
if (!isJoinPersisted(join)) {
|
|
235
|
-
throw new Error("Otium join credentials were not durably persisted");
|
|
236
|
-
}
|
|
237
|
-
if (!pending)
|
|
238
|
-
return path;
|
|
239
|
-
unlinkSync(pendingPath);
|
|
240
|
-
const directoryFd = openSync(dirname(pendingPath), "r");
|
|
241
|
-
try {
|
|
242
|
-
fsyncSync(directoryFd);
|
|
243
|
-
} finally {
|
|
244
|
-
closeSync(directoryFd);
|
|
245
|
-
}
|
|
246
|
-
return path;
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export { parseEnrollmentInvite, pendingEnrollmentPath, isEnrollmentPending, previewEnrollment, claimEnrollment, commitEnrollment };
|
|
251
|
-
|
|
252
|
-
//# debugId=3E67B5DF4109FD5964756E2164756E21
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../adapters/otium/src/enrollment.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import {\n createDecipheriv,\n createPrivateKey,\n createPublicKey,\n diffieHellman,\n generateKeyPairSync,\n hkdfSync,\n randomUUID,\n} from \"node:crypto\";\nimport {\n chmodSync,\n closeSync,\n existsSync,\n fsyncSync,\n linkSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { DATA_DIR } from \"@negotium/core\";\nimport {\n isJoinPersisted,\n joinCredentialDigest,\n type OtiumJoin,\n type SaveJoinOptions,\n saveJoinWhileLocked,\n withJoinCredentialLock,\n} from \"@/join\";\nimport { assertSecureCentralUrl, assertSecureRelayUrl } from \"@/secure-transport\";\n\nconst INFO = Buffer.from(\"otium-node-enrollment-v1\", \"utf8\");\n\nexport interface EnrollmentInvite {\n v: 2;\n central: string;\n token: string;\n}\n\ninterface PendingEnrollment extends EnrollmentInvite {\n idempotencyKey: string;\n privateKey: string;\n publicKey: string;\n /** Frozen with the idempotency key so retries replay the same claim body. */\n nodeName?: string;\n claimed?: {\n digest: string;\n cellId: string;\n };\n}\n\nexport interface EnrollmentCredentialEnvelope {\n v: 1;\n algorithm: \"X25519-HKDF-SHA256-AES-256-GCM\";\n ephemeralPublicKey: string;\n salt: string;\n nonce: string;\n ciphertext: string;\n tag: string;\n}\n\nexport interface EnrollmentPreviewResponse {\n ok: true;\n preview: {\n workspace: { id: string; slug: string; name: string };\n suggestedNodeName: string | null;\n transport: \"relay\";\n relayUrl: string;\n expiresAt: string;\n status: string;\n topics: string;\n };\n}\n\ninterface EnrollmentClaimResponse {\n ok: true;\n relayUrl: string;\n cell: { id: string; baseUrl: string };\n credential: EnrollmentCredentialEnvelope;\n}\n\nexport function parseEnrollmentInvite(code: string): EnrollmentInvite {\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(code.trim(), \"base64url\").toString(\"utf8\"));\n } catch {\n throw new Error(\"production invite must be base64url JSON\");\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"production invite must decode to an object\");\n }\n const raw = parsed as Record<string, unknown>;\n const central = typeof raw.central === \"string\" ? raw.central.trim().replace(/\\/+$/, \"\") : \"\";\n const token = typeof raw.token === \"string\" ? raw.token.trim() : \"\";\n if (raw.v !== 2 || !central || !token.startsWith(\"nei_\")) {\n throw new Error(\"production invite requires {v:2, central:http(s), token:nei_…}\");\n }\n assertSecureCentralUrl(central);\n return { v: 2, central, token };\n}\n\nexport function pendingEnrollmentPath(): string {\n return resolve(DATA_DIR, \"otium-enrollment-pending.json\");\n}\n\nexport function isEnrollmentPending(invite: EnrollmentInvite): boolean {\n const path = pendingEnrollmentPath();\n if (!existsSync(path)) return false;\n try {\n const saved = JSON.parse(readFileSync(path, \"utf8\")) as PendingEnrollment;\n return saved.central === invite.central && saved.token === invite.token;\n } catch {\n return false;\n }\n}\n\nfunction loadOrCreatePending(invite: EnrollmentInvite, nodeName?: string): PendingEnrollment {\n const path = pendingEnrollmentPath();\n if (existsSync(path)) {\n const saved = JSON.parse(readFileSync(path, \"utf8\")) as PendingEnrollment;\n if (saved.central === invite.central && saved.token === invite.token) return saved;\n throw new Error(`another Otium enrollment is pending at ${path}`);\n }\n const pair = generateKeyPairSync(\"x25519\");\n const pending: PendingEnrollment = {\n ...invite,\n idempotencyKey: randomUUID(),\n privateKey: pair.privateKey.export({ format: \"der\", type: \"pkcs8\" }).toString(\"base64url\"),\n publicKey: pair.publicKey.export({ format: \"der\", type: \"spki\" }).toString(\"base64url\"),\n ...(nodeName ? { nodeName } : {}),\n };\n mkdirSync(dirname(path), { recursive: true });\n const temporaryPath = resolve(\n dirname(path),\n `.otium-enrollment-pending.json.${process.pid}.${randomUUID()}.tmp`,\n );\n let fd: number | undefined;\n try {\n fd = openSync(temporaryPath, \"wx\", 0o600);\n writeFileSync(fd, `${JSON.stringify(pending, null, 2)}\\n`, \"utf8\");\n fsyncSync(fd);\n closeSync(fd);\n fd = undefined;\n // Publish only the completely written key material and never overwrite a\n // concurrent enrollment created by another process.\n linkSync(temporaryPath, path);\n unlinkSync(temporaryPath);\n chmodSync(path, 0o600);\n const directoryFd = openSync(dirname(path), \"r\");\n try {\n fsyncSync(directoryFd);\n } finally {\n closeSync(directoryFd);\n }\n } catch (error) {\n if (fd !== undefined) closeSync(fd);\n if (existsSync(temporaryPath)) unlinkSync(temporaryPath);\n if ((error as NodeJS.ErrnoException).code === \"EEXIST\" && existsSync(path)) {\n const saved = JSON.parse(readFileSync(path, \"utf8\")) as PendingEnrollment;\n if (saved.central === invite.central && saved.token === invite.token) return saved;\n }\n throw error;\n }\n return pending;\n}\n\nfunction replacePendingEnrollment(pending: PendingEnrollment): void {\n const path = pendingEnrollmentPath();\n const directory = dirname(path);\n const temporaryPath = resolve(\n directory,\n `.otium-enrollment-pending.json.${process.pid}.${randomUUID()}.tmp`,\n );\n let fd: number | undefined;\n try {\n fd = openSync(temporaryPath, \"wx\", 0o600);\n writeFileSync(fd, `${JSON.stringify(pending, null, 2)}\\n`, \"utf8\");\n fsyncSync(fd);\n closeSync(fd);\n fd = undefined;\n renameSync(temporaryPath, path);\n const directoryFd = openSync(directory, \"r\");\n try {\n fsyncSync(directoryFd);\n } finally {\n closeSync(directoryFd);\n }\n } catch (error) {\n if (fd !== undefined) closeSync(fd);\n if (existsSync(temporaryPath)) unlinkSync(temporaryPath);\n throw error;\n }\n}\n\nfunction recordClaimedCredential(pending: PendingEnrollment, join: OtiumJoin): void {\n withJoinCredentialLock(() => {\n const current = JSON.parse(readFileSync(pendingEnrollmentPath(), \"utf8\")) as PendingEnrollment;\n if (\n current.central !== pending.central ||\n current.token !== pending.token ||\n current.idempotencyKey !== pending.idempotencyKey ||\n current.publicKey !== pending.publicKey\n ) {\n throw new Error(\"pending Otium enrollment changed while its claim was in flight\");\n }\n const claimed = { digest: joinCredentialDigest(join), cellId: join.cellId };\n if (\n current.claimed &&\n (current.claimed.digest !== claimed.digest || current.claimed.cellId !== claimed.cellId)\n ) {\n throw new Error(\"central returned different credentials for an idempotent enrollment claim\");\n }\n replacePendingEnrollment({ ...current, claimed });\n });\n}\n\nfunction openCredential(envelope: EnrollmentCredentialEnvelope, privateKeyDer: string): string {\n if (envelope.v !== 1 || envelope.algorithm !== \"X25519-HKDF-SHA256-AES-256-GCM\") {\n throw new Error(\"unsupported enrollment credential envelope\");\n }\n const privateKey = createPrivateKey({\n key: Buffer.from(privateKeyDer, \"base64url\"),\n format: \"der\",\n type: \"pkcs8\",\n });\n const ephemeral = createPublicKey({\n key: Buffer.from(envelope.ephemeralPublicKey, \"base64url\"),\n format: \"der\",\n type: \"spki\",\n });\n const shared = diffieHellman({ privateKey, publicKey: ephemeral });\n const key = Buffer.from(\n hkdfSync(\"sha256\", shared, Buffer.from(envelope.salt, \"base64url\"), INFO, 32),\n );\n const decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(envelope.nonce, \"base64url\"));\n decipher.setAAD(INFO);\n decipher.setAuthTag(Buffer.from(envelope.tag, \"base64url\"));\n return Buffer.concat([\n decipher.update(Buffer.from(envelope.ciphertext, \"base64url\")),\n decipher.final(),\n ]).toString(\"utf8\");\n}\n\nasync function postJson<T>(\n url: string,\n body: unknown,\n headers?: Record<string, string>,\n): Promise<T> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...headers },\n body: JSON.stringify(body),\n });\n const result = (await response.json()) as Record<string, unknown>;\n if (!response.ok) throw new Error(String(result.error || `request failed (${response.status})`));\n return result as T;\n}\n\nexport async function previewEnrollment(\n invite: EnrollmentInvite,\n): Promise<EnrollmentPreviewResponse> {\n return postJson<EnrollmentPreviewResponse>(`${invite.central}/api/v1/node-enrollments/preview`, {\n token: invite.token,\n });\n}\n\nexport async function claimEnrollment(\n invite: EnrollmentInvite,\n nodeName?: string,\n): Promise<OtiumJoin> {\n const pending = loadOrCreatePending(invite, nodeName);\n const response = await postJson<EnrollmentClaimResponse>(\n `${invite.central}/api/v1/node-enrollments/claim`,\n {\n token: invite.token,\n credentialPublicKey: pending.publicKey,\n ...(pending.nodeName ? { nodeName: pending.nodeName } : {}),\n },\n { \"idempotency-key\": pending.idempotencyKey },\n );\n const credential = response.credential as EnrollmentCredentialEnvelope;\n const secret = openCredential(credential, pending.privateKey);\n if (!secret.startsWith(\"rcs_\")) throw new Error(\"central returned an invalid runtime credential\");\n const join: OtiumJoin = {\n v: 2,\n central: invite.central,\n relay: String(response.relayUrl),\n cellId: String(response.cell?.id),\n secret,\n };\n if (!join.relay || !join.cellId || join.cellId === \"undefined\") {\n throw new Error(\"central returned an incomplete enrollment response\");\n }\n assertSecureRelayUrl(join.relay);\n recordClaimedCredential(pending, join);\n return join;\n}\n\n/**\n * Durably commit a claimed enrollment. The retry key is deliberately removed\n * only after the atomic join-file save is fsynced and verified. A crash before\n * that point leaves enough key material to replay the idempotent claim.\n */\nexport function commitEnrollment(join: OtiumJoin, options: SaveJoinOptions = {}): string {\n return withJoinCredentialLock(() => {\n const pendingPath = pendingEnrollmentPath();\n const pending = existsSync(pendingPath)\n ? (JSON.parse(readFileSync(pendingPath, \"utf8\")) as PendingEnrollment)\n : null;\n const digest = joinCredentialDigest(join);\n if (\n pending &&\n (!pending.claimed ||\n pending.claimed.digest !== digest ||\n pending.claimed.cellId !== join.cellId)\n ) {\n throw new Error(\n `pending Otium enrollment at ${pendingPath} does not match these credentials`,\n );\n }\n\n const path = saveJoinWhileLocked(join, options);\n if (!isJoinPersisted(join)) {\n throw new Error(\"Otium join credentials were not durably persisted\");\n }\n if (!pending) return path;\n\n // The same lock protects save→verification→pending deletion from a\n // concurrent explicit replacement through saveJoin().\n unlinkSync(pendingPath);\n const directoryFd = openSync(dirname(pendingPath), \"r\");\n try {\n fsyncSync(directoryFd);\n } finally {\n closeSync(directoryFd);\n }\n return path;\n });\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "3E67B5DF4109FD5964756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
package/dist/chunk-91ckrvk8.js
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
WsHub,
|
|
4
|
-
cloneProfileForChild,
|
|
5
|
-
copyMessagesForTopic,
|
|
6
|
-
isLegacySharedGeneral
|
|
7
|
-
} from "./chunk-h9vbhhgr.js";
|
|
8
|
-
import {
|
|
9
|
-
findTopicTitleConflict,
|
|
10
|
-
getTopic,
|
|
11
|
-
getTopicSessionId,
|
|
12
|
-
inferTopicKind,
|
|
13
|
-
isTopicVisible,
|
|
14
|
-
listTopics,
|
|
15
|
-
setTopicSessionId,
|
|
16
|
-
upsertTopic
|
|
17
|
-
} from "./chunk-f361dwba.js";
|
|
18
|
-
import {
|
|
19
|
-
cleanupAgentFork,
|
|
20
|
-
forkAgentSession
|
|
21
|
-
} from "./chunk-8cahx7xc.js";
|
|
22
|
-
import {
|
|
23
|
-
getRegistry
|
|
24
|
-
} from "./chunk-6d4fmea5.js";
|
|
25
|
-
import {
|
|
26
|
-
appendConversationEventStrict,
|
|
27
|
-
cloneConversationLog,
|
|
28
|
-
readConversation
|
|
29
|
-
} from "./chunk-61px0717.js";
|
|
30
|
-
import {
|
|
31
|
-
resolveModelForAgent
|
|
32
|
-
} from "./chunk-tsstrcfc.js";
|
|
33
|
-
import {
|
|
34
|
-
getApiTopicConfig,
|
|
35
|
-
setApiTopicConfig
|
|
36
|
-
} from "./chunk-6ah5farg.js";
|
|
37
|
-
import"./chunk-m9e3tbr8.js";
|
|
38
|
-
import"./chunk-mh5mexsf.js";
|
|
39
|
-
import {
|
|
40
|
-
resolveTopicWorkspaceDir
|
|
41
|
-
} from "./chunk-qwy57177.js";
|
|
42
|
-
import {
|
|
43
|
-
beginRuntimeTopicMaintenance,
|
|
44
|
-
isTopicRunning
|
|
45
|
-
} from "./chunk-h980hb2n.js";
|
|
46
|
-
import {
|
|
47
|
-
logger
|
|
48
|
-
} from "./chunk-ffefbd3r.js";
|
|
49
|
-
import"./chunk-pg1azh6n.js";
|
|
50
|
-
import {
|
|
51
|
-
storageDatabase
|
|
52
|
-
} from "./chunk-1a6kv9vt.js";
|
|
53
|
-
import"./chunk-f5xgprg1.js";
|
|
54
|
-
import"./chunk-hwy3n31w.js";
|
|
55
|
-
|
|
56
|
-
// ../../packages/core/src/topics/derive.ts
|
|
57
|
-
import { randomUUID } from "crypto";
|
|
58
|
-
import { mkdirSync } from "fs";
|
|
59
|
-
function getTopics() {
|
|
60
|
-
return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));
|
|
61
|
-
}
|
|
62
|
-
function getVisibleTopics() {
|
|
63
|
-
return getTopics().filter(isTopicVisible).map((topic) => {
|
|
64
|
-
if (!topic.agent)
|
|
65
|
-
return topic;
|
|
66
|
-
const registry = getRegistry(topic.agent);
|
|
67
|
-
const config = getApiTopicConfig(topic.id);
|
|
68
|
-
const requestedEffort = config?.effort ?? topic.defaultEffort;
|
|
69
|
-
return {
|
|
70
|
-
...topic,
|
|
71
|
-
effectiveModel: resolveModelForAgent(topic.agent, config?.model ?? topic.defaultModel, registry),
|
|
72
|
-
effectiveEffort: requestedEffort && registry.validateEffort(requestedEffort) ? requestedEffort : registry.defaultEffort
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function updateTopic(topicId, patch) {
|
|
77
|
-
const topic = getTopic(topicId);
|
|
78
|
-
if (!topic)
|
|
79
|
-
return false;
|
|
80
|
-
Object.assign(topic, patch);
|
|
81
|
-
upsertTopic(topic);
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
function isParticipant(topic, userId) {
|
|
85
|
-
return topic.participants.some((p) => p.userId === userId);
|
|
86
|
-
}
|
|
87
|
-
function nextDerivedTopicTitle(sourceTitle, kind, suffix) {
|
|
88
|
-
const visibleTitles = new Set(listTopics().filter((topic) => topic.kind === kind).map((topic) => topic.title.toLowerCase()));
|
|
89
|
-
let n = 1;
|
|
90
|
-
let title = `${sourceTitle}-${suffix}-${n}`;
|
|
91
|
-
while (visibleTitles.has(title.toLowerCase())) {
|
|
92
|
-
n += 1;
|
|
93
|
-
title = `${sourceTitle}-${suffix}-${n}`;
|
|
94
|
-
}
|
|
95
|
-
return title;
|
|
96
|
-
}
|
|
97
|
-
function effectiveAgentForTopic(_topicId, topic) {
|
|
98
|
-
if (!topic.agent)
|
|
99
|
-
return;
|
|
100
|
-
return topic.agent;
|
|
101
|
-
}
|
|
102
|
-
function rollbackHandleFor(agent, sessionId, rolloutPath) {
|
|
103
|
-
return { agent, forkId: sessionId, rolloutPath };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
class TopicTitleConflictError extends Error {
|
|
107
|
-
title;
|
|
108
|
-
constructor(title) {
|
|
109
|
-
super(`A topic named "${title}" already exists`);
|
|
110
|
-
this.title = title;
|
|
111
|
-
this.name = "TopicTitleConflictError";
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
class TopicDeriveBusyError extends Error {
|
|
116
|
-
constructor(message = "Topic is busy; try again when the current operation finishes") {
|
|
117
|
-
super(message);
|
|
118
|
-
this.name = "TopicDeriveBusyError";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function createDerivedTopic(sourceTopicId, userId, copyHistory, opts) {
|
|
122
|
-
const topic = getTopic(sourceTopicId);
|
|
123
|
-
if (!topic)
|
|
124
|
-
return null;
|
|
125
|
-
if (topic.kind === "manager")
|
|
126
|
-
return null;
|
|
127
|
-
if (!isParticipant(topic, userId))
|
|
128
|
-
return null;
|
|
129
|
-
const subagent = copyHistory ? undefined : opts?.subagent;
|
|
130
|
-
let maintenance = null;
|
|
131
|
-
if (!subagent && !opts?.allowActiveSource) {
|
|
132
|
-
if (isTopicRunning(sourceTopicId))
|
|
133
|
-
throw new TopicDeriveBusyError;
|
|
134
|
-
maintenance = beginRuntimeTopicMaintenance(sourceTopicId);
|
|
135
|
-
if (!maintenance)
|
|
136
|
-
throw new TopicDeriveBusyError;
|
|
137
|
-
if (isTopicRunning(sourceTopicId)) {
|
|
138
|
-
maintenance.finish();
|
|
139
|
-
maintenance = null;
|
|
140
|
-
throw new TopicDeriveBusyError;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
return await createDerivedTopicUnderFence(topic, sourceTopicId, userId, copyHistory, opts);
|
|
145
|
-
} finally {
|
|
146
|
-
maintenance?.finish();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
async function createDerivedTopicUnderFence(topic, sourceTopicId, userId, copyHistory, opts) {
|
|
150
|
-
const now = new Date().toISOString();
|
|
151
|
-
const subagent = copyHistory ? undefined : opts?.subagent;
|
|
152
|
-
const suffix = copyHistory ? "fork" : subagent ? "agent" : "spawn";
|
|
153
|
-
const sourceConfig = getApiTopicConfig(sourceTopicId);
|
|
154
|
-
const agent = subagent?.agent ?? effectiveAgentForTopic(sourceTopicId, topic);
|
|
155
|
-
const participants = copyHistory || subagent ? [
|
|
156
|
-
...topic.participants.filter((p) => p.userId !== userId).map((p) => ({ ...p, role: "member" })),
|
|
157
|
-
{ userId, role: "owner" }
|
|
158
|
-
] : [{ userId, role: "owner" }];
|
|
159
|
-
const kind = topic.kind ?? inferTopicKind(topic);
|
|
160
|
-
const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix);
|
|
161
|
-
const conflict = findTopicTitleConflict(title, kind);
|
|
162
|
-
if (conflict) {
|
|
163
|
-
logger.info({ sourceTopicId, title, kind, conflictTopicId: conflict.id }, "createDerivedTopic: title conflict");
|
|
164
|
-
throw new TopicTitleConflictError(title);
|
|
165
|
-
}
|
|
166
|
-
const derived = {
|
|
167
|
-
id: randomUUID(),
|
|
168
|
-
title,
|
|
169
|
-
kind,
|
|
170
|
-
description: topic.description,
|
|
171
|
-
agent: subagent?.agent ?? topic.agent,
|
|
172
|
-
defaultModel: subagent?.model ?? topic.defaultModel,
|
|
173
|
-
defaultEffort: topic.defaultEffort,
|
|
174
|
-
aiMode: topic.aiMode,
|
|
175
|
-
aiMention: topic.aiMention,
|
|
176
|
-
participants,
|
|
177
|
-
createdAt: now,
|
|
178
|
-
lastMessageAt: now,
|
|
179
|
-
parentTopicId: sourceTopicId,
|
|
180
|
-
isFork: copyHistory,
|
|
181
|
-
...subagent ? { isSubagent: true } : {},
|
|
182
|
-
visibility: topic.visibility,
|
|
183
|
-
accessMode: topic.accessMode
|
|
184
|
-
};
|
|
185
|
-
let sessionId;
|
|
186
|
-
let rollbackHandle;
|
|
187
|
-
try {
|
|
188
|
-
if (agent) {
|
|
189
|
-
const cwd = resolveTopicWorkspaceDir(derived.id);
|
|
190
|
-
mkdirSync(cwd, { recursive: true });
|
|
191
|
-
const registry = getRegistry(agent);
|
|
192
|
-
const requestedRolloutModel = subagent?.model ?? (subagent?.agent ? undefined : sourceConfig?.model) ?? derived.defaultModel;
|
|
193
|
-
const rolloutModel = resolveModelForAgent(agent, requestedRolloutModel, registry);
|
|
194
|
-
const requestedRolloutEffort = subagent?.agent ? registry.defaultEffort : sourceConfig?.effort ?? derived.defaultEffort;
|
|
195
|
-
const rolloutEffort = requestedRolloutEffort && registry.validateEffort(requestedRolloutEffort) ? requestedRolloutEffort : registry.defaultEffort;
|
|
196
|
-
if (copyHistory) {
|
|
197
|
-
const parentSessionId = getTopicSessionId(sourceTopicId);
|
|
198
|
-
if (parentSessionId) {
|
|
199
|
-
try {
|
|
200
|
-
const fork = await forkAgentSession({
|
|
201
|
-
agent,
|
|
202
|
-
parentSessionId,
|
|
203
|
-
cwd,
|
|
204
|
-
userId,
|
|
205
|
-
topicName: topic.title,
|
|
206
|
-
title,
|
|
207
|
-
model: rolloutModel,
|
|
208
|
-
...rolloutEffort ? { effort: rolloutEffort } : {}
|
|
209
|
-
});
|
|
210
|
-
sessionId = fork.forkId;
|
|
211
|
-
rollbackHandle = fork;
|
|
212
|
-
} catch (err) {
|
|
213
|
-
logger.warn({ err, sourceTopicId, title: topic.title, agent, parentSessionId }, "createDerivedTopic: native fork failed; synthesizing from unified history");
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (!sessionId) {
|
|
217
|
-
const rollout = registry.writeRollout({
|
|
218
|
-
cwd,
|
|
219
|
-
entries: readConversation(userId, topic.title),
|
|
220
|
-
model: rolloutModel,
|
|
221
|
-
...rolloutEffort ? { effort: rolloutEffort } : {}
|
|
222
|
-
});
|
|
223
|
-
sessionId = rollout.sessionId;
|
|
224
|
-
rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
const rollout = registry.writeRollout({
|
|
228
|
-
cwd,
|
|
229
|
-
entries: [],
|
|
230
|
-
model: rolloutModel,
|
|
231
|
-
...rolloutEffort ? { effort: rolloutEffort } : {}
|
|
232
|
-
});
|
|
233
|
-
sessionId = rollout.sessionId;
|
|
234
|
-
rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const created = storageDatabase.transaction(() => {
|
|
238
|
-
const currentSource = getTopic(sourceTopicId);
|
|
239
|
-
if (!currentSource || currentSource.kind === "manager" || !isParticipant(currentSource, userId)) {
|
|
240
|
-
throw new TopicDeriveBusyError("Source topic changed while deriving; try again");
|
|
241
|
-
}
|
|
242
|
-
const transactionalConflict = findTopicTitleConflict(title, kind);
|
|
243
|
-
if (transactionalConflict)
|
|
244
|
-
throw new TopicTitleConflictError(title);
|
|
245
|
-
upsertTopic(derived);
|
|
246
|
-
if (subagent) {
|
|
247
|
-
const childConfig = { ...sourceConfig ?? {} };
|
|
248
|
-
delete childConfig.mcp;
|
|
249
|
-
if (subagent.agent) {
|
|
250
|
-
delete childConfig.model;
|
|
251
|
-
}
|
|
252
|
-
if (subagent.model)
|
|
253
|
-
childConfig.model = subagent.model;
|
|
254
|
-
if (Object.keys(childConfig).length > 0)
|
|
255
|
-
setApiTopicConfig(derived.id, childConfig);
|
|
256
|
-
} else if (sourceConfig) {
|
|
257
|
-
setApiTopicConfig(derived.id, sourceConfig);
|
|
258
|
-
}
|
|
259
|
-
if (copyHistory) {
|
|
260
|
-
copyMessagesForTopic(sourceTopicId, derived.id);
|
|
261
|
-
cloneConversationLog({
|
|
262
|
-
userId,
|
|
263
|
-
srcTopic: topic.title,
|
|
264
|
-
dstTopic: derived.title
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
if (sessionId && agent) {
|
|
268
|
-
setTopicSessionId(derived.id, sessionId, {
|
|
269
|
-
reason: copyHistory ? "slash-fork" : subagent ? "spawn-subagent" : "slash-spawn",
|
|
270
|
-
agent
|
|
271
|
-
});
|
|
272
|
-
appendConversationEventStrict(userId, derived.title, agent, {
|
|
273
|
-
type: "session",
|
|
274
|
-
sessionId
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return derived;
|
|
278
|
-
}).immediate();
|
|
279
|
-
try {
|
|
280
|
-
const profileClone = await cloneProfileForChild({
|
|
281
|
-
userId,
|
|
282
|
-
srcTopic: sourceTopicId,
|
|
283
|
-
dstTopic: derived.id
|
|
284
|
-
});
|
|
285
|
-
logger.info({
|
|
286
|
-
sourceTopicId,
|
|
287
|
-
derivedTopicId: derived.id,
|
|
288
|
-
copyHistory,
|
|
289
|
-
subagent: Boolean(subagent),
|
|
290
|
-
profileClone
|
|
291
|
-
}, "createDerivedTopic: playwright profile clone");
|
|
292
|
-
} catch (profileErr) {
|
|
293
|
-
logger.warn({ err: profileErr, sourceTopicId, derivedTopicId: derived.id }, "createDerivedTopic: playwright profile clone threw (continuing)");
|
|
294
|
-
}
|
|
295
|
-
WsHub.get().broadcastTopicCreated(created);
|
|
296
|
-
return created;
|
|
297
|
-
} catch (err) {
|
|
298
|
-
if (rollbackHandle)
|
|
299
|
-
cleanupAgentFork(rollbackHandle);
|
|
300
|
-
if (err instanceof TopicDeriveBusyError || err instanceof TopicTitleConflictError)
|
|
301
|
-
throw err;
|
|
302
|
-
logger.warn({ err, sourceTopicId, derivedTopicId: derived.id, copyHistory }, "createDerivedTopic: failed to create derived topic");
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
export {
|
|
307
|
-
updateTopic,
|
|
308
|
-
isParticipant,
|
|
309
|
-
getVisibleTopics,
|
|
310
|
-
getTopics,
|
|
311
|
-
createDerivedTopic,
|
|
312
|
-
TopicTitleConflictError,
|
|
313
|
-
TopicDeriveBusyError
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
export { getTopics, getVisibleTopics, updateTopic, isParticipant, TopicTitleConflictError, TopicDeriveBusyError, createDerivedTopic };
|
|
317
|
-
|
|
318
|
-
//# debugId=DBAB5ABB3DF8FA7264756E2164756E21
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../packages/core/src/topics/derive.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Derived-topic creation — fork (config+history copy), spawn (config-only),\n * and subagent (agent-initiated worker room) share this helper.\n *\n * Ported from otium runtime-api `api/routes/topics.ts`, minus the REST route\n * table; the route-facing wrappers (`getTopics`, `updateTopic`) live here too\n * so ported call sites keep working.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { cleanupAgentFork, type ForkHandle, forkAgentSession } from \"#agents/fork\";\nimport { resolveModelForAgent } from \"#agents/model-catalog\";\nimport { getRegistry } from \"#agents/registry\";\nimport { WsHub } from \"#bus\";\nimport { resolveTopicWorkspaceDir } from \"#platform/config\";\nimport { logger } from \"#platform/logger\";\nimport { cloneProfileForChild } from \"#platform/playwright/manager\";\nimport { isTopicRunning } from \"#query/active-rooms\";\nimport { copyMessagesForTopic } from \"#storage/api-messages\";\nimport { getApiTopicConfig, setApiTopicConfig } from \"#storage/api-topic-config\";\nimport {\n findTopicTitleConflict,\n getTopic,\n getTopicSessionId,\n inferTopicKind,\n isTopicVisible,\n listTopics,\n setTopicSessionId,\n upsertTopic,\n} from \"#storage/api-topics\";\nimport {\n appendConversationEventStrict,\n cloneConversationLog,\n readConversation,\n} from \"#storage/conversations\";\nimport { db } from \"#storage/forum-db\";\nimport { beginRuntimeTopicMaintenance } from \"#storage/runtime-topic-state\";\nimport { isLegacySharedGeneral } from \"#topics/personal-general\";\nimport type { AgentKind } from \"#types\";\nimport type { TopicDto } from \"#types/api\";\n\nexport function getTopics(): TopicDto[] {\n return listTopics().filter((topic) => !isLegacySharedGeneral(topic.id));\n}\n\n/** Topics adapters may show in lists and selection UIs. */\nexport function getVisibleTopics(): TopicDto[] {\n return getTopics()\n .filter(isTopicVisible)\n .map((topic) => {\n if (!topic.agent) return topic;\n const registry = getRegistry(topic.agent);\n const config = getApiTopicConfig(topic.id);\n const requestedEffort = config?.effort ?? topic.defaultEffort;\n return {\n ...topic,\n effectiveModel: resolveModelForAgent(\n topic.agent,\n config?.model ?? topic.defaultModel,\n registry,\n ),\n effectiveEffort:\n requestedEffort && registry.validateEffort(requestedEffort)\n ? requestedEffort\n : registry.defaultEffort,\n };\n });\n}\n\nexport function updateTopic(topicId: string, patch: Partial<TopicDto>): boolean {\n const topic = getTopic(topicId);\n if (!topic) return false;\n Object.assign(topic, patch);\n upsertTopic(topic);\n return true;\n}\n\nexport function isParticipant(topic: TopicDto, userId: string): boolean {\n return topic.participants.some((p) => p.userId === userId);\n}\n\nfunction nextDerivedTopicTitle(\n sourceTitle: string,\n kind: TopicDto[\"kind\"],\n suffix: \"fork\" | \"spawn\" | \"agent\",\n): string {\n const visibleTitles = new Set(\n listTopics()\n .filter((topic) => topic.kind === kind)\n .map((topic) => topic.title.toLowerCase()),\n );\n let n = 1;\n let title = `${sourceTitle}-${suffix}-${n}`;\n while (visibleTitles.has(title.toLowerCase())) {\n n += 1;\n title = `${sourceTitle}-${suffix}-${n}`;\n }\n return title;\n}\n\nfunction effectiveAgentForTopic(_topicId: string, topic: TopicDto): AgentKind | undefined {\n if (!topic.agent) return undefined;\n return topic.agent;\n}\n\nfunction rollbackHandleFor(agent: AgentKind, sessionId: string, rolloutPath: string): ForkHandle {\n return { agent, forkId: sessionId, rolloutPath };\n}\n\n/** Thrown when a user-supplied derived-topic name collides with an existing\n * topic title. Callers surface this as a specific \"pick a different name\"\n * message instead of the generic membership/session failure. */\nexport class TopicTitleConflictError extends Error {\n constructor(readonly title: string) {\n super(`A topic named \"${title}\" already exists`);\n this.name = \"TopicTitleConflictError\";\n }\n}\n\nexport class TopicDeriveBusyError extends Error {\n constructor(message = \"Topic is busy; try again when the current operation finishes\") {\n super(message);\n this.name = \"TopicDeriveBusyError\";\n }\n}\n\ninterface DerivedTopicOptions {\n name?: string;\n subagent?: { agent?: AgentKind; model?: string };\n allowActiveSource?: boolean;\n}\n\n/**\n * Shared helper for spawn (config-only copy) and fork (config+history copy).\n *\n * - fork (copyHistory=true): inherits ALL source participants, copies messages,\n * creator becomes owner, and forks the source AI session when AI is enabled.\n * - spawn (copyHistory=false): config only, creator is sole owner, empty history.\n * It still creates a fresh AI session when AI is enabled.\n * - subagent (copyHistory=false + opts.subagent): agent-initiated worker room.\n * Marked `isSubagent`, optionally overriding the child's agent/model.\n *\n * @param sourceTopicId - must exist and caller must be a participant\n * @param userId - caller (becomes owner of the new topic)\n * @param copyHistory - true for fork, false for spawn\n * @param opts - optional custom name and subagent overrides\n * @returns the newly created TopicDto or null on error\n * @throws TopicDeriveBusyError when user-triggered derivation races active work or maintenance\n * @throws TopicTitleConflictError when the requested name is already taken\n */\nexport async function createDerivedTopic(\n sourceTopicId: string,\n userId: string,\n copyHistory: boolean,\n opts?: DerivedTopicOptions,\n): Promise<TopicDto | null> {\n const topic = getTopic(sourceTopicId);\n if (!topic) return null;\n if (topic.kind === \"manager\") return null;\n if (!isParticipant(topic, userId)) return null;\n\n const subagent = copyHistory ? undefined : opts?.subagent;\n let maintenance: ReturnType<typeof beginRuntimeTopicMaintenance> = null;\n if (!subagent && !opts?.allowActiveSource) {\n if (isTopicRunning(sourceTopicId)) throw new TopicDeriveBusyError();\n maintenance = beginRuntimeTopicMaintenance(sourceTopicId);\n if (!maintenance) throw new TopicDeriveBusyError();\n if (isTopicRunning(sourceTopicId)) {\n maintenance.finish();\n maintenance = null;\n throw new TopicDeriveBusyError();\n }\n }\n\n try {\n return await createDerivedTopicUnderFence(topic, sourceTopicId, userId, copyHistory, opts);\n } finally {\n maintenance?.finish();\n }\n}\n\nasync function createDerivedTopicUnderFence(\n topic: TopicDto,\n sourceTopicId: string,\n userId: string,\n copyHistory: boolean,\n opts?: DerivedTopicOptions,\n): Promise<TopicDto | null> {\n const now = new Date().toISOString();\n const subagent = copyHistory ? undefined : opts?.subagent;\n const suffix = copyHistory ? \"fork\" : subagent ? \"agent\" : \"spawn\";\n const sourceConfig = getApiTopicConfig(sourceTopicId);\n const agent = subagent?.agent ?? effectiveAgentForTopic(sourceTopicId, topic);\n\n // Fork: inherit all source participants, creator becomes owner\n // Spawn: creator is sole owner\n // Subagent: inherit all source participants — everyone who can see the\n // parent room sees the card there, so its \"view room\" target must be\n // accessible to them too (single-topic GET rejects non-participants).\n const participants: TopicDto[\"participants\"] =\n copyHistory || subagent\n ? [\n ...topic.participants\n .filter((p) => p.userId !== userId)\n .map((p) => ({ ...p, role: \"member\" as const })),\n { userId, role: \"owner\" as const },\n ]\n : [{ userId, role: \"owner\" as const }];\n const kind = topic.kind ?? inferTopicKind(topic);\n const title = opts?.name?.trim() || nextDerivedTopicTitle(topic.title, kind, suffix);\n const conflict = findTopicTitleConflict(title, kind);\n if (conflict) {\n logger.info(\n { sourceTopicId, title, kind, conflictTopicId: conflict.id },\n \"createDerivedTopic: title conflict\",\n );\n throw new TopicTitleConflictError(title);\n }\n const derived: TopicDto = {\n id: randomUUID(),\n title,\n kind,\n description: topic.description,\n agent: subagent?.agent ?? topic.agent,\n defaultModel: subagent?.model ?? topic.defaultModel,\n defaultEffort: topic.defaultEffort,\n aiMode: topic.aiMode,\n aiMention: topic.aiMention,\n participants,\n createdAt: now,\n lastMessageAt: now,\n parentTopicId: sourceTopicId,\n isFork: copyHistory,\n ...(subagent ? { isSubagent: true } : {}),\n visibility: topic.visibility,\n accessMode: topic.accessMode,\n };\n\n let sessionId: string | undefined;\n let rollbackHandle: ForkHandle | undefined;\n\n try {\n if (agent) {\n const cwd = resolveTopicWorkspaceDir(derived.id);\n mkdirSync(cwd, { recursive: true });\n const registry = getRegistry(agent);\n const requestedRolloutModel =\n subagent?.model ??\n (subagent?.agent ? undefined : sourceConfig?.model) ??\n derived.defaultModel;\n const rolloutModel = resolveModelForAgent(agent, requestedRolloutModel, registry);\n const requestedRolloutEffort = subagent?.agent\n ? registry.defaultEffort\n : (sourceConfig?.effort ?? derived.defaultEffort);\n const rolloutEffort =\n requestedRolloutEffort && registry.validateEffort(requestedRolloutEffort)\n ? requestedRolloutEffort\n : registry.defaultEffort;\n\n if (copyHistory) {\n const parentSessionId = getTopicSessionId(sourceTopicId);\n if (parentSessionId) {\n try {\n const fork = await forkAgentSession({\n agent,\n parentSessionId,\n cwd,\n userId,\n topicName: topic.title,\n title,\n model: rolloutModel,\n ...(rolloutEffort ? { effort: rolloutEffort } : {}),\n });\n sessionId = fork.forkId;\n rollbackHandle = fork;\n } catch (err) {\n logger.warn(\n { err, sourceTopicId, title: topic.title, agent, parentSessionId },\n \"createDerivedTopic: native fork failed; synthesizing from unified history\",\n );\n }\n }\n if (!sessionId) {\n const rollout = registry.writeRollout({\n cwd,\n entries: readConversation(userId, topic.title),\n model: rolloutModel,\n ...(rolloutEffort ? { effort: rolloutEffort } : {}),\n });\n sessionId = rollout.sessionId;\n rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);\n }\n } else {\n const rollout = registry.writeRollout({\n cwd,\n entries: [],\n model: rolloutModel,\n ...(rolloutEffort ? { effort: rolloutEffort } : {}),\n });\n sessionId = rollout.sessionId;\n rollbackHandle = rollbackHandleFor(agent, rollout.sessionId, rollout.rolloutPath);\n }\n }\n\n const created = db\n .transaction(() => {\n const currentSource = getTopic(sourceTopicId);\n if (\n !currentSource ||\n currentSource.kind === \"manager\" ||\n !isParticipant(currentSource, userId)\n ) {\n throw new TopicDeriveBusyError(\"Source topic changed while deriving; try again\");\n }\n const transactionalConflict = findTopicTitleConflict(title, kind);\n if (transactionalConflict) throw new TopicTitleConflictError(title);\n upsertTopic(derived);\n if (subagent) {\n // Subagent worker rooms do NOT cascade the parent's optional MCP\n // whitelist — they start with the default (required-only) MCP set,\n // mirroring clawgram's subagent policy. An explicit agent/model\n // override must also beat the parent's config override, which\n // startAiTurn resolves ahead of the topic defaults; dropping the\n // inherited model when only the agent changes avoids pinning the\n // child to a model that belongs to the parent's agent.\n const childConfig = { ...(sourceConfig ?? {}) };\n delete childConfig.mcp;\n if (subagent.agent) {\n delete childConfig.model;\n }\n if (subagent.model) childConfig.model = subagent.model;\n if (Object.keys(childConfig).length > 0) setApiTopicConfig(derived.id, childConfig);\n } else if (sourceConfig) {\n setApiTopicConfig(derived.id, sourceConfig);\n }\n if (copyHistory) {\n copyMessagesForTopic(sourceTopicId, derived.id);\n cloneConversationLog({\n userId,\n srcTopic: topic.title,\n dstTopic: derived.title,\n });\n }\n if (sessionId && agent) {\n setTopicSessionId(derived.id, sessionId, {\n reason: copyHistory ? \"slash-fork\" : subagent ? \"spawn-subagent\" : \"slash-spawn\",\n agent,\n });\n appendConversationEventStrict(userId, derived.title, agent, {\n type: \"session\",\n sessionId,\n });\n }\n return derived;\n })\n .immediate();\n\n // Derived rooms inherit the browser profile only when the creator already\n // owns the source. Crossing an owner boundary always starts with a fresh\n // profile so cookies and login state cannot be transferred by a member.\n try {\n const profileClone = await cloneProfileForChild({\n userId,\n srcTopic: sourceTopicId,\n dstTopic: derived.id,\n });\n logger.info(\n {\n sourceTopicId,\n derivedTopicId: derived.id,\n copyHistory,\n subagent: Boolean(subagent),\n profileClone,\n },\n \"createDerivedTopic: playwright profile clone\",\n );\n } catch (profileErr) {\n logger.warn(\n { err: profileErr, sourceTopicId, derivedTopicId: derived.id },\n \"createDerivedTopic: playwright profile clone threw (continuing)\",\n );\n }\n\n // Every derived room (fork/spawn/subagent) is user-visible: tell all\n // inherited participants, not just the client that issued the request.\n WsHub.get().broadcastTopicCreated(created);\n return created;\n } catch (err) {\n if (rollbackHandle) cleanupAgentFork(rollbackHandle);\n if (err instanceof TopicDeriveBusyError || err instanceof TopicTitleConflictError) throw err;\n logger.warn(\n { err, sourceTopicId, derivedTopicId: derived.id, copyHistory },\n \"createDerivedTopic: failed to create derived topic\",\n );\n return null;\n }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "DBAB5ABB3DF8FA7264756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|