@ganglion/xacpx-relay 0.9.18 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +207 -20
- package/dist/maintenance.d.ts +2 -1
- package/dist/relay-web/assets/DashboardView-EVK-kyc5.js +430 -0
- package/dist/relay-web/assets/InviteView-D6_fuQ6u.js +1 -0
- package/dist/relay-web/assets/InviteView-hJ3Eqm6F.css +1 -0
- package/dist/relay-web/assets/{LoginView-C6CwOivl.js → LoginView-B270qavX.js} +1 -1
- package/dist/relay-web/assets/{SettingsView-BiPxFqyx.js → SettingsView-04bWB9fl.js} +2 -2
- package/dist/relay-web/assets/{abnfDiagram-VRR7QNED-D3eynLH4.js → abnfDiagram-VRR7QNED-Cv28PqIA.js} +1 -1
- package/dist/relay-web/assets/{arc-DUs51LXM.js → arc-B3Mx49EP.js} +1 -1
- package/dist/relay-web/assets/{architectureDiagram-ZJ3FMSHR-CnBlByS3.js → architectureDiagram-ZJ3FMSHR-CK-Lsk_U.js} +1 -1
- package/dist/relay-web/assets/{blockDiagram-677ZJIJ3-CVXJAHk4.js → blockDiagram-677ZJIJ3-C5aBMzqI.js} +1 -1
- package/dist/relay-web/assets/{c4Diagram-LMCZKHZV-JM82ZYu8.js → c4Diagram-LMCZKHZV-BlQI5wLu.js} +1 -1
- package/dist/relay-web/assets/channel-1MbVMhlH.js +1 -0
- package/dist/relay-web/assets/{chunk-2Q5K7J3B-drrGPFvX.js → chunk-2Q5K7J3B-CrCOkSE9.js} +1 -1
- package/dist/relay-web/assets/{chunk-32BRIVSS-B9yf1_gn.js → chunk-32BRIVSS-Bd6OcIKX.js} +1 -1
- package/dist/relay-web/assets/{chunk-5VM5RSS4-Do9WVyKQ.js → chunk-5VM5RSS4-CrBYK6bf.js} +1 -1
- package/dist/relay-web/assets/{chunk-EX3LRPZG-DDzz-ACk.js → chunk-EX3LRPZG-CfQdQUT8.js} +1 -1
- package/dist/relay-web/assets/{chunk-JWPE2WC7-UI6iUoHI.js → chunk-JWPE2WC7-3IOarpnO.js} +1 -1
- package/dist/relay-web/assets/{chunk-MOJQB5TN-ByIrBez3.js → chunk-MOJQB5TN-DtdyRAam.js} +1 -1
- package/dist/relay-web/assets/{chunk-RYQCIY6F-6y_9H1Ib.js → chunk-RYQCIY6F-BMymW-Ns.js} +1 -1
- package/dist/relay-web/assets/{chunk-V7JOEXUC-CqNKiWQ8.js → chunk-V7JOEXUC-Bm52mzv8.js} +1 -1
- package/dist/relay-web/assets/{chunk-VR4S4FIN-C2pH6FSw.js → chunk-VR4S4FIN-C7QLObJe.js} +1 -1
- package/dist/relay-web/assets/{chunk-XXDRQBXY-DFAqoBna.js → chunk-XXDRQBXY-DdwwHyIL.js} +1 -1
- package/dist/relay-web/assets/classDiagram-OUVF2IWQ-BcNmbj1M.js +1 -0
- package/dist/relay-web/assets/classDiagram-v2-EOCWNBFH-BcNmbj1M.js +1 -0
- package/dist/relay-web/assets/{cose-bilkent-JH36ORCC-Cpf6uf3d.js → cose-bilkent-JH36ORCC-PpMhQH69.js} +1 -1
- package/dist/relay-web/assets/{cynefin-VYW2F7L2-IXQUEi0W.js → cynefin-VYW2F7L2-CUBqRnnQ.js} +1 -1
- package/dist/relay-web/assets/{cynefinDiagram-TSTJHNR4-BC9f1Fi3.js → cynefinDiagram-TSTJHNR4-BC_TJ62y.js} +1 -1
- package/dist/relay-web/assets/{dagre-VKFMJZFB-BxXN216n.js → dagre-VKFMJZFB-B_of3-kV.js} +1 -1
- package/dist/relay-web/assets/{diagram-FQU43EPY-CkmHSEIt.js → diagram-FQU43EPY-DwufAj8L.js} +1 -1
- package/dist/relay-web/assets/{diagram-G47NLZAW-CbIEa_OU.js → diagram-G47NLZAW-B1Wh2BCB.js} +1 -1
- package/dist/relay-web/assets/{diagram-NH7WQ7WH-4qbaTukk.js → diagram-NH7WQ7WH-B8wmK-4H.js} +1 -1
- package/dist/relay-web/assets/{diagram-OA4YK3LP-yeg3CVfp.js → diagram-OA4YK3LP-DsSFS7pG.js} +1 -1
- package/dist/relay-web/assets/{diagram-WEI45ONY-Dgsr9As5.js → diagram-WEI45ONY-Dzxv-wo4.js} +1 -1
- package/dist/relay-web/assets/{ebnfDiagram-CCIWWBDH-CFtS6ZSp.js → ebnfDiagram-CCIWWBDH-D5pQGrHB.js} +1 -1
- package/dist/relay-web/assets/{erDiagram-Q63AITRT-MppHd6Ui.js → erDiagram-Q63AITRT-CKrm8sAE.js} +1 -1
- package/dist/relay-web/assets/{flowDiagram-23GEKE2U-DHKJxooJ.js → flowDiagram-23GEKE2U-qB2FERkS.js} +1 -1
- package/dist/relay-web/assets/{ganttDiagram-NO4QXBWP-B6MraPUE.js → ganttDiagram-NO4QXBWP-CQU1ZcEC.js} +1 -1
- package/dist/relay-web/assets/{ghostty-web-KHKgWovV.js → ghostty-web-Cib0xsSw.js} +1 -1
- package/dist/relay-web/assets/{gitGraphDiagram-IHSO6WYX-B61DehtB.js → gitGraphDiagram-IHSO6WYX-d6OQNr9X.js} +1 -1
- package/dist/relay-web/assets/index-D6SM2DoZ.js +98 -0
- package/dist/relay-web/assets/{index-DbQzlB4Y.css → index-DtbxKCKB.css} +1 -1
- package/dist/relay-web/assets/{infoDiagram-FWYZ7A6U-B7LgrKBn.js → infoDiagram-FWYZ7A6U-BgH2uoid.js} +1 -1
- package/dist/relay-web/assets/{ishikawaDiagram-FXEZZL3T-Bt1aT3yu.js → ishikawaDiagram-FXEZZL3T-DzEM5nGH.js} +1 -1
- package/dist/relay-web/assets/{journeyDiagram-5HDEW3XC-Cze-GQu3.js → journeyDiagram-5HDEW3XC-DITafq2J.js} +1 -1
- package/dist/relay-web/assets/{kanban-definition-HUTT4EX6-Dv5rVbgW.js → kanban-definition-HUTT4EX6-D-tHpErm.js} +1 -1
- package/dist/relay-web/assets/{linear-BpCIiHGU.js → linear-BYtx3Cpi.js} +1 -1
- package/dist/relay-web/assets/{mermaid.core-hW6nrk5d.js → mermaid.core-BK5xScER.js} +4 -4
- package/dist/relay-web/assets/{mindmap-definition-LN4V7U3C-CR9uIKLT.js → mindmap-definition-LN4V7U3C-BL-IGWZe.js} +1 -1
- package/dist/relay-web/assets/{pegDiagram-2B236MQR-BBmEWqWF.js → pegDiagram-2B236MQR-cfZauhKw.js} +1 -1
- package/dist/relay-web/assets/{pieDiagram-ENE6RG2P-BE3C-3Pw.js → pieDiagram-ENE6RG2P-CNfeQ62O.js} +1 -1
- package/dist/relay-web/assets/{quadrantDiagram-ABIIQ3AL-CvZX0jzO.js → quadrantDiagram-ABIIQ3AL-BFB6jOnt.js} +1 -1
- package/dist/relay-web/assets/{railroadDiagram-RFXS5EU6-CwOncWTj.js → railroadDiagram-RFXS5EU6-Cln7vzNZ.js} +1 -1
- package/dist/relay-web/assets/{requirementDiagram-TGXJPOKE-D81d73C3.js → requirementDiagram-TGXJPOKE-B5_FNmJB.js} +1 -1
- package/dist/relay-web/assets/{sankeyDiagram-HTMAVEWB-DcKcptSO.js → sankeyDiagram-HTMAVEWB-BZ1RWUL6.js} +1 -1
- package/dist/relay-web/assets/{sequenceDiagram-DBY2YBRQ-BLA_BsI9.js → sequenceDiagram-DBY2YBRQ-7IBw-dCX.js} +1 -1
- package/dist/relay-web/assets/{sizeCapture-X5ZJPWSS-Bf1kuTrb.js → sizeCapture-X5ZJPWSS-DcX5jLpS.js} +1 -1
- package/dist/relay-web/assets/{stateDiagram-2N3HPSRC-DwitPAKb.js → stateDiagram-2N3HPSRC-mqNdl_tU.js} +1 -1
- package/dist/relay-web/assets/stateDiagram-v2-6OUMAXLB-8SM3ysz0.js +1 -0
- package/dist/relay-web/assets/{swimlanes-5IMT3BWC-Cu2lv2-l.js → swimlanes-5IMT3BWC-B_zIEJ-q.js} +2 -2
- package/dist/relay-web/assets/swimlanesDiagram-G3AALYLV-BD1iVVgC.js +8 -0
- package/dist/relay-web/assets/{theme-tJlCLrde.js → theme-D3iqo32T.js} +1 -1
- package/dist/relay-web/assets/{timeline-definition-FHXFAJF6-3TdgbJ9h.js → timeline-definition-FHXFAJF6-DtIH7yoe.js} +1 -1
- package/dist/relay-web/assets/{vennDiagram-L72KCM5P-CGzOG85H.js → vennDiagram-L72KCM5P-BWW7oGjc.js} +1 -1
- package/dist/relay-web/assets/{wardleyDiagram-EHGQE667-Djz14Pom.js → wardleyDiagram-EHGQE667-Oj3I-rxo.js} +1 -1
- package/dist/relay-web/assets/{xychartDiagram-FW5EYKEG-ByN2Qux6.js → xychartDiagram-FW5EYKEG-BB8IDwkp.js} +1 -1
- package/dist/relay-web/index.html +2 -2
- package/dist/relay-web/sw.js +1 -1
- package/dist/stores/accounts.d.ts +36 -0
- package/package.json +2 -2
- package/dist/relay-web/assets/DashboardView-3omD0mEP.js +0 -419
- package/dist/relay-web/assets/channel-Y_xckp_8.js +0 -1
- package/dist/relay-web/assets/classDiagram-OUVF2IWQ-BMcCziZn.js +0 -1
- package/dist/relay-web/assets/classDiagram-v2-EOCWNBFH-BMcCziZn.js +0 -1
- package/dist/relay-web/assets/index-CMGeKM1T.js +0 -98
- package/dist/relay-web/assets/stateDiagram-v2-6OUMAXLB-K0Mc3sNE.js +0 -1
- package/dist/relay-web/assets/swimlanesDiagram-G3AALYLV-CF7Iyv4J.js +0 -8
package/dist/cli.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ export declare function defaultDbPath(): string;
|
|
|
14
14
|
* mis-resolve. The `cliJsPath` param exists only for tests.
|
|
15
15
|
*/
|
|
16
16
|
export declare function resolveBundledWebRoot(cliJsPath?: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Parses an invite TTL like "30m", "12h", "7d" into milliseconds.
|
|
19
|
+
* Returns null for malformed input; undefined input yields the 7-day default.
|
|
20
|
+
* Capped at 10 years — beyond that Date arithmetic overflows the ECMAScript
|
|
21
|
+
* time range and toISOString() throws instead of printing usage.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseTtlMs(raw: string | undefined): number | null;
|
|
17
24
|
export interface StartOptions {
|
|
18
25
|
dbPath: string;
|
|
19
26
|
httpPort: number;
|
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,15 @@ function initSchema(db) {
|
|
|
86
86
|
expires_at TEXT NOT NULL,
|
|
87
87
|
used_at TEXT
|
|
88
88
|
);
|
|
89
|
+
CREATE TABLE IF NOT EXISTS invite_codes (
|
|
90
|
+
id TEXT PRIMARY KEY,
|
|
91
|
+
code_hash TEXT NOT NULL UNIQUE,
|
|
92
|
+
label TEXT,
|
|
93
|
+
created_at TEXT NOT NULL,
|
|
94
|
+
expires_at TEXT NOT NULL,
|
|
95
|
+
used_at TEXT,
|
|
96
|
+
used_account_id TEXT REFERENCES accounts(id)
|
|
97
|
+
);
|
|
89
98
|
CREATE TABLE IF NOT EXISTS instances (
|
|
90
99
|
id TEXT PRIMARY KEY,
|
|
91
100
|
account_id TEXT NOT NULL REFERENCES accounts(id),
|
|
@@ -276,6 +285,71 @@ class AccountStore {
|
|
|
276
285
|
this.db.run("DELETE FROM web_sessions WHERE expires_at <= ?", [iso]);
|
|
277
286
|
return ws?.n ?? 0;
|
|
278
287
|
}
|
|
288
|
+
issueInviteCode(label, ttlMs) {
|
|
289
|
+
const id = randomUUID();
|
|
290
|
+
const code = generateToken();
|
|
291
|
+
const nowMs = this.now().getTime();
|
|
292
|
+
const expiresAt = new Date(nowMs + ttlMs).toISOString();
|
|
293
|
+
this.db.run("INSERT INTO invite_codes (id, code_hash, label, created_at, expires_at) VALUES (?, ?, ?, ?, ?)", [id, hashToken(code), label ?? null, new Date(nowMs).toISOString(), expiresAt]);
|
|
294
|
+
return { id, code, expiresAt };
|
|
295
|
+
}
|
|
296
|
+
redeemInviteCode(code) {
|
|
297
|
+
const codeHash = hashToken(code);
|
|
298
|
+
const row = this.db.get("SELECT id, label, expires_at, used_at FROM invite_codes WHERE code_hash = ?", [codeHash]);
|
|
299
|
+
if (!row || row.used_at !== null || new Date(row.expires_at).getTime() <= this.now().getTime()) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const nowIso = this.now().toISOString();
|
|
303
|
+
const accountId = randomUUID();
|
|
304
|
+
const username = `u-${randomUUID()}`;
|
|
305
|
+
const loginTokenId = randomUUID();
|
|
306
|
+
const token = generateToken();
|
|
307
|
+
this.db.exec("BEGIN");
|
|
308
|
+
try {
|
|
309
|
+
this.db.run("UPDATE invite_codes SET used_at = ?, used_account_id = ? WHERE code_hash = ? AND used_at IS NULL", [nowIso, accountId, codeHash]);
|
|
310
|
+
this.db.run("INSERT INTO accounts (id, username, created_at) VALUES (?, ?, ?)", [accountId, username, nowIso]);
|
|
311
|
+
this.db.run("INSERT INTO login_tokens (id, token_hash, account_id, label, created_at, last_used_at) VALUES (?, ?, ?, ?, ?, NULL)", [loginTokenId, hashToken(token), accountId, row.label, nowIso]);
|
|
312
|
+
this.db.exec("COMMIT");
|
|
313
|
+
} catch (e) {
|
|
314
|
+
this.db.exec("ROLLBACK");
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
return { token, username, accountId, loginTokenId };
|
|
318
|
+
}
|
|
319
|
+
listInviteCodes() {
|
|
320
|
+
return this.db.all("SELECT id, label, created_at, expires_at, used_at FROM invite_codes ORDER BY created_at").map((row) => ({
|
|
321
|
+
id: row.id,
|
|
322
|
+
label: row.label,
|
|
323
|
+
createdAt: row.created_at,
|
|
324
|
+
expiresAt: row.expires_at,
|
|
325
|
+
usedAt: row.used_at
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
inviteIdFor(valueOrId) {
|
|
329
|
+
const byValue = this.db.get("SELECT id FROM invite_codes WHERE code_hash = ?", [hashToken(valueOrId)]);
|
|
330
|
+
if (byValue)
|
|
331
|
+
return byValue.id;
|
|
332
|
+
const byId = this.db.get("SELECT id FROM invite_codes WHERE id = ?", [valueOrId]);
|
|
333
|
+
if (byId)
|
|
334
|
+
return byId.id;
|
|
335
|
+
const byPrefix = this.db.all("SELECT id FROM invite_codes WHERE id LIKE ? || '%'", [valueOrId]);
|
|
336
|
+
if (byPrefix.length === 1)
|
|
337
|
+
return byPrefix[0].id;
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
removeInviteCode(id) {
|
|
341
|
+
const existing = this.db.get("SELECT id FROM invite_codes WHERE id = ?", [id]);
|
|
342
|
+
if (!existing)
|
|
343
|
+
return false;
|
|
344
|
+
this.db.run("DELETE FROM invite_codes WHERE id = ?", [id]);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
pruneInviteCodes(now) {
|
|
348
|
+
const iso = now.toISOString();
|
|
349
|
+
const row = this.db.get("SELECT COUNT(*) AS n FROM invite_codes WHERE expires_at <= ? OR used_at IS NOT NULL", [iso]);
|
|
350
|
+
this.db.run("DELETE FROM invite_codes WHERE expires_at <= ? OR used_at IS NOT NULL", [iso]);
|
|
351
|
+
return row?.n ?? 0;
|
|
352
|
+
}
|
|
279
353
|
}
|
|
280
354
|
|
|
281
355
|
// packages/relay/src/stores/instances.ts
|
|
@@ -1009,13 +1083,16 @@ var MAX_ATTACHMENT_FIELD_LEN = 256;
|
|
|
1009
1083
|
function boundField(v) {
|
|
1010
1084
|
return typeof v === "string" ? v.slice(0, MAX_ATTACHMENT_FIELD_LEN) : v;
|
|
1011
1085
|
}
|
|
1012
|
-
function
|
|
1086
|
+
function rpcSessionLocks(type, payload) {
|
|
1013
1087
|
const value = payload;
|
|
1014
1088
|
if (type === MSG3.prompt || type === MSG3.commandExecute) {
|
|
1015
|
-
return typeof value.sessionAlias === "string" && value.sessionAlias ? value.sessionAlias : undefined;
|
|
1089
|
+
return typeof value.sessionAlias === "string" && value.sessionAlias ? { alias: value.sessionAlias, lifecycle: false, turn: true } : undefined;
|
|
1090
|
+
}
|
|
1091
|
+
if (type === MSG3.sessionsRename) {
|
|
1092
|
+
return typeof value.alias === "string" && value.alias ? { alias: value.alias, lifecycle: true, turn: false } : undefined;
|
|
1016
1093
|
}
|
|
1017
|
-
if (type === MSG3.sessionsCreate || type === MSG3.sessionsRemove || type === MSG3.sessionsArchive || type === MSG3.sessionsUnarchive
|
|
1018
|
-
return typeof value.alias === "string" && value.alias ? value.alias : undefined;
|
|
1094
|
+
if (type === MSG3.sessionsCreate || type === MSG3.sessionsRemove || type === MSG3.sessionsArchive || type === MSG3.sessionsUnarchive) {
|
|
1095
|
+
return typeof value.alias === "string" && value.alias ? { alias: value.alias, lifecycle: true, turn: true } : undefined;
|
|
1019
1096
|
}
|
|
1020
1097
|
return;
|
|
1021
1098
|
}
|
|
@@ -1046,7 +1123,8 @@ function createApp(deps) {
|
|
|
1046
1123
|
const pairingTtlMs = deps.pairingTtlMs ?? 10 * 60 * 1000;
|
|
1047
1124
|
const trustProxy = deps.trustProxy ?? false;
|
|
1048
1125
|
const now = deps.now ?? (() => new Date);
|
|
1049
|
-
const
|
|
1126
|
+
const acquireSessionLifecycleRpcLock = createKeyedRpcLock();
|
|
1127
|
+
const acquireSessionTurnRpcLock = createKeyedRpcLock();
|
|
1050
1128
|
const loginFailures = new Map;
|
|
1051
1129
|
let globalFailures = { count: 0, windowStart: 0 };
|
|
1052
1130
|
function sweepPerIpLoginFailures(nowMs) {
|
|
@@ -1111,8 +1189,28 @@ function createApp(deps) {
|
|
|
1111
1189
|
});
|
|
1112
1190
|
app.post("/api/register", (c) => c.json({ error: "not-found" }, 404));
|
|
1113
1191
|
app.post("/api/invites", (c) => c.json({ error: "not-found" }, 404));
|
|
1192
|
+
app.post("/api/invites/redeem", async (c) => {
|
|
1193
|
+
if (!requireJson(c.req.header("content-type")))
|
|
1194
|
+
return c.json({ error: "unsupported-media-type" }, 415);
|
|
1195
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1196
|
+
const nowMs = now().getTime();
|
|
1197
|
+
sweepPerIpLoginFailures(nowMs);
|
|
1198
|
+
const ip = clientIp(c, trustProxy);
|
|
1199
|
+
if (isRateLimited(ip, nowMs)) {
|
|
1200
|
+
deps.logger?.info("relay.invite.rejected", "invite redeem rejected", { reason: "rate-limited", ip });
|
|
1201
|
+
return c.json({ error: "too-many-attempts" }, 429);
|
|
1202
|
+
}
|
|
1203
|
+
const r = deps.accounts.redeemInviteCode(typeof body.code === "string" ? body.code : "");
|
|
1204
|
+
if (!r) {
|
|
1205
|
+
recordFailure(ip, nowMs);
|
|
1206
|
+
deps.logger?.info("relay.invite.rejected", "invite redeem rejected", { reason: "invalid-code", ip });
|
|
1207
|
+
return c.json({ error: "invalid-code" }, 401);
|
|
1208
|
+
}
|
|
1209
|
+
deps.logger?.info("relay.invite.redeemed", "invite redeemed", { accountId: r.accountId });
|
|
1210
|
+
return c.json({ token: r.token, username: r.username });
|
|
1211
|
+
});
|
|
1114
1212
|
app.use("/api/*", async (c, next) => {
|
|
1115
|
-
if (c.req.path === "/api/login")
|
|
1213
|
+
if (c.req.path === "/api/login" || c.req.path === "/api/invites/redeem")
|
|
1116
1214
|
return next();
|
|
1117
1215
|
const token = getCookie(c, SESSION_COOKIE);
|
|
1118
1216
|
const account = token ? deps.accounts.getSessionAccount(token) : null;
|
|
@@ -1229,7 +1327,7 @@ function createApp(deps) {
|
|
|
1229
1327
|
isOwner: true
|
|
1230
1328
|
};
|
|
1231
1329
|
}
|
|
1232
|
-
|
|
1330
|
+
const releaseSessionRpcLocks = [];
|
|
1233
1331
|
let persistedPromptId;
|
|
1234
1332
|
try {
|
|
1235
1333
|
if (body.type === MSG3.prompt && !parseControlPayload(MSG3.prompt, payload)) {
|
|
@@ -1251,9 +1349,15 @@ function createApp(deps) {
|
|
|
1251
1349
|
if (approxBytes > 10 * 1024 * 1024)
|
|
1252
1350
|
return c.json({ error: "file-too-large" }, 413);
|
|
1253
1351
|
}
|
|
1254
|
-
const
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
1352
|
+
const sessionLocks = rpcSessionLocks(body.type, payload);
|
|
1353
|
+
if (sessionLocks) {
|
|
1354
|
+
const key = `${instance.id}\x00${sessionLocks.alias}`;
|
|
1355
|
+
if (sessionLocks.lifecycle) {
|
|
1356
|
+
releaseSessionRpcLocks.push(await acquireSessionLifecycleRpcLock(key));
|
|
1357
|
+
}
|
|
1358
|
+
if (sessionLocks.turn) {
|
|
1359
|
+
releaseSessionRpcLocks.push(await acquireSessionTurnRpcLock(key));
|
|
1360
|
+
}
|
|
1257
1361
|
}
|
|
1258
1362
|
if (body.type === MSG3.prompt || body.type === MSG3.commandExecute) {
|
|
1259
1363
|
const p = payload;
|
|
@@ -1299,7 +1403,9 @@ function createApp(deps) {
|
|
|
1299
1403
|
return c.json({ error: message }, 504);
|
|
1300
1404
|
return c.json({ error: message }, 500);
|
|
1301
1405
|
} finally {
|
|
1302
|
-
|
|
1406
|
+
for (let i = releaseSessionRpcLocks.length - 1;i >= 0; i--) {
|
|
1407
|
+
releaseSessionRpcLocks[i]?.();
|
|
1408
|
+
}
|
|
1303
1409
|
}
|
|
1304
1410
|
});
|
|
1305
1411
|
if (deps.webRoot) {
|
|
@@ -1329,7 +1435,8 @@ function runMaintenance(stores, opts) {
|
|
|
1329
1435
|
});
|
|
1330
1436
|
const sessionsDeleted = stores.accounts.pruneExpired(now);
|
|
1331
1437
|
const pairingTokensDeleted = stores.instances.prunePairingTokens(now);
|
|
1332
|
-
|
|
1438
|
+
const inviteCodesDeleted = stores.accounts.pruneInviteCodes(now);
|
|
1439
|
+
return { messagesDeleted, sessionsDeleted, pairingTokensDeleted, inviteCodesDeleted };
|
|
1333
1440
|
}
|
|
1334
1441
|
function startMaintenanceLoop(stores, opts, intervalMs, onError) {
|
|
1335
1442
|
const tick = () => {
|
|
@@ -1721,8 +1828,10 @@ var USAGE = [
|
|
|
1721
1828
|
" start [--db <path>] [--web-root <dir>] [--host 0.0.0.0] [--http-port 8787] [--ws-port <n>] [--history-retention-days 30] [--request-timeout-ms 120000] [--trust-proxy]",
|
|
1722
1829
|
" (--ws-port omitted = gateway merged onto the HTTP port; pass it only for a dedicated gateway port)",
|
|
1723
1830
|
" add token [--label <note>] [--db <path>]",
|
|
1831
|
+
" add invite [--label <note>] [--ttl <n>{m|h|d}] [--url <base>] [--db <path>]",
|
|
1724
1832
|
" ls [--db <path>]",
|
|
1725
1833
|
" rm token <value-or-id> [--db <path>]",
|
|
1834
|
+
" rm invite <code-or-id> [--db <path>]",
|
|
1726
1835
|
" update [--check] (self-update @ganglion/xacpx-relay; --check only reports)",
|
|
1727
1836
|
"",
|
|
1728
1837
|
" Defaults: --db ~/.xacpx-relay/relay.db --web-root auto-detects the bundled dashboard"
|
|
@@ -1736,6 +1845,20 @@ function flag(args, name) {
|
|
|
1736
1845
|
function hasFlag(args, name) {
|
|
1737
1846
|
return args.includes(name);
|
|
1738
1847
|
}
|
|
1848
|
+
function parseTtlMs(raw) {
|
|
1849
|
+
if (raw === undefined)
|
|
1850
|
+
return 7 * 24 * 60 * 60 * 1000;
|
|
1851
|
+
const match = /^(\d+)([mhd])$/.exec(raw);
|
|
1852
|
+
if (!match)
|
|
1853
|
+
return null;
|
|
1854
|
+
const n = Number(match[1]);
|
|
1855
|
+
if (n <= 0)
|
|
1856
|
+
return null;
|
|
1857
|
+
const unit = match[2] === "m" ? 60000 : match[2] === "h" ? 3600000 : 86400000;
|
|
1858
|
+
const ms = n * unit;
|
|
1859
|
+
const maxMs = 10 * 365 * 86400000;
|
|
1860
|
+
return ms > maxMs ? null : ms;
|
|
1861
|
+
}
|
|
1739
1862
|
function parseStartOptions(args) {
|
|
1740
1863
|
const dbPath = flag(args, "--db") ?? defaultDbPath();
|
|
1741
1864
|
const retentionRaw = flag(args, "--history-retention-days");
|
|
@@ -1793,21 +1916,64 @@ async function runRelayCli(args, io) {
|
|
|
1793
1916
|
runtime.close();
|
|
1794
1917
|
}
|
|
1795
1918
|
}
|
|
1919
|
+
if (args[0] === "add" && args[1] === "invite") {
|
|
1920
|
+
const label = flag(args, "--label");
|
|
1921
|
+
const ttlRaw = flag(args, "--ttl");
|
|
1922
|
+
if (hasFlag(args, "--ttl") && ttlRaw === undefined) {
|
|
1923
|
+
io.print(USAGE);
|
|
1924
|
+
return 1;
|
|
1925
|
+
}
|
|
1926
|
+
const ttlMs = parseTtlMs(ttlRaw);
|
|
1927
|
+
if (ttlMs === null) {
|
|
1928
|
+
io.print(USAGE);
|
|
1929
|
+
return 1;
|
|
1930
|
+
}
|
|
1931
|
+
const runtime = await createRelayRuntime(dbPath);
|
|
1932
|
+
try {
|
|
1933
|
+
const { code, expiresAt } = runtime.accounts.issueInviteCode(label, ttlMs);
|
|
1934
|
+
io.print(`invite code: ${code}`);
|
|
1935
|
+
const baseUrl = flag(args, "--url");
|
|
1936
|
+
if (baseUrl) {
|
|
1937
|
+
io.print(`redeem link: ${baseUrl.replace(/\/+$/, "")}/invite/${code}`);
|
|
1938
|
+
} else {
|
|
1939
|
+
io.print(`redeem path: /invite/${code} (append to your hub URL)`);
|
|
1940
|
+
}
|
|
1941
|
+
io.print(`(share it now — single-use, not shown again; expires ${expiresAt.slice(0, 16).replace("T", " ")} UTC)`);
|
|
1942
|
+
return 0;
|
|
1943
|
+
} finally {
|
|
1944
|
+
runtime.close();
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1796
1947
|
if (args[0] === "ls") {
|
|
1797
1948
|
const runtime = await createRelayRuntime(dbPath);
|
|
1798
1949
|
try {
|
|
1799
1950
|
const tokens = runtime.accounts.listTokens();
|
|
1800
1951
|
if (tokens.length === 0) {
|
|
1801
1952
|
io.print("(no tokens)");
|
|
1802
|
-
|
|
1953
|
+
} else {
|
|
1954
|
+
io.print("id label created #instances");
|
|
1955
|
+
io.print("-------- -------------------- -------------------- ----------");
|
|
1956
|
+
for (const t of tokens) {
|
|
1957
|
+
const shortId = t.id.slice(0, 8);
|
|
1958
|
+
const label = (t.label ?? "").slice(0, 20).padEnd(20);
|
|
1959
|
+
const created = t.createdAt.slice(0, 19).replace("T", " ");
|
|
1960
|
+
io.print(`${shortId} ${label} ${created} ${String(t.instanceCount).padStart(10)}`);
|
|
1961
|
+
}
|
|
1803
1962
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
io.print(
|
|
1963
|
+
const invites = runtime.accounts.listInviteCodes();
|
|
1964
|
+
if (invites.length > 0) {
|
|
1965
|
+
const nowMs = Date.now();
|
|
1966
|
+
io.print("");
|
|
1967
|
+
io.print("invites");
|
|
1968
|
+
io.print("id label expires (UTC) status");
|
|
1969
|
+
io.print("-------- -------------------- -------------------- -------");
|
|
1970
|
+
for (const inv of invites) {
|
|
1971
|
+
const shortId = inv.id.slice(0, 8);
|
|
1972
|
+
const label = (inv.label ?? "").slice(0, 20).padEnd(20);
|
|
1973
|
+
const expires = inv.expiresAt.slice(0, 19).replace("T", " ");
|
|
1974
|
+
const status = inv.usedAt !== null ? "used" : new Date(inv.expiresAt).getTime() <= nowMs ? "expired" : "unused";
|
|
1975
|
+
io.print(`${shortId} ${label} ${expires} ${status}`);
|
|
1976
|
+
}
|
|
1811
1977
|
}
|
|
1812
1978
|
return 0;
|
|
1813
1979
|
} finally {
|
|
@@ -1834,6 +2000,26 @@ async function runRelayCli(args, io) {
|
|
|
1834
2000
|
runtime.close();
|
|
1835
2001
|
}
|
|
1836
2002
|
}
|
|
2003
|
+
if (args[0] === "rm" && args[1] === "invite") {
|
|
2004
|
+
const valueOrId = args[2];
|
|
2005
|
+
if (!valueOrId || valueOrId.startsWith("--")) {
|
|
2006
|
+
io.print(USAGE);
|
|
2007
|
+
return 1;
|
|
2008
|
+
}
|
|
2009
|
+
const runtime = await createRelayRuntime(dbPath);
|
|
2010
|
+
try {
|
|
2011
|
+
const inviteId = runtime.accounts.inviteIdFor(valueOrId);
|
|
2012
|
+
if (!inviteId) {
|
|
2013
|
+
io.print(`invite not found: ${valueOrId}`);
|
|
2014
|
+
return 1;
|
|
2015
|
+
}
|
|
2016
|
+
runtime.accounts.removeInviteCode(inviteId);
|
|
2017
|
+
io.print("removed");
|
|
2018
|
+
return 0;
|
|
2019
|
+
} finally {
|
|
2020
|
+
runtime.close();
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
1837
2023
|
if (args[0] === "update") {
|
|
1838
2024
|
return await handleRelayUpdate(args.slice(1), { print: io.print });
|
|
1839
2025
|
}
|
|
@@ -1849,6 +2035,7 @@ if (__require.main == __require.module) {
|
|
|
1849
2035
|
export {
|
|
1850
2036
|
runRelayCli,
|
|
1851
2037
|
resolveBundledWebRoot,
|
|
2038
|
+
parseTtlMs,
|
|
1852
2039
|
parseStartOptions,
|
|
1853
2040
|
defaultDbPath
|
|
1854
2041
|
};
|
package/dist/maintenance.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ export interface MaintenanceSummary {
|
|
|
15
15
|
messagesDeleted: number;
|
|
16
16
|
sessionsDeleted: number;
|
|
17
17
|
pairingTokensDeleted: number;
|
|
18
|
+
inviteCodesDeleted: number;
|
|
18
19
|
}
|
|
19
|
-
/** Runs one maintenance pass: prune old/excess messages, GC expired sessions/pairing tokens. */
|
|
20
|
+
/** Runs one maintenance pass: prune old/excess messages, GC expired sessions/pairing tokens/invite codes. */
|
|
20
21
|
export declare function runMaintenance(stores: MaintenanceStores, opts: MaintenanceOptions): MaintenanceSummary;
|
|
21
22
|
/** Starts a periodic maintenance loop. Returns a stop function. */
|
|
22
23
|
export declare function startMaintenanceLoop(stores: MaintenanceStores, opts: MaintenanceOptions, intervalMs: number, onError?: (err: unknown) => void): () => void;
|