latticesql 3.4.5 → 3.4.7
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.js +113 -6
- package/dist/index.cjs +100 -27
- package/dist/index.js +77 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10313,6 +10313,21 @@ var init_registry = __esm({
|
|
|
10313
10313
|
["table", "values"]
|
|
10314
10314
|
)
|
|
10315
10315
|
},
|
|
10316
|
+
{
|
|
10317
|
+
name: "create_secret",
|
|
10318
|
+
description: "Store a secret/credential \u2014 an API key, password, OAuth token, connection string, etc. \u2014 by name in the encrypted secrets store. Use this whenever the user gives you a credential to save or asks you to remember/store a secret. WRITE-ONLY: you can save a secret but you can NEVER read, list, echo, or retrieve existing secret values \u2014 they are hidden from you. The value is encrypted at rest.",
|
|
10319
|
+
mutates: true,
|
|
10320
|
+
category: "row",
|
|
10321
|
+
args: obj(
|
|
10322
|
+
{
|
|
10323
|
+
name: str('A short label for the secret, e.g. "GitHub password" or "OpenAI API key".'),
|
|
10324
|
+
value: str("The secret value to store."),
|
|
10325
|
+
kind: str('Optional kind, e.g. "password", "api_key", "token", "connection_string".'),
|
|
10326
|
+
description: str("Optional note about what the secret is for.")
|
|
10327
|
+
},
|
|
10328
|
+
["name", "value"]
|
|
10329
|
+
)
|
|
10330
|
+
},
|
|
10316
10331
|
{
|
|
10317
10332
|
name: "create_artifact",
|
|
10318
10333
|
description: "Create a markdown document and save it as a file artifact. Use this whenever the user asks you to create, write, draft, or make a document, note, write-up, summary, report, or file \u2014 you author the content as GitHub-flavored markdown and it is saved in the files entity as a markdown artifact, then opened in the viewer for them. Prefer this over create_row on files for any document the user wants to keep. It follows the same sharing rules as any file (private mode \u2192 private).",
|
|
@@ -13156,6 +13171,7 @@ var init_ingest_url = __esm({
|
|
|
13156
13171
|
});
|
|
13157
13172
|
|
|
13158
13173
|
// src/gui/ai/dispatch.ts
|
|
13174
|
+
import { randomUUID } from "crypto";
|
|
13159
13175
|
function visibilityDenialReason(opts) {
|
|
13160
13176
|
if (opts.kind === "table") {
|
|
13161
13177
|
return opts.canManageTableDefault ? null : "Only the workspace owner can change a table's default sharing.";
|
|
@@ -13371,6 +13387,21 @@ async function executeFunction(ctx, name, args) {
|
|
|
13371
13387
|
);
|
|
13372
13388
|
return { ok: true, result: { id } };
|
|
13373
13389
|
}
|
|
13390
|
+
case "create_secret": {
|
|
13391
|
+
const secretName = requireString(args.name, "name");
|
|
13392
|
+
const secretValue2 = requireString(args.value, "value");
|
|
13393
|
+
const kind = typeof args.kind === "string" && args.kind ? args.kind : null;
|
|
13394
|
+
const description = typeof args.description === "string" && args.description ? args.description : null;
|
|
13395
|
+
const id = randomUUID();
|
|
13396
|
+
await ctx.db.insert("secrets", {
|
|
13397
|
+
id,
|
|
13398
|
+
name: secretName,
|
|
13399
|
+
value: secretValue2,
|
|
13400
|
+
kind,
|
|
13401
|
+
description
|
|
13402
|
+
});
|
|
13403
|
+
return { ok: true, result: { id, name: secretName } };
|
|
13404
|
+
}
|
|
13374
13405
|
case "create_artifact": {
|
|
13375
13406
|
const table = requireTable("files", ctx.validTables);
|
|
13376
13407
|
const title = requireString(args.title, "title");
|
|
@@ -13701,6 +13732,7 @@ var init_dispatch = __esm({
|
|
|
13701
13732
|
"get_history",
|
|
13702
13733
|
"create_row",
|
|
13703
13734
|
"create_artifact",
|
|
13735
|
+
"create_secret",
|
|
13704
13736
|
"ingest_url",
|
|
13705
13737
|
"set_definition",
|
|
13706
13738
|
"set_visibility",
|
|
@@ -56049,9 +56081,41 @@ var appJs = `
|
|
|
56049
56081
|
}
|
|
56050
56082
|
function reloadForUpdate(label) {
|
|
56051
56083
|
if (reloadingForUpdate) return;
|
|
56084
|
+
// Guard against an infinite reload loop. Reloading when the server version
|
|
56085
|
+
// changes is the seamless-update trigger \u2014 but if the version the page was
|
|
56086
|
+
// SERVED with keeps disagreeing with /api/version (e.g. a stale or duplicate
|
|
56087
|
+
// server is holding the port and reporting a different version), every fresh
|
|
56088
|
+
// page would reload again. That unbounded loop pegs memory and crashes the
|
|
56089
|
+
// browser. Cap reloads to MAX within WINDOW; past that, stop and surface the
|
|
56090
|
+
// mismatch instead of spinning. A genuine update reloads once and then the
|
|
56091
|
+
// versions agree, which clears the counter (see checkServerVersion).
|
|
56092
|
+
var KEY = 'lattice:updateReloads',
|
|
56093
|
+
MAX = 3,
|
|
56094
|
+
WINDOW = 60000,
|
|
56095
|
+
now = Date.now(),
|
|
56096
|
+
recent = [];
|
|
56097
|
+
try {
|
|
56098
|
+
recent = (JSON.parse(sessionStorage.getItem(KEY) || '[]') || []).filter(function (t) {
|
|
56099
|
+
return now - t < WINDOW;
|
|
56100
|
+
});
|
|
56101
|
+
} catch (_) {
|
|
56102
|
+
/* sessionStorage blocked \u2014 degrade to a single best-effort reload below */
|
|
56103
|
+
}
|
|
56104
|
+
if (recent.length >= MAX) {
|
|
56105
|
+
showUpdatePill('Version mismatch \u2014 stopped auto-reloading. Reload manually if needed.');
|
|
56106
|
+
return;
|
|
56107
|
+
}
|
|
56108
|
+
recent.push(now);
|
|
56109
|
+
try {
|
|
56110
|
+
sessionStorage.setItem(KEY, JSON.stringify(recent));
|
|
56111
|
+
} catch (_) {
|
|
56112
|
+
/* best-effort */
|
|
56113
|
+
}
|
|
56052
56114
|
reloadingForUpdate = true;
|
|
56053
56115
|
showUpdatePill(label || 'Updated \u2014 reloading\u2026');
|
|
56054
|
-
setTimeout(function () {
|
|
56116
|
+
setTimeout(function () {
|
|
56117
|
+
location.reload();
|
|
56118
|
+
}, 600);
|
|
56055
56119
|
}
|
|
56056
56120
|
// Manual upgrade fallback: show an "Update available \u2014 Upgrade" link next to
|
|
56057
56121
|
// the version chip only when the server reports a newer, installable version.
|
|
@@ -56083,7 +56147,16 @@ var appJs = `
|
|
|
56083
56147
|
var v = d && d.version ? String(d.version).replace(/^v/, '').trim() : '';
|
|
56084
56148
|
if (!v) return;
|
|
56085
56149
|
if (v !== BOOT_VERSION) reloadForUpdate('Updated to v' + v + ' \u2014 reloading\u2026');
|
|
56086
|
-
else
|
|
56150
|
+
else {
|
|
56151
|
+
hideUpdatePill();
|
|
56152
|
+
// Versions agree \u2014 clear the reload-loop guard so a later genuine
|
|
56153
|
+
// update can reload again from a clean slate.
|
|
56154
|
+
try {
|
|
56155
|
+
sessionStorage.removeItem('lattice:updateReloads');
|
|
56156
|
+
} catch (_) {
|
|
56157
|
+
/* best-effort */
|
|
56158
|
+
}
|
|
56159
|
+
}
|
|
56087
56160
|
})
|
|
56088
56161
|
.catch(function () { /* offline / mid-restart \u2014 the next reconnect retries */ });
|
|
56089
56162
|
// Refresh the manual-upgrade link alongside the reconnect version check.
|
|
@@ -64852,7 +64925,7 @@ function redeemInviteToken(email, token) {
|
|
|
64852
64925
|
init_markdown();
|
|
64853
64926
|
init_rls();
|
|
64854
64927
|
init_adapter();
|
|
64855
|
-
import { randomUUID } from "crypto";
|
|
64928
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
64856
64929
|
|
|
64857
64930
|
// src/framework/cloud-migration.ts
|
|
64858
64931
|
init_lattice();
|
|
@@ -65647,7 +65720,7 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
65647
65720
|
`INSERT INTO "__lattice_member_invites" ("id","role","email_hash","email","expires_at")
|
|
65648
65721
|
VALUES (?, ?, ?, ?, ?)`,
|
|
65649
65722
|
[
|
|
65650
|
-
|
|
65723
|
+
randomUUID2(),
|
|
65651
65724
|
role,
|
|
65652
65725
|
emailHash,
|
|
65653
65726
|
// Plaintext email stored ONLY in this owner-only table so the owner's
|
|
@@ -69987,6 +70060,28 @@ ${e6.stack ?? ""}`
|
|
|
69987
70060
|
};
|
|
69988
70061
|
}
|
|
69989
70062
|
|
|
70063
|
+
// src/gui/probe-running.ts
|
|
70064
|
+
async function probeRunningGui(port, timeoutMs = 1e3) {
|
|
70065
|
+
const controller = new AbortController();
|
|
70066
|
+
const timer = setTimeout(() => {
|
|
70067
|
+
controller.abort();
|
|
70068
|
+
}, timeoutMs);
|
|
70069
|
+
try {
|
|
70070
|
+
const res = await fetch(`http://127.0.0.1:${String(port)}/api/version`, {
|
|
70071
|
+
signal: controller.signal
|
|
70072
|
+
});
|
|
70073
|
+
if (!res.ok) return null;
|
|
70074
|
+
const data = await res.json();
|
|
70075
|
+
if (typeof data !== "object" || data === null || !("version" in data)) return null;
|
|
70076
|
+
const v2 = data.version;
|
|
70077
|
+
return { version: typeof v2 === "string" ? v2 : "" };
|
|
70078
|
+
} catch {
|
|
70079
|
+
return null;
|
|
70080
|
+
} finally {
|
|
70081
|
+
clearTimeout(timer);
|
|
70082
|
+
}
|
|
70083
|
+
}
|
|
70084
|
+
|
|
69990
70085
|
// src/gui/supervisor.ts
|
|
69991
70086
|
import { spawn as spawn3 } from "child_process";
|
|
69992
70087
|
async function superviseGui(opts) {
|
|
@@ -70221,7 +70316,7 @@ function printHelp() {
|
|
|
70221
70316
|
);
|
|
70222
70317
|
}
|
|
70223
70318
|
function getVersion() {
|
|
70224
|
-
if (true) return "3.4.
|
|
70319
|
+
if (true) return "3.4.7";
|
|
70225
70320
|
try {
|
|
70226
70321
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
70227
70322
|
const pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
|
|
@@ -70418,6 +70513,18 @@ async function runWatch(args) {
|
|
|
70418
70513
|
process.on("SIGTERM", shutdown);
|
|
70419
70514
|
}
|
|
70420
70515
|
async function runGui(args) {
|
|
70516
|
+
const port = args.port;
|
|
70517
|
+
if (!process.env.LATTICE_GUI_SUPERVISED) {
|
|
70518
|
+
const running = await probeRunningGui(port);
|
|
70519
|
+
if (running) {
|
|
70520
|
+
const url = `http://127.0.0.1:${String(port)}/`;
|
|
70521
|
+
console.log(
|
|
70522
|
+
`Lattice is already running at ${url}${running.version ? ` (v${running.version})` : ""} \u2014 opening it.`
|
|
70523
|
+
);
|
|
70524
|
+
if (!args.noOpen) openUrl(url);
|
|
70525
|
+
return;
|
|
70526
|
+
}
|
|
70527
|
+
}
|
|
70421
70528
|
if (!process.env.LATTICE_GUI_SUPERVISED && detectInstallContext().installable) {
|
|
70422
70529
|
try {
|
|
70423
70530
|
await superviseGui({
|
|
@@ -70445,7 +70552,7 @@ async function runGui(args) {
|
|
|
70445
70552
|
configPath: boot.configPath,
|
|
70446
70553
|
outputDir: boot.contextDir,
|
|
70447
70554
|
latticeRoot: boot.root,
|
|
70448
|
-
port
|
|
70555
|
+
port,
|
|
70449
70556
|
openBrowser: !args.noOpen,
|
|
70450
70557
|
autoRender: true,
|
|
70451
70558
|
version: getVersion(),
|
package/dist/index.cjs
CHANGED
|
@@ -50606,6 +50606,21 @@ var init_registry = __esm({
|
|
|
50606
50606
|
["table", "values"]
|
|
50607
50607
|
)
|
|
50608
50608
|
},
|
|
50609
|
+
{
|
|
50610
|
+
name: "create_secret",
|
|
50611
|
+
description: "Store a secret/credential \u2014 an API key, password, OAuth token, connection string, etc. \u2014 by name in the encrypted secrets store. Use this whenever the user gives you a credential to save or asks you to remember/store a secret. WRITE-ONLY: you can save a secret but you can NEVER read, list, echo, or retrieve existing secret values \u2014 they are hidden from you. The value is encrypted at rest.",
|
|
50612
|
+
mutates: true,
|
|
50613
|
+
category: "row",
|
|
50614
|
+
args: obj(
|
|
50615
|
+
{
|
|
50616
|
+
name: str('A short label for the secret, e.g. "GitHub password" or "OpenAI API key".'),
|
|
50617
|
+
value: str("The secret value to store."),
|
|
50618
|
+
kind: str('Optional kind, e.g. "password", "api_key", "token", "connection_string".'),
|
|
50619
|
+
description: str("Optional note about what the secret is for.")
|
|
50620
|
+
},
|
|
50621
|
+
["name", "value"]
|
|
50622
|
+
)
|
|
50623
|
+
},
|
|
50609
50624
|
{
|
|
50610
50625
|
name: "create_artifact",
|
|
50611
50626
|
description: "Create a markdown document and save it as a file artifact. Use this whenever the user asks you to create, write, draft, or make a document, note, write-up, summary, report, or file \u2014 you author the content as GitHub-flavored markdown and it is saved in the files entity as a markdown artifact, then opened in the viewer for them. Prefer this over create_row on files for any document the user wants to keep. It follows the same sharing rules as any file (private mode \u2192 private).",
|
|
@@ -53028,6 +53043,21 @@ async function executeFunction(ctx, name, args) {
|
|
|
53028
53043
|
);
|
|
53029
53044
|
return { ok: true, result: { id } };
|
|
53030
53045
|
}
|
|
53046
|
+
case "create_secret": {
|
|
53047
|
+
const secretName = requireString(args.name, "name");
|
|
53048
|
+
const secretValue2 = requireString(args.value, "value");
|
|
53049
|
+
const kind = typeof args.kind === "string" && args.kind ? args.kind : null;
|
|
53050
|
+
const description = typeof args.description === "string" && args.description ? args.description : null;
|
|
53051
|
+
const id = (0, import_node_crypto18.randomUUID)();
|
|
53052
|
+
await ctx.db.insert("secrets", {
|
|
53053
|
+
id,
|
|
53054
|
+
name: secretName,
|
|
53055
|
+
value: secretValue2,
|
|
53056
|
+
kind,
|
|
53057
|
+
description
|
|
53058
|
+
});
|
|
53059
|
+
return { ok: true, result: { id, name: secretName } };
|
|
53060
|
+
}
|
|
53031
53061
|
case "create_artifact": {
|
|
53032
53062
|
const table = requireTable("files", ctx.validTables);
|
|
53033
53063
|
const title = requireString(args.title, "title");
|
|
@@ -53331,10 +53361,11 @@ async function executeFunction(ctx, name, args) {
|
|
|
53331
53361
|
return { ok: false, error: e6.message };
|
|
53332
53362
|
}
|
|
53333
53363
|
}
|
|
53334
|
-
var DISPATCHABLE, ASSISTANT_HIDDEN_TABLES, SECRET_MASK, BULK_FILTER_OPS;
|
|
53364
|
+
var import_node_crypto18, DISPATCHABLE, ASSISTANT_HIDDEN_TABLES, SECRET_MASK, BULK_FILTER_OPS;
|
|
53335
53365
|
var init_dispatch = __esm({
|
|
53336
53366
|
"src/gui/ai/dispatch.ts"() {
|
|
53337
53367
|
"use strict";
|
|
53368
|
+
import_node_crypto18 = require("crypto");
|
|
53338
53369
|
init_registry();
|
|
53339
53370
|
init_lattice_docs();
|
|
53340
53371
|
init_fts();
|
|
@@ -53358,6 +53389,7 @@ var init_dispatch = __esm({
|
|
|
53358
53389
|
"get_history",
|
|
53359
53390
|
"create_row",
|
|
53360
53391
|
"create_artifact",
|
|
53392
|
+
"create_secret",
|
|
53361
53393
|
"ingest_url",
|
|
53362
53394
|
"set_definition",
|
|
53363
53395
|
"set_visibility",
|
|
@@ -57927,9 +57959,41 @@ var appJs = `
|
|
|
57927
57959
|
}
|
|
57928
57960
|
function reloadForUpdate(label) {
|
|
57929
57961
|
if (reloadingForUpdate) return;
|
|
57962
|
+
// Guard against an infinite reload loop. Reloading when the server version
|
|
57963
|
+
// changes is the seamless-update trigger \u2014 but if the version the page was
|
|
57964
|
+
// SERVED with keeps disagreeing with /api/version (e.g. a stale or duplicate
|
|
57965
|
+
// server is holding the port and reporting a different version), every fresh
|
|
57966
|
+
// page would reload again. That unbounded loop pegs memory and crashes the
|
|
57967
|
+
// browser. Cap reloads to MAX within WINDOW; past that, stop and surface the
|
|
57968
|
+
// mismatch instead of spinning. A genuine update reloads once and then the
|
|
57969
|
+
// versions agree, which clears the counter (see checkServerVersion).
|
|
57970
|
+
var KEY = 'lattice:updateReloads',
|
|
57971
|
+
MAX = 3,
|
|
57972
|
+
WINDOW = 60000,
|
|
57973
|
+
now = Date.now(),
|
|
57974
|
+
recent = [];
|
|
57975
|
+
try {
|
|
57976
|
+
recent = (JSON.parse(sessionStorage.getItem(KEY) || '[]') || []).filter(function (t) {
|
|
57977
|
+
return now - t < WINDOW;
|
|
57978
|
+
});
|
|
57979
|
+
} catch (_) {
|
|
57980
|
+
/* sessionStorage blocked \u2014 degrade to a single best-effort reload below */
|
|
57981
|
+
}
|
|
57982
|
+
if (recent.length >= MAX) {
|
|
57983
|
+
showUpdatePill('Version mismatch \u2014 stopped auto-reloading. Reload manually if needed.');
|
|
57984
|
+
return;
|
|
57985
|
+
}
|
|
57986
|
+
recent.push(now);
|
|
57987
|
+
try {
|
|
57988
|
+
sessionStorage.setItem(KEY, JSON.stringify(recent));
|
|
57989
|
+
} catch (_) {
|
|
57990
|
+
/* best-effort */
|
|
57991
|
+
}
|
|
57930
57992
|
reloadingForUpdate = true;
|
|
57931
57993
|
showUpdatePill(label || 'Updated \u2014 reloading\u2026');
|
|
57932
|
-
setTimeout(function () {
|
|
57994
|
+
setTimeout(function () {
|
|
57995
|
+
location.reload();
|
|
57996
|
+
}, 600);
|
|
57933
57997
|
}
|
|
57934
57998
|
// Manual upgrade fallback: show an "Update available \u2014 Upgrade" link next to
|
|
57935
57999
|
// the version chip only when the server reports a newer, installable version.
|
|
@@ -57961,7 +58025,16 @@ var appJs = `
|
|
|
57961
58025
|
var v = d && d.version ? String(d.version).replace(/^v/, '').trim() : '';
|
|
57962
58026
|
if (!v) return;
|
|
57963
58027
|
if (v !== BOOT_VERSION) reloadForUpdate('Updated to v' + v + ' \u2014 reloading\u2026');
|
|
57964
|
-
else
|
|
58028
|
+
else {
|
|
58029
|
+
hideUpdatePill();
|
|
58030
|
+
// Versions agree \u2014 clear the reload-loop guard so a later genuine
|
|
58031
|
+
// update can reload again from a clean slate.
|
|
58032
|
+
try {
|
|
58033
|
+
sessionStorage.removeItem('lattice:updateReloads');
|
|
58034
|
+
} catch (_) {
|
|
58035
|
+
/* best-effort */
|
|
58036
|
+
}
|
|
58037
|
+
}
|
|
57965
58038
|
})
|
|
57966
58039
|
.catch(function () { /* offline / mid-restart \u2014 the next reconnect retries */ });
|
|
57967
58040
|
// Refresh the manual-upgrade link alongside the reconnect version check.
|
|
@@ -66322,11 +66395,11 @@ var import_yaml6 = require("yaml");
|
|
|
66322
66395
|
init_lattice();
|
|
66323
66396
|
init_user_config();
|
|
66324
66397
|
init_cloud_connect();
|
|
66325
|
-
var
|
|
66398
|
+
var import_node_crypto20 = require("crypto");
|
|
66326
66399
|
init_members();
|
|
66327
66400
|
|
|
66328
66401
|
// src/cloud/invite.ts
|
|
66329
|
-
var
|
|
66402
|
+
var import_node_crypto19 = require("crypto");
|
|
66330
66403
|
var VERSION = 1;
|
|
66331
66404
|
var SALT_LEN = 16;
|
|
66332
66405
|
var SECRET_LEN = 32;
|
|
@@ -66344,15 +66417,15 @@ function poolerAwareUser(host, role, ownerUser) {
|
|
|
66344
66417
|
return ref ? `${role}.${ref}` : role;
|
|
66345
66418
|
}
|
|
66346
66419
|
function deriveKey2(tokenSecret, email, salt) {
|
|
66347
|
-
const emailSalt = Buffer.from((0,
|
|
66348
|
-
return Buffer.from((0,
|
|
66420
|
+
const emailSalt = Buffer.from((0, import_node_crypto19.scryptSync)(normalizeEmail(email), salt, KEY_LEN));
|
|
66421
|
+
return Buffer.from((0, import_node_crypto19.hkdfSync)("sha256", tokenSecret, emailSalt, HKDF_INFO, KEY_LEN));
|
|
66349
66422
|
}
|
|
66350
66423
|
function mintInviteToken(input) {
|
|
66351
66424
|
const email = normalizeEmail(input.email);
|
|
66352
66425
|
if (!email) throw new Error("lattice: an invite must be bound to an email");
|
|
66353
|
-
const salt = (0,
|
|
66354
|
-
const tokenSecret = (0,
|
|
66355
|
-
const nonce = (0,
|
|
66426
|
+
const salt = (0, import_node_crypto19.randomBytes)(SALT_LEN);
|
|
66427
|
+
const tokenSecret = (0, import_node_crypto19.randomBytes)(SECRET_LEN);
|
|
66428
|
+
const nonce = (0, import_node_crypto19.randomBytes)(NONCE_LEN);
|
|
66356
66429
|
const key = deriveKey2(tokenSecret, email, salt);
|
|
66357
66430
|
const payload = {
|
|
66358
66431
|
v: 1,
|
|
@@ -66366,7 +66439,7 @@ function mintInviteToken(input) {
|
|
|
66366
66439
|
expires_at: input.expiresAt.toISOString(),
|
|
66367
66440
|
...input.workspaceName?.trim() ? { workspace_name: input.workspaceName.trim() } : {}
|
|
66368
66441
|
};
|
|
66369
|
-
const cipher = (0,
|
|
66442
|
+
const cipher = (0, import_node_crypto19.createCipheriv)("aes-256-gcm", key, nonce);
|
|
66370
66443
|
cipher.setAAD(Buffer.from(email, "utf8"));
|
|
66371
66444
|
const ct = Buffer.concat([cipher.update(JSON.stringify(payload), "utf8"), cipher.final()]);
|
|
66372
66445
|
const tag = cipher.getAuthTag();
|
|
@@ -66389,7 +66462,7 @@ function redeemInviteToken(email, token) {
|
|
|
66389
66462
|
const tag = raw.subarray(raw.length - TAG_LEN2);
|
|
66390
66463
|
const ct = raw.subarray(off, raw.length - TAG_LEN2);
|
|
66391
66464
|
const key = deriveKey2(tokenSecret, normEmail, salt);
|
|
66392
|
-
const decipher = (0,
|
|
66465
|
+
const decipher = (0, import_node_crypto19.createDecipheriv)("aes-256-gcm", key, nonce);
|
|
66393
66466
|
decipher.setAAD(Buffer.from(normEmail, "utf8"));
|
|
66394
66467
|
decipher.setAuthTag(tag);
|
|
66395
66468
|
let plaintext;
|
|
@@ -66417,7 +66490,7 @@ function redeemInviteToken(email, token) {
|
|
|
66417
66490
|
init_markdown();
|
|
66418
66491
|
init_rls();
|
|
66419
66492
|
init_adapter();
|
|
66420
|
-
var
|
|
66493
|
+
var import_node_crypto21 = require("crypto");
|
|
66421
66494
|
init_parser();
|
|
66422
66495
|
init_lattice_root();
|
|
66423
66496
|
init_workspace();
|
|
@@ -66502,7 +66575,7 @@ function parseAndValidateLogo(dataUri) {
|
|
|
66502
66575
|
error: `logo must be square (got ${String(dims.width)}\xD7${String(dims.height)})`
|
|
66503
66576
|
};
|
|
66504
66577
|
}
|
|
66505
|
-
return { ok: true, mime, bytes, etag: (0,
|
|
66578
|
+
return { ok: true, mime, bytes, etag: (0, import_node_crypto20.createHash)("sha256").update(bytes).digest("hex") };
|
|
66506
66579
|
}
|
|
66507
66580
|
function updateActiveWorkspaceToCloud(configPath, displayName, key) {
|
|
66508
66581
|
const root6 = findLatticeRoot((0, import_node_path36.dirname)(configPath));
|
|
@@ -67012,7 +67085,7 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
67012
67085
|
`INSERT INTO "__lattice_member_invites" ("id","role","email_hash","email","expires_at")
|
|
67013
67086
|
VALUES (?, ?, ?, ?, ?)`,
|
|
67014
67087
|
[
|
|
67015
|
-
(0,
|
|
67088
|
+
(0, import_node_crypto21.randomUUID)(),
|
|
67016
67089
|
role,
|
|
67017
67090
|
emailHash,
|
|
67018
67091
|
// Plaintext email stored ONLY in this owner-only table so the owner's
|
|
@@ -67838,7 +67911,7 @@ var import_node_os9 = require("os");
|
|
|
67838
67911
|
var import_node_path37 = require("path");
|
|
67839
67912
|
init_mutations();
|
|
67840
67913
|
init_extract();
|
|
67841
|
-
var
|
|
67914
|
+
var import_node_crypto22 = require("crypto");
|
|
67842
67915
|
init_assistant_routes();
|
|
67843
67916
|
init_enrich();
|
|
67844
67917
|
init_ingest_url();
|
|
@@ -68079,7 +68152,7 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
68079
68152
|
let s3Status = null;
|
|
68080
68153
|
const s3cfg = resolveActiveS3Config(ctx.configPath);
|
|
68081
68154
|
if (s3cfg) {
|
|
68082
|
-
const sha256 = blob?.sha256 ?? (0,
|
|
68155
|
+
const sha256 = blob?.sha256 ?? (0, import_node_crypto22.createHash)("sha256").update(buf).digest("hex");
|
|
68083
68156
|
const key = s3Key(s3cfg.prefix, sha256);
|
|
68084
68157
|
try {
|
|
68085
68158
|
const store = await createS3Store(s3cfg);
|
|
@@ -68111,7 +68184,7 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
68111
68184
|
realPath = rawFilePath;
|
|
68112
68185
|
}
|
|
68113
68186
|
}
|
|
68114
|
-
const fileSha = blob?.sha256 ?? s3Ref?.sha256 ?? (0,
|
|
68187
|
+
const fileSha = blob?.sha256 ?? s3Ref?.sha256 ?? (0, import_node_crypto22.createHash)("sha256").update(buf).digest("hex");
|
|
68115
68188
|
const uploadRow = {
|
|
68116
68189
|
id: fileId,
|
|
68117
68190
|
...fileIdentity(name2, fileId),
|
|
@@ -71200,7 +71273,7 @@ ${e6.stack ?? ""}`
|
|
|
71200
71273
|
// src/cloud/file-source-key-store.ts
|
|
71201
71274
|
var import_node_fs36 = require("fs");
|
|
71202
71275
|
var import_node_path39 = require("path");
|
|
71203
|
-
var
|
|
71276
|
+
var import_node_crypto23 = require("crypto");
|
|
71204
71277
|
var ENC_HEADER = "LATTICE-KMS-v1\n";
|
|
71205
71278
|
var SCRYPT_N = 1 << 15;
|
|
71206
71279
|
var SCRYPT_R = 8;
|
|
@@ -71227,7 +71300,7 @@ var FileSourceKeyStore = class {
|
|
|
71227
71300
|
getOrCreate(sourceId) {
|
|
71228
71301
|
let key = this.cache.get(sourceId);
|
|
71229
71302
|
if (!key) {
|
|
71230
|
-
key = (0,
|
|
71303
|
+
key = (0, import_node_crypto23.randomBytes)(KEY_LEN2);
|
|
71231
71304
|
this.cache.set(sourceId, key);
|
|
71232
71305
|
this.persist();
|
|
71233
71306
|
}
|
|
@@ -71271,7 +71344,7 @@ var FileSourceKeyStore = class {
|
|
|
71271
71344
|
const obj2 = {};
|
|
71272
71345
|
for (const [k6, v2] of this.cache) obj2[k6] = v2.toString("base64");
|
|
71273
71346
|
const encoded = this.encodeFile(obj2);
|
|
71274
|
-
const tmpPath = `${this.path}.tmp-${process.pid.toString()}-${(0,
|
|
71347
|
+
const tmpPath = `${this.path}.tmp-${process.pid.toString()}-${(0, import_node_crypto23.randomBytes)(4).toString("hex")}`;
|
|
71275
71348
|
(0, import_node_fs36.writeFileSync)(tmpPath, encoded, { mode: 384 });
|
|
71276
71349
|
try {
|
|
71277
71350
|
(0, import_node_fs36.chmodSync)(tmpPath, 384);
|
|
@@ -71314,14 +71387,14 @@ var FileSourceKeyStore = class {
|
|
|
71314
71387
|
if (passphrase === void 0) {
|
|
71315
71388
|
throw new Error("lattice: key file is encrypted but no passphrase was configured");
|
|
71316
71389
|
}
|
|
71317
|
-
const derived = (0,
|
|
71390
|
+
const derived = (0, import_node_crypto23.scryptSync)(passphrase, salt, KEY_LEN2, {
|
|
71318
71391
|
N: SCRYPT_N,
|
|
71319
71392
|
r: SCRYPT_R,
|
|
71320
71393
|
p: SCRYPT_P,
|
|
71321
71394
|
maxmem: 64 * 1024 * 1024
|
|
71322
71395
|
// raise Node's default 32MB cap so N=2^15 fits
|
|
71323
71396
|
});
|
|
71324
|
-
const decipher = (0,
|
|
71397
|
+
const decipher = (0, import_node_crypto23.createDecipheriv)("aes-256-gcm", derived, iv);
|
|
71325
71398
|
decipher.setAuthTag(tag);
|
|
71326
71399
|
let plaintext;
|
|
71327
71400
|
try {
|
|
@@ -71336,15 +71409,15 @@ var FileSourceKeyStore = class {
|
|
|
71336
71409
|
if (!this.passphrase) {
|
|
71337
71410
|
return Buffer.from(json, "utf8");
|
|
71338
71411
|
}
|
|
71339
|
-
const salt = (0,
|
|
71340
|
-
const iv = (0,
|
|
71341
|
-
const derived = (0,
|
|
71412
|
+
const salt = (0, import_node_crypto23.randomBytes)(SALT_LEN2);
|
|
71413
|
+
const iv = (0, import_node_crypto23.randomBytes)(IV_LEN2);
|
|
71414
|
+
const derived = (0, import_node_crypto23.scryptSync)(this.passphrase, salt, KEY_LEN2, {
|
|
71342
71415
|
N: SCRYPT_N,
|
|
71343
71416
|
r: SCRYPT_R,
|
|
71344
71417
|
p: SCRYPT_P,
|
|
71345
71418
|
maxmem: 64 * 1024 * 1024
|
|
71346
71419
|
});
|
|
71347
|
-
const cipher = (0,
|
|
71420
|
+
const cipher = (0, import_node_crypto23.createCipheriv)("aes-256-gcm", derived, iv);
|
|
71348
71421
|
const ct = Buffer.concat([cipher.update(json, "utf8"), cipher.final()]);
|
|
71349
71422
|
const tag = cipher.getAuthTag();
|
|
71350
71423
|
const body = `${salt.toString("hex")}:${iv.toString("hex")}:${Buffer.concat([ct, tag]).toString("hex")}`;
|
package/dist/index.js
CHANGED
|
@@ -50597,6 +50597,21 @@ var init_registry = __esm({
|
|
|
50597
50597
|
["table", "values"]
|
|
50598
50598
|
)
|
|
50599
50599
|
},
|
|
50600
|
+
{
|
|
50601
|
+
name: "create_secret",
|
|
50602
|
+
description: "Store a secret/credential \u2014 an API key, password, OAuth token, connection string, etc. \u2014 by name in the encrypted secrets store. Use this whenever the user gives you a credential to save or asks you to remember/store a secret. WRITE-ONLY: you can save a secret but you can NEVER read, list, echo, or retrieve existing secret values \u2014 they are hidden from you. The value is encrypted at rest.",
|
|
50603
|
+
mutates: true,
|
|
50604
|
+
category: "row",
|
|
50605
|
+
args: obj(
|
|
50606
|
+
{
|
|
50607
|
+
name: str('A short label for the secret, e.g. "GitHub password" or "OpenAI API key".'),
|
|
50608
|
+
value: str("The secret value to store."),
|
|
50609
|
+
kind: str('Optional kind, e.g. "password", "api_key", "token", "connection_string".'),
|
|
50610
|
+
description: str("Optional note about what the secret is for.")
|
|
50611
|
+
},
|
|
50612
|
+
["name", "value"]
|
|
50613
|
+
)
|
|
50614
|
+
},
|
|
50600
50615
|
{
|
|
50601
50616
|
name: "create_artifact",
|
|
50602
50617
|
description: "Create a markdown document and save it as a file artifact. Use this whenever the user asks you to create, write, draft, or make a document, note, write-up, summary, report, or file \u2014 you author the content as GitHub-flavored markdown and it is saved in the files entity as a markdown artifact, then opened in the viewer for them. Prefer this over create_row on files for any document the user wants to keep. It follows the same sharing rules as any file (private mode \u2192 private).",
|
|
@@ -52803,6 +52818,7 @@ var init_ingest_url = __esm({
|
|
|
52803
52818
|
});
|
|
52804
52819
|
|
|
52805
52820
|
// src/gui/ai/dispatch.ts
|
|
52821
|
+
import { randomUUID } from "crypto";
|
|
52806
52822
|
function visibilityDenialReason(opts) {
|
|
52807
52823
|
if (opts.kind === "table") {
|
|
52808
52824
|
return opts.canManageTableDefault ? null : "Only the workspace owner can change a table's default sharing.";
|
|
@@ -53018,6 +53034,21 @@ async function executeFunction(ctx, name, args) {
|
|
|
53018
53034
|
);
|
|
53019
53035
|
return { ok: true, result: { id } };
|
|
53020
53036
|
}
|
|
53037
|
+
case "create_secret": {
|
|
53038
|
+
const secretName = requireString(args.name, "name");
|
|
53039
|
+
const secretValue2 = requireString(args.value, "value");
|
|
53040
|
+
const kind = typeof args.kind === "string" && args.kind ? args.kind : null;
|
|
53041
|
+
const description = typeof args.description === "string" && args.description ? args.description : null;
|
|
53042
|
+
const id = randomUUID();
|
|
53043
|
+
await ctx.db.insert("secrets", {
|
|
53044
|
+
id,
|
|
53045
|
+
name: secretName,
|
|
53046
|
+
value: secretValue2,
|
|
53047
|
+
kind,
|
|
53048
|
+
description
|
|
53049
|
+
});
|
|
53050
|
+
return { ok: true, result: { id, name: secretName } };
|
|
53051
|
+
}
|
|
53021
53052
|
case "create_artifact": {
|
|
53022
53053
|
const table = requireTable("files", ctx.validTables);
|
|
53023
53054
|
const title = requireString(args.title, "title");
|
|
@@ -53348,6 +53379,7 @@ var init_dispatch = __esm({
|
|
|
53348
53379
|
"get_history",
|
|
53349
53380
|
"create_row",
|
|
53350
53381
|
"create_artifact",
|
|
53382
|
+
"create_secret",
|
|
53351
53383
|
"ingest_url",
|
|
53352
53384
|
"set_definition",
|
|
53353
53385
|
"set_visibility",
|
|
@@ -57752,9 +57784,41 @@ var appJs = `
|
|
|
57752
57784
|
}
|
|
57753
57785
|
function reloadForUpdate(label) {
|
|
57754
57786
|
if (reloadingForUpdate) return;
|
|
57787
|
+
// Guard against an infinite reload loop. Reloading when the server version
|
|
57788
|
+
// changes is the seamless-update trigger \u2014 but if the version the page was
|
|
57789
|
+
// SERVED with keeps disagreeing with /api/version (e.g. a stale or duplicate
|
|
57790
|
+
// server is holding the port and reporting a different version), every fresh
|
|
57791
|
+
// page would reload again. That unbounded loop pegs memory and crashes the
|
|
57792
|
+
// browser. Cap reloads to MAX within WINDOW; past that, stop and surface the
|
|
57793
|
+
// mismatch instead of spinning. A genuine update reloads once and then the
|
|
57794
|
+
// versions agree, which clears the counter (see checkServerVersion).
|
|
57795
|
+
var KEY = 'lattice:updateReloads',
|
|
57796
|
+
MAX = 3,
|
|
57797
|
+
WINDOW = 60000,
|
|
57798
|
+
now = Date.now(),
|
|
57799
|
+
recent = [];
|
|
57800
|
+
try {
|
|
57801
|
+
recent = (JSON.parse(sessionStorage.getItem(KEY) || '[]') || []).filter(function (t) {
|
|
57802
|
+
return now - t < WINDOW;
|
|
57803
|
+
});
|
|
57804
|
+
} catch (_) {
|
|
57805
|
+
/* sessionStorage blocked \u2014 degrade to a single best-effort reload below */
|
|
57806
|
+
}
|
|
57807
|
+
if (recent.length >= MAX) {
|
|
57808
|
+
showUpdatePill('Version mismatch \u2014 stopped auto-reloading. Reload manually if needed.');
|
|
57809
|
+
return;
|
|
57810
|
+
}
|
|
57811
|
+
recent.push(now);
|
|
57812
|
+
try {
|
|
57813
|
+
sessionStorage.setItem(KEY, JSON.stringify(recent));
|
|
57814
|
+
} catch (_) {
|
|
57815
|
+
/* best-effort */
|
|
57816
|
+
}
|
|
57755
57817
|
reloadingForUpdate = true;
|
|
57756
57818
|
showUpdatePill(label || 'Updated \u2014 reloading\u2026');
|
|
57757
|
-
setTimeout(function () {
|
|
57819
|
+
setTimeout(function () {
|
|
57820
|
+
location.reload();
|
|
57821
|
+
}, 600);
|
|
57758
57822
|
}
|
|
57759
57823
|
// Manual upgrade fallback: show an "Update available \u2014 Upgrade" link next to
|
|
57760
57824
|
// the version chip only when the server reports a newer, installable version.
|
|
@@ -57786,7 +57850,16 @@ var appJs = `
|
|
|
57786
57850
|
var v = d && d.version ? String(d.version).replace(/^v/, '').trim() : '';
|
|
57787
57851
|
if (!v) return;
|
|
57788
57852
|
if (v !== BOOT_VERSION) reloadForUpdate('Updated to v' + v + ' \u2014 reloading\u2026');
|
|
57789
|
-
else
|
|
57853
|
+
else {
|
|
57854
|
+
hideUpdatePill();
|
|
57855
|
+
// Versions agree \u2014 clear the reload-loop guard so a later genuine
|
|
57856
|
+
// update can reload again from a clean slate.
|
|
57857
|
+
try {
|
|
57858
|
+
sessionStorage.removeItem('lattice:updateReloads');
|
|
57859
|
+
} catch (_) {
|
|
57860
|
+
/* best-effort */
|
|
57861
|
+
}
|
|
57862
|
+
}
|
|
57790
57863
|
})
|
|
57791
57864
|
.catch(function () { /* offline / mid-restart \u2014 the next reconnect retries */ });
|
|
57792
57865
|
// Refresh the manual-upgrade link alongside the reconnect version check.
|
|
@@ -66241,7 +66314,7 @@ function redeemInviteToken(email, token) {
|
|
|
66241
66314
|
init_markdown();
|
|
66242
66315
|
init_rls();
|
|
66243
66316
|
init_adapter();
|
|
66244
|
-
import { randomUUID } from "crypto";
|
|
66317
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
66245
66318
|
init_parser();
|
|
66246
66319
|
init_lattice_root();
|
|
66247
66320
|
init_workspace();
|
|
@@ -66836,7 +66909,7 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
66836
66909
|
`INSERT INTO "__lattice_member_invites" ("id","role","email_hash","email","expires_at")
|
|
66837
66910
|
VALUES (?, ?, ?, ?, ?)`,
|
|
66838
66911
|
[
|
|
66839
|
-
|
|
66912
|
+
randomUUID2(),
|
|
66840
66913
|
role,
|
|
66841
66914
|
emailHash,
|
|
66842
66915
|
// Plaintext email stored ONLY in this owner-only table so the owner's
|
package/package.json
CHANGED