@superatomai/sdk-node 0.0.56-dsp → 0.0.57-dsp
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/index.d.mts +298 -1
- package/dist/index.d.ts +298 -1
- package/dist/index.js +574 -61
- package/dist/index.mjs +554 -59
- package/dist/userResponse/scripts/script-bootstrap.js +20 -11
- package/dist/userResponse/scripts/script-bootstrap.mjs +20 -11
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -284,11 +284,17 @@ var UserPromptRequestPayloadSchema = z3.object({
|
|
|
284
284
|
}).optional(),
|
|
285
285
|
responseMode: z3.enum(["component", "text"]).optional()
|
|
286
286
|
});
|
|
287
|
+
var MessageAuthContextSchema = z3.object({
|
|
288
|
+
userId: z3.string(),
|
|
289
|
+
orgId: z3.string().optional(),
|
|
290
|
+
role: z3.string().optional()
|
|
291
|
+
});
|
|
287
292
|
var UserPromptRequestMessageSchema = z3.object({
|
|
288
293
|
id: z3.string(),
|
|
289
294
|
from: MessageParticipantSchema,
|
|
290
295
|
type: z3.literal("USER_PROMPT_REQ"),
|
|
291
|
-
payload: UserPromptRequestPayloadSchema
|
|
296
|
+
payload: UserPromptRequestPayloadSchema,
|
|
297
|
+
authContext: MessageAuthContextSchema.optional()
|
|
292
298
|
});
|
|
293
299
|
var UserPromptAbortRequestPayloadSchema = z3.object({
|
|
294
300
|
uiBlockId: z3.string(),
|
|
@@ -738,7 +744,8 @@ var DashCompRequestMessageSchema = z3.object({
|
|
|
738
744
|
id: z3.string(),
|
|
739
745
|
from: MessageParticipantSchema,
|
|
740
746
|
type: z3.literal("DASH_COMP_REQ"),
|
|
741
|
-
payload: DashCompRequestPayloadSchema
|
|
747
|
+
payload: DashCompRequestPayloadSchema,
|
|
748
|
+
authContext: MessageAuthContextSchema.optional()
|
|
742
749
|
});
|
|
743
750
|
var DashboardAgentRequestPayloadSchema = z3.object({
|
|
744
751
|
prompt: z3.string(),
|
|
@@ -811,7 +818,8 @@ var ReportCompRequestMessageSchema = z3.object({
|
|
|
811
818
|
id: z3.string(),
|
|
812
819
|
from: MessageParticipantSchema,
|
|
813
820
|
type: z3.literal("REPORT_COMP_REQ"),
|
|
814
|
-
payload: ReportCompRequestPayloadSchema
|
|
821
|
+
payload: ReportCompRequestPayloadSchema,
|
|
822
|
+
authContext: MessageAuthContextSchema.optional()
|
|
815
823
|
});
|
|
816
824
|
var ReportCompAbortRequestPayloadSchema = z3.object({
|
|
817
825
|
reportId: z3.string()
|
|
@@ -1605,6 +1613,101 @@ var ThreadManager = class _ThreadManager {
|
|
|
1605
1613
|
|
|
1606
1614
|
// src/utils/query-cache.ts
|
|
1607
1615
|
import crypto from "crypto";
|
|
1616
|
+
|
|
1617
|
+
// src/utils/request-context.ts
|
|
1618
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1619
|
+
var STORAGE_KEY = Symbol.for("superatom.requestContext.v1");
|
|
1620
|
+
function getStorage() {
|
|
1621
|
+
const g = globalThis;
|
|
1622
|
+
let store = g[STORAGE_KEY];
|
|
1623
|
+
if (!store) {
|
|
1624
|
+
store = new AsyncLocalStorage();
|
|
1625
|
+
g[STORAGE_KEY] = store;
|
|
1626
|
+
}
|
|
1627
|
+
return store;
|
|
1628
|
+
}
|
|
1629
|
+
function enforcementApplies() {
|
|
1630
|
+
return (process.env.SA_SQL_REWRITE_MODE || "").toLowerCase() === "enforce";
|
|
1631
|
+
}
|
|
1632
|
+
function getRequestContext() {
|
|
1633
|
+
return getStorage().getStore();
|
|
1634
|
+
}
|
|
1635
|
+
function requireRequestContext(what = "this operation") {
|
|
1636
|
+
const ctx = getStorage().getStore();
|
|
1637
|
+
if (!ctx) {
|
|
1638
|
+
throw new MissingRequestContextError(
|
|
1639
|
+
`No request context for ${what}. The caller must establish one with runWithContext() or declare itself trusted with runAsSystem().`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
return ctx;
|
|
1643
|
+
}
|
|
1644
|
+
var MissingRequestContextError = class extends Error {
|
|
1645
|
+
constructor(message) {
|
|
1646
|
+
super(message);
|
|
1647
|
+
this.name = "MissingRequestContextError";
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
function runWithContext(ctx, fn) {
|
|
1651
|
+
return getStorage().run(ctx, fn);
|
|
1652
|
+
}
|
|
1653
|
+
function runAsUser(user, fn) {
|
|
1654
|
+
const { userId, orgId, role, projectId, requestId, policies } = user;
|
|
1655
|
+
return runWithContext(
|
|
1656
|
+
{ principal: { kind: "user", userId, orgId, role }, projectId, requestId, policies },
|
|
1657
|
+
fn
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
function runAsSystem(reason, fn, opts) {
|
|
1661
|
+
if (!reason || !reason.trim()) {
|
|
1662
|
+
throw new Error("runAsSystem() requires a non-empty reason \u2014 it identifies the path in the audit log.");
|
|
1663
|
+
}
|
|
1664
|
+
return runWithContext(
|
|
1665
|
+
{ principal: { kind: "system", reason: reason.trim() }, projectId: opts?.projectId, requestId: opts?.requestId },
|
|
1666
|
+
fn
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
function captureContext() {
|
|
1670
|
+
return getStorage().getStore();
|
|
1671
|
+
}
|
|
1672
|
+
function withCapturedContext(ctx, fn) {
|
|
1673
|
+
return ctx ? runWithContext(ctx, fn) : fn();
|
|
1674
|
+
}
|
|
1675
|
+
function bindContext(fn) {
|
|
1676
|
+
const captured = captureContext();
|
|
1677
|
+
return (...args) => withCapturedContext(captured, () => fn(...args));
|
|
1678
|
+
}
|
|
1679
|
+
function describeContext(ctx = getRequestContext()) {
|
|
1680
|
+
if (!ctx) return "anonymous";
|
|
1681
|
+
return ctx.principal.kind === "user" ? `user:${ctx.principal.userId}` : `system:${ctx.principal.reason}`;
|
|
1682
|
+
}
|
|
1683
|
+
function isUserContext(ctx = getRequestContext()) {
|
|
1684
|
+
return ctx?.principal.kind === "user";
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// src/utils/query-cache.ts
|
|
1688
|
+
var fingerprints = /* @__PURE__ */ new WeakMap();
|
|
1689
|
+
function canonicalPolicies(policies) {
|
|
1690
|
+
const norm = policies.map((p) => ({
|
|
1691
|
+
s: p.sourceId ?? "*",
|
|
1692
|
+
t: [...p.readableTables ?? []].sort(),
|
|
1693
|
+
f: (p.rowFilters ?? []).map((f) => ({
|
|
1694
|
+
t: f.table ?? "*",
|
|
1695
|
+
c: f.column,
|
|
1696
|
+
o: f.op ?? "in",
|
|
1697
|
+
v: [...f.values ?? []].map((v) => String(v)).sort()
|
|
1698
|
+
})).sort((a, b) => `${a.t}.${a.c}.${a.o}`.localeCompare(`${b.t}.${b.c}.${b.o}`))
|
|
1699
|
+
}));
|
|
1700
|
+
norm.sort((a, b) => a.s.localeCompare(b.s));
|
|
1701
|
+
return JSON.stringify(norm);
|
|
1702
|
+
}
|
|
1703
|
+
function policyFingerprint(policies) {
|
|
1704
|
+
const memo = fingerprints.get(policies);
|
|
1705
|
+
if (memo !== void 0) return memo;
|
|
1706
|
+
const fp = crypto.createHash("sha256").update(canonicalPolicies(policies)).digest("hex").slice(0, 16);
|
|
1707
|
+
fingerprints.set(policies, fp);
|
|
1708
|
+
return fp;
|
|
1709
|
+
}
|
|
1710
|
+
var warnedNoContext = false;
|
|
1608
1711
|
var QueryCache = class {
|
|
1609
1712
|
constructor() {
|
|
1610
1713
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -1636,6 +1739,31 @@ var QueryCache = class {
|
|
|
1636
1739
|
getTTL() {
|
|
1637
1740
|
return this.ttlMs / 60 / 1e3;
|
|
1638
1741
|
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Which access bucket this call reads and writes.
|
|
1744
|
+
*
|
|
1745
|
+
* `''` means the unrestricted bucket and produces a key identical to the one
|
|
1746
|
+
* used before authorization existed. `null` means do not touch the cache.
|
|
1747
|
+
*/
|
|
1748
|
+
scope() {
|
|
1749
|
+
if (!enforcementApplies()) return "";
|
|
1750
|
+
const ctx = getRequestContext();
|
|
1751
|
+
if (!ctx) {
|
|
1752
|
+
if (!warnedNoContext) {
|
|
1753
|
+
warnedNoContext = true;
|
|
1754
|
+
logger.warn("[QueryCache] bypassed \u2014 no request context under SA_SQL_REWRITE_MODE=enforce");
|
|
1755
|
+
}
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const policies = ctx.policies ?? [];
|
|
1759
|
+
return policies.length ? policyFingerprint(policies) : "";
|
|
1760
|
+
}
|
|
1761
|
+
/** The stored key: the scope namespace, then the caller's key. NUL cannot occur in SQL. */
|
|
1762
|
+
scopedKey(query) {
|
|
1763
|
+
const s = this.scope();
|
|
1764
|
+
if (s === null) return null;
|
|
1765
|
+
return s ? `${s}\0${query}` : query;
|
|
1766
|
+
}
|
|
1639
1767
|
/**
|
|
1640
1768
|
* Store query result in data cache.
|
|
1641
1769
|
* If the key already exists, it's removed first so the re-insert places it
|
|
@@ -1643,12 +1771,14 @@ var QueryCache = class {
|
|
|
1643
1771
|
* a genuinely new key past the size limit.
|
|
1644
1772
|
*/
|
|
1645
1773
|
set(query, data) {
|
|
1646
|
-
this.
|
|
1774
|
+
const key = this.scopedKey(query);
|
|
1775
|
+
if (key === null) return;
|
|
1776
|
+
this.cache.delete(key);
|
|
1647
1777
|
if (this.cache.size >= this.maxCacheSize) {
|
|
1648
1778
|
const oldestKey = this.cache.keys().next().value;
|
|
1649
1779
|
if (oldestKey) this.cache.delete(oldestKey);
|
|
1650
1780
|
}
|
|
1651
|
-
this.cache.set(
|
|
1781
|
+
this.cache.set(key, {
|
|
1652
1782
|
query,
|
|
1653
1783
|
data,
|
|
1654
1784
|
timestamp: Date.now()
|
|
@@ -1661,14 +1791,16 @@ var QueryCache = class {
|
|
|
1661
1791
|
* iteration order — turning FIFO eviction into true LRU.
|
|
1662
1792
|
*/
|
|
1663
1793
|
get(query) {
|
|
1664
|
-
const
|
|
1794
|
+
const key = this.scopedKey(query);
|
|
1795
|
+
if (key === null) return null;
|
|
1796
|
+
const entry = this.cache.get(key);
|
|
1665
1797
|
if (!entry) return null;
|
|
1666
1798
|
if (Date.now() - entry.timestamp > this.ttlMs) {
|
|
1667
|
-
this.cache.delete(
|
|
1799
|
+
this.cache.delete(key);
|
|
1668
1800
|
return null;
|
|
1669
1801
|
}
|
|
1670
|
-
this.cache.delete(
|
|
1671
|
-
this.cache.set(
|
|
1802
|
+
this.cache.delete(key);
|
|
1803
|
+
this.cache.set(key, entry);
|
|
1672
1804
|
logger.info(`[QueryCache] Cache HIT for query (${query.substring(0, 50)}...)`);
|
|
1673
1805
|
return entry.data;
|
|
1674
1806
|
}
|
|
@@ -1682,7 +1814,8 @@ var QueryCache = class {
|
|
|
1682
1814
|
* Remove a specific query from cache
|
|
1683
1815
|
*/
|
|
1684
1816
|
delete(query) {
|
|
1685
|
-
this.
|
|
1817
|
+
const key = this.scopedKey(query);
|
|
1818
|
+
if (key !== null) this.cache.delete(key);
|
|
1686
1819
|
}
|
|
1687
1820
|
/**
|
|
1688
1821
|
* Clear all cached entries
|
|
@@ -2185,6 +2318,96 @@ function buildDirectToolCacheKey(toolId, parameters) {
|
|
|
2185
2318
|
return `et-direct:${toolId}:${stableStringify(parameters || {})}`;
|
|
2186
2319
|
}
|
|
2187
2320
|
|
|
2321
|
+
// src/handlers/request-identity.ts
|
|
2322
|
+
function policiesFromConfig(config2) {
|
|
2323
|
+
const entries = Array.isArray(config2) ? config2 : [config2];
|
|
2324
|
+
const out = [];
|
|
2325
|
+
for (const entry of entries) {
|
|
2326
|
+
if (!entry || typeof entry !== "object") continue;
|
|
2327
|
+
const e = entry;
|
|
2328
|
+
const rawFilters = Array.isArray(e.rowFilters) ? e.rowFilters : [];
|
|
2329
|
+
const rowFilters = [];
|
|
2330
|
+
for (const f of rawFilters) {
|
|
2331
|
+
if (!f || typeof f !== "object") continue;
|
|
2332
|
+
const rf = f;
|
|
2333
|
+
if (typeof rf.column !== "string" || !rf.column) continue;
|
|
2334
|
+
if (!Array.isArray(rf.values) || rf.values.length === 0) continue;
|
|
2335
|
+
const op = typeof rf.op === "string" ? rf.op.toLowerCase() : "in";
|
|
2336
|
+
if (op !== "in" && op !== "not_in" && op !== "eq" && op !== "ne") continue;
|
|
2337
|
+
rowFilters.push({
|
|
2338
|
+
table: typeof rf.table === "string" && rf.table ? rf.table : void 0,
|
|
2339
|
+
column: rf.column,
|
|
2340
|
+
op,
|
|
2341
|
+
values: rf.values,
|
|
2342
|
+
label: typeof rf.label === "string" && rf.label.trim() ? rf.label.trim() : void 0
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
const readableTables = Array.isArray(e.readableTables) ? e.readableTables.filter((t) => typeof t === "string" && !!t) : [];
|
|
2346
|
+
if (rowFilters.length === 0 && readableTables.length === 0) continue;
|
|
2347
|
+
const label = typeof e.label === "string" && e.label.trim() ? e.label.trim() : void 0;
|
|
2348
|
+
if (!label && rowFilters.some((f) => !f.label) && !warnedUnlabelled) {
|
|
2349
|
+
warnedUnlabelled = true;
|
|
2350
|
+
logger.warn(
|
|
2351
|
+
'[RequestIdentity] An access policy has no "label" \u2014 the agent will describe it to the user as a raw SQL predicate. Add a short noun phrase, e.g. "label": "Hyderabad branch".'
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
out.push({
|
|
2355
|
+
sourceId: typeof e.sourceId === "string" && e.sourceId ? e.sourceId : null,
|
|
2356
|
+
label,
|
|
2357
|
+
rowFilters,
|
|
2358
|
+
readableTables
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
return out;
|
|
2362
|
+
}
|
|
2363
|
+
var warnedUntrusted = false;
|
|
2364
|
+
var warnedAnonymous = false;
|
|
2365
|
+
var warnedUnlabelled = false;
|
|
2366
|
+
var announcedVerified = false;
|
|
2367
|
+
var reportedIdMismatches = /* @__PURE__ */ new Set();
|
|
2368
|
+
function runWithMessageIdentity(message, fn) {
|
|
2369
|
+
const verified = message?.authContext;
|
|
2370
|
+
const requestId = message?.id;
|
|
2371
|
+
const policies = policiesFromConfig(message?.payload?.config);
|
|
2372
|
+
if (verified?.userId) {
|
|
2373
|
+
const claimedId = message?.payload?.userId;
|
|
2374
|
+
if (!announcedVerified) {
|
|
2375
|
+
announcedVerified = true;
|
|
2376
|
+
logger.info(
|
|
2377
|
+
`[RequestIdentity] Using the relay-verified authContext as the principal (userId ${verified.userId})${claimedId ? `; client claimed ${claimedId === verified.userId ? "the same id" : claimedId}` : ""}.`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
if (claimedId && claimedId !== verified.userId) {
|
|
2381
|
+
const pair = `${verified.userId}<-${claimedId}`;
|
|
2382
|
+
if (!reportedIdMismatches.has(pair) && reportedIdMismatches.size < 100) {
|
|
2383
|
+
reportedIdMismatches.add(pair);
|
|
2384
|
+
logger.warn(
|
|
2385
|
+
`[RequestIdentity] IDENTITY MISMATCH: the verified session says userId=${verified.userId} but the client sent payload.userId=${claimedId}. The verified id wins. Anything keyed on the principal (conversation history, threads, bookmarks) will look EMPTY for this user until the two agree.`
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return runAsUser(
|
|
2390
|
+
{ userId: verified.userId, orgId: verified.orgId, role: verified.role, requestId, policies },
|
|
2391
|
+
fn
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
const claimed = message?.payload?.userId;
|
|
2395
|
+
if (typeof claimed === "string" && claimed) {
|
|
2396
|
+
if (!warnedUntrusted) {
|
|
2397
|
+
warnedUntrusted = true;
|
|
2398
|
+
logger.warn(
|
|
2399
|
+
"[RequestIdentity] Using the client-supplied payload.userId as the principal \u2014 the relay is not stamping a verified authContext yet, so this identity is NOT trustworthy. Fine for plumbing; do not enable policy enforcement until the relay change lands."
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
return runAsUser({ userId: claimed, requestId, policies }, fn);
|
|
2403
|
+
}
|
|
2404
|
+
if (!warnedAnonymous) {
|
|
2405
|
+
warnedAnonymous = true;
|
|
2406
|
+
logger.info("[RequestIdentity] No user identity on this message \u2014 running without a request context.");
|
|
2407
|
+
}
|
|
2408
|
+
return fn();
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2188
2411
|
// src/handlers/data-request.ts
|
|
2189
2412
|
function getQueryCacheKey(query) {
|
|
2190
2413
|
if (typeof query === "string") {
|
|
@@ -2224,6 +2447,9 @@ function getCacheKey(collection, op, params, tools) {
|
|
|
2224
2447
|
return "";
|
|
2225
2448
|
}
|
|
2226
2449
|
async function handleDataRequest(data, collections, sendMessage, tools) {
|
|
2450
|
+
return runWithMessageIdentity(data, () => handleDataRequestInner(data, collections, sendMessage, tools));
|
|
2451
|
+
}
|
|
2452
|
+
async function handleDataRequestInner(data, collections, sendMessage, tools) {
|
|
2227
2453
|
let requestId;
|
|
2228
2454
|
let collection;
|
|
2229
2455
|
let op;
|
|
@@ -7081,7 +7307,6 @@ Rules: state ONLY filters you actually applied (do not claim a filter you didn't
|
|
|
7081
7307
|
### Knowledge Base Context
|
|
7082
7308
|
{{KNOWLEDGE_BASE_CONTEXT}}
|
|
7083
7309
|
|
|
7084
|
-
### User Access Scope
|
|
7085
7310
|
{{USER_ACCESS_CONTEXT}}
|
|
7086
7311
|
|
|
7087
7312
|
### Conversation History
|
|
@@ -7111,7 +7336,6 @@ Your job is to fetch the requested data using the available tool.
|
|
|
7111
7336
|
|
|
7112
7337
|
### Critical Rules
|
|
7113
7338
|
- **CRITICAL: Always apply the filtering rules from the Global Knowledge Base and Knowledge Base Context sections below.** These define how business entities should be filtered in queries. If a knowledge base entry specifies column conditions for an entity, ALWAYS include those conditions as WHERE clauses.
|
|
7114
|
-
- **CRITICAL: Always apply the restrictions in the User Access Scope section below \u2014 unlike other defaults in this prompt, this is NOT overridden by an explicit user request.** If it names a value for a dimension (e.g. a location/branch id) and this table has a column representing that dimension, ALWAYS filter on it, inferring the matching column from the schema. If the user explicitly asks for a different value on that dimension, do NOT fetch it \u2014 say they don't have access to it.
|
|
7115
7339
|
- **CRITICAL: Follow ALL the database-specific SQL rules above.** These prevent syntax errors specific to this database engine. Violating them will cause query failures.
|
|
7116
7340
|
- **CRITICAL ROW LIMIT: Every query MUST limit results to {{MAX_ROWS}} rows maximum.** {{ROW_LIMIT_SYNTAX}}. Queries without a row limit will be rejected. For UNION queries, apply the limit to the outermost query or to each branch.
|
|
7117
7341
|
|
|
@@ -7155,7 +7379,6 @@ Your job is to fetch the requested data using the available tool.
|
|
|
7155
7379
|
### Knowledge Base Context
|
|
7156
7380
|
{{KNOWLEDGE_BASE_CONTEXT}}
|
|
7157
7381
|
|
|
7158
|
-
### User Access Scope
|
|
7159
7382
|
{{USER_ACCESS_CONTEXT}}`,
|
|
7160
7383
|
user: `Current Date and Time: {{CURRENT_DATETIME}}
|
|
7161
7384
|
|
|
@@ -9136,9 +9359,9 @@ var PromptLoader = class {
|
|
|
9136
9359
|
const rulesPath = path4.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
|
|
9137
9360
|
try {
|
|
9138
9361
|
if (fs5.existsSync(rulesPath)) {
|
|
9139
|
-
const
|
|
9140
|
-
this.databaseRulesCache.set(this.databaseType,
|
|
9141
|
-
return
|
|
9362
|
+
const rules2 = fs5.readFileSync(rulesPath, "utf-8");
|
|
9363
|
+
this.databaseRulesCache.set(this.databaseType, rules2);
|
|
9364
|
+
return rules2;
|
|
9142
9365
|
}
|
|
9143
9366
|
} catch (error) {
|
|
9144
9367
|
logger.warn(`Could not load database rules for '${this.databaseType}' from file system: ${error}`);
|
|
@@ -9179,9 +9402,9 @@ var PromptLoader = class {
|
|
|
9179
9402
|
const rulesPath = path4.join(this.promptsDir, "database-rules", `${dbType}.md`);
|
|
9180
9403
|
try {
|
|
9181
9404
|
if (fs5.existsSync(rulesPath)) {
|
|
9182
|
-
const
|
|
9183
|
-
this.databaseRulesCache.set(dbType,
|
|
9184
|
-
return
|
|
9405
|
+
const rules2 = fs5.readFileSync(rulesPath, "utf-8");
|
|
9406
|
+
this.databaseRulesCache.set(dbType, rules2);
|
|
9407
|
+
return rules2;
|
|
9185
9408
|
}
|
|
9186
9409
|
} catch (error) {
|
|
9187
9410
|
logger.warn(`Could not load database rules for '${dbType}' from file system: ${error}`);
|
|
@@ -9879,6 +10102,57 @@ function validateAndFixSqlQuery(query, dbType) {
|
|
|
9879
10102
|
};
|
|
9880
10103
|
}
|
|
9881
10104
|
|
|
10105
|
+
// src/userResponse/agents/access-section.ts
|
|
10106
|
+
var HEADING = "### User Access Scope";
|
|
10107
|
+
var enforced = enforcementApplies;
|
|
10108
|
+
var EMPTY_RESULT_RULE = `**An EMPTY result is not evidence of absence.** Zero rows under a restriction usually means the records are outside the user's access, not that they do not exist. Never report it as "no data found", "there are none", or "no records exist" \u2014 say the data is outside their access.`;
|
|
10109
|
+
function mainRule() {
|
|
10110
|
+
return enforced() ? "**These restrictions are applied automatically to every query before it runs.** Do NOT write them into your SQL, into an INTENT you dispatch to a source, or into a saved script \u2014 a scope you add is redundant, and it goes stale the moment this user's access changes. Dispatch the question as the user asked it; the scope is enforced for you.\n**If the user names a value outside this scope** (by name or via a resolved id), do not query it \u2014 say they lack access. For a question mixing in-scope and out-of-scope items (a comparison, say), answer the part you can and state plainly which part you cannot.\n" + EMPTY_RESULT_RULE : "**CRITICAL: Apply this on every question and every dispatch \u2014 it overrides an explicit user request AND a resolved entity id.** If the user names a value outside this scope (by name or via a resolved id), do not query it \u2014 say they lack access. For a question mixing in-scope and out-of-scope items (a comparison, say), answer the part you can and state plainly which part you cannot.\n" + EMPTY_RESULT_RULE;
|
|
10111
|
+
}
|
|
10112
|
+
function sourceRule() {
|
|
10113
|
+
return (enforced() ? "**These restrictions are already applied to every query before it runs.** Do NOT add them to your SQL \u2014 a filter you write is redundant at best, and stale the moment this user's access changes. Write the query as if unrestricted; the scope is enforced for you.\n" : "**CRITICAL: Apply these restrictions on every query \u2014 they override both an explicit user request AND a RESOLVED ENTITIES id.** If a restriction names a value for a dimension (e.g. a location/branch id) and the table you are querying has a column representing that dimension, ALWAYS filter on it, inferring the matching column from the schema. Never use the user's or a resolved id's value where it conflicts. On conflict, refuse and say the user lacks access \u2014 do not fetch.\n") + EMPTY_RESULT_RULE;
|
|
10114
|
+
}
|
|
10115
|
+
function editRule() {
|
|
10116
|
+
return enforced() ? "**CRITICAL: this overrides the edit instruction.** The scope below is applied automatically on every run. If the script hardcodes any of it \u2014 as a SQL predicate or in its text \u2014 REMOVE it: a baked-in scope returns zero rows the moment this user's access changes. Never add one." : '**CRITICAL: this overrides the edit instruction.** A filter that conflicts with the scope below is not "correct as-is" \u2014 fix it regardless of what the edit instruction named.';
|
|
10117
|
+
}
|
|
10118
|
+
var rules = () => ({
|
|
10119
|
+
main: mainRule(),
|
|
10120
|
+
source: sourceRule(),
|
|
10121
|
+
edit: editRule()
|
|
10122
|
+
});
|
|
10123
|
+
function buildAccessSection(renderedConfig, variant) {
|
|
10124
|
+
const body = (renderedConfig || "").trim();
|
|
10125
|
+
if (!body) return "";
|
|
10126
|
+
return `${HEADING}
|
|
10127
|
+
${rules()[variant]}
|
|
10128
|
+
${body}`;
|
|
10129
|
+
}
|
|
10130
|
+
function buildAccessBodyContract(renderedConfig) {
|
|
10131
|
+
if (!(renderedConfig || "").trim()) return "";
|
|
10132
|
+
return `
|
|
10133
|
+
|
|
10134
|
+
### Access policy \u2014 never write it into the script
|
|
10135
|
+
|
|
10136
|
+
A query result may report that an authorization policy restricted it. That restriction is enforced
|
|
10137
|
+
automatically on every run, including replays. So:
|
|
10138
|
+
|
|
10139
|
+
- **Never put a policy predicate in your SQL** (e.g. a branch/tenant/region id you were told was
|
|
10140
|
+
applied). It is redundant, and it goes stale the moment that user's access changes \u2014 the recipe
|
|
10141
|
+
would keep filtering on the old scope while the live policy applies the new one, giving zero rows.
|
|
10142
|
+
- **Never hardcode the scope in text.** \`getAnalysis\` and \`getComponents\` receive the restrictions
|
|
10143
|
+
that actually applied to THIS run as their 6th argument, \`scope: string[]\`. Use it:
|
|
10144
|
+
|
|
10145
|
+
\`\`\`js
|
|
10146
|
+
export function getAnalysis(data, params, fmt, datasets, entities, scope) {
|
|
10147
|
+
const limitedTo = scope.length ? \` (\${scope.join('; ')})\` : '';
|
|
10148
|
+
return { analysis: \`**\${fmt.num(data[0].Total)} records**\${limitedTo}.\` };
|
|
10149
|
+
}
|
|
10150
|
+
\`\`\`
|
|
10151
|
+
|
|
10152
|
+
\`scope\` is empty when no policy applied, so the same body is correct for restricted and
|
|
10153
|
+
unrestricted users alike.`;
|
|
10154
|
+
}
|
|
10155
|
+
|
|
9882
10156
|
// src/userResponse/agents/source-agent.ts
|
|
9883
10157
|
var SourceAgent = class {
|
|
9884
10158
|
constructor(tool, config2, streamBuffer, options) {
|
|
@@ -10065,6 +10339,21 @@ Analyze the error and try again with a corrected query.`;
|
|
|
10065
10339
|
name: this.tool.name,
|
|
10066
10340
|
params: cappedInput,
|
|
10067
10341
|
result: {
|
|
10342
|
+
// The query was restricted before it ran. The agent cannot see
|
|
10343
|
+
// this from the SQL it wrote — it wrote no such restriction — so
|
|
10344
|
+
// without being told it labels the answer as if unrestricted.
|
|
10345
|
+
// Confirmed live: a branch-scoped count reported as covering
|
|
10346
|
+
// every branch, plus a wasted turn trying to remove a
|
|
10347
|
+
// restriction that is re-applied to every query.
|
|
10348
|
+
//
|
|
10349
|
+
// The WORDING is a fixed, generic frame; the restrictions
|
|
10350
|
+
// themselves are whatever the deployment's policy produced.
|
|
10351
|
+
// Every client shares this code, so nothing here may assume a
|
|
10352
|
+
// policy TYPE (row filter, masked column, restricted table) or
|
|
10353
|
+
// any client's vocabulary.
|
|
10354
|
+
...result.metadata?.appliedPolicies?.length ? {
|
|
10355
|
+
_ACCESS_POLICY_APPLIED: `An authorization policy restricted this query before it ran: ${result.metadata.appliedPolicies.join("; ")}. These results are ONLY what this user is permitted to see. State the restriction in titles, analysis and method lines; never present the figures as complete, overall, or covering everything. The policy is re-applied to every query, so rewriting without the restriction returns the same result \u2014 do not retry to widen it.`
|
|
10356
|
+
} : {},
|
|
10068
10357
|
...hitFetchCap ? {
|
|
10069
10358
|
_WARNING_TRUNCATED: `This result FILLED the ${this.config.maxRowsFetched}-row fetch cap \u2014 it is a PARTIAL head slice, NOT the full result. There are almost certainly more rows, and (if ordered) often all from just the first few groups (e.g. the first districts alphabetically). Treat the set of districts/categories/entities and any count/total shown here as INCOMPLETE \u2014 never as the full set.`
|
|
10070
10359
|
} : {},
|
|
@@ -10090,7 +10379,9 @@ Analyze the error and try again with a corrected query.`;
|
|
|
10090
10379
|
const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
|
|
10091
10380
|
const followUpNote = successfulQueries < 2 ? "STOP now and return this data \u2014 it satisfies the intent in the common case. Make a second query ONLY if a column or row the intent explicitly requires is verifiably MISSING from these results (not merely to widen, re-sort, or double-check). A redundant follow-up wastes a full round-trip." : "Maximum queries reached. Use the data you have.";
|
|
10092
10381
|
const capWarning = hitFetchCap ? ` \u26A0\uFE0F This filled the ${this.config.maxRowsFetched}-row fetch cap \u2014 a partial head slice; the groups/values shown are NOT the complete set.` : "";
|
|
10093
|
-
|
|
10382
|
+
const emptyUnderPolicy = resultData.length === 0;
|
|
10383
|
+
const policyNote = result.metadata?.appliedPolicies?.length ? ` \u{1F512} An authorization policy restricted this query before it ran: ${result.metadata.appliedPolicies.join("; ")}. These rows are ONLY what this user may see. ` + (emptyUnderPolicy ? `It returned ZERO rows UNDER THAT RESTRICTION \u2014 that is NOT evidence the records do not exist; they are most likely outside this user's access. Say that; never report it as "no data found" or "there are none". ` : "") + `Work that restriction into your wording naturally (e.g. "\u2026 for <restriction>"); never paste it in as a raw filter expression, and never call the figures complete or overall. The policy re-applies to every query, so re-running without it returns the same rows. Do NOT copy the restriction into your SQL or into a saved script: it is enforced automatically, and a policy written into a script goes stale the moment this user's access changes.` : "";
|
|
10384
|
+
return `\u2705 Query executed successfully. ${resultData.length} rows returned.${capWarning}${policyNote} ${followUpNote}
|
|
10094
10385
|
|
|
10095
10386
|
${formatted}`;
|
|
10096
10387
|
} catch (execError) {
|
|
@@ -10309,7 +10600,7 @@ Even if a table appears in the detailed schema above, search_schema returns samp
|
|
|
10309
10600
|
logger.warn(`[SourceAgent:${this.tool.name}] Trace capture failed for schemaSearchInstructionsText (non-blocking): ${traceErr}`);
|
|
10310
10601
|
}
|
|
10311
10602
|
const displaySourceType = sourceType === "excel" || sourceType === "csv" ? `${sourceType} \u2014 uses DuckDB SQL engine` : sourceType;
|
|
10312
|
-
const userAccessContextText = this.config.userAccessContext
|
|
10603
|
+
const userAccessContextText = buildAccessSection(this.config.userAccessContext ?? "", "source");
|
|
10313
10604
|
try {
|
|
10314
10605
|
if (this.traceSchemaEntry) this.traceSchemaEntry.userAccessContextText = userAccessContextText;
|
|
10315
10606
|
} catch (traceErr) {
|
|
@@ -10498,6 +10789,55 @@ var LineSplitter = class {
|
|
|
10498
10789
|
}
|
|
10499
10790
|
};
|
|
10500
10791
|
|
|
10792
|
+
// src/utils/context-token.ts
|
|
10793
|
+
import { randomBytes } from "crypto";
|
|
10794
|
+
var DEFAULT_TTL_MS = 30 * 6e4;
|
|
10795
|
+
var KEY = Symbol.for("superatom.contextTokens.v1");
|
|
10796
|
+
function registry() {
|
|
10797
|
+
const g = globalThis;
|
|
10798
|
+
let m = g[KEY];
|
|
10799
|
+
if (!m) {
|
|
10800
|
+
m = /* @__PURE__ */ new Map();
|
|
10801
|
+
g[KEY] = m;
|
|
10802
|
+
}
|
|
10803
|
+
return m;
|
|
10804
|
+
}
|
|
10805
|
+
function sweep(now = Date.now()) {
|
|
10806
|
+
const reg = registry();
|
|
10807
|
+
for (const [token, entry] of reg) {
|
|
10808
|
+
if (entry.expiresAt <= now) reg.delete(token);
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
function mintContextToken(ttlMs = DEFAULT_TTL_MS) {
|
|
10812
|
+
const ctx = captureContext();
|
|
10813
|
+
if (!ctx) return void 0;
|
|
10814
|
+
sweep();
|
|
10815
|
+
const token = randomBytes(24).toString("base64url");
|
|
10816
|
+
registry().set(token, { ctx, expiresAt: Date.now() + ttlMs });
|
|
10817
|
+
return token;
|
|
10818
|
+
}
|
|
10819
|
+
function resolveContextToken(token) {
|
|
10820
|
+
if (!token) return void 0;
|
|
10821
|
+
const entry = registry().get(token);
|
|
10822
|
+
if (!entry) return void 0;
|
|
10823
|
+
if (entry.expiresAt <= Date.now()) {
|
|
10824
|
+
registry().delete(token);
|
|
10825
|
+
logger.warn("[ContextToken] Rejected an expired token \u2014 the script outlived its context grant.");
|
|
10826
|
+
return void 0;
|
|
10827
|
+
}
|
|
10828
|
+
return entry.ctx;
|
|
10829
|
+
}
|
|
10830
|
+
function revokeContextToken(token) {
|
|
10831
|
+
if (token) registry().delete(token);
|
|
10832
|
+
}
|
|
10833
|
+
function contextTokenCount() {
|
|
10834
|
+
sweep();
|
|
10835
|
+
return registry().size;
|
|
10836
|
+
}
|
|
10837
|
+
function clearContextTokens() {
|
|
10838
|
+
registry().clear();
|
|
10839
|
+
}
|
|
10840
|
+
|
|
10501
10841
|
// src/userResponse/scripts/script-runner.ts
|
|
10502
10842
|
var fmtConfig;
|
|
10503
10843
|
function setFmtConfig(cfg) {
|
|
@@ -10623,7 +10963,8 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
|
|
|
10623
10963
|
// analysisData: getData produces it
|
|
10624
10964
|
void 0,
|
|
10625
10965
|
// analysisDatasets: ctx.emit produces them
|
|
10626
|
-
options.entities
|
|
10966
|
+
options.entities,
|
|
10967
|
+
options.appliedPolicies
|
|
10627
10968
|
);
|
|
10628
10969
|
const totalMs = Date.now() - startedAt;
|
|
10629
10970
|
if (result.kind === "ok") {
|
|
@@ -10671,10 +11012,11 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
|
|
|
10671
11012
|
scriptSemaphore.release();
|
|
10672
11013
|
}
|
|
10673
11014
|
}
|
|
10674
|
-
function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities) {
|
|
11015
|
+
function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities, appliedPolicies) {
|
|
10675
11016
|
return new Promise((resolve2) => {
|
|
10676
11017
|
const tsxBin = resolveTsxBinary();
|
|
10677
11018
|
const bootstrap = resolveBootstrapPath();
|
|
11019
|
+
const contextToken = mintContextToken();
|
|
10678
11020
|
const child = spawn(
|
|
10679
11021
|
process.execPath,
|
|
10680
11022
|
// node — tsx runs via its own cli, we invoke it with node
|
|
@@ -10693,7 +11035,12 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
|
|
|
10693
11035
|
env: {
|
|
10694
11036
|
...process.env,
|
|
10695
11037
|
// Keep the child quiet about dotenv etc.
|
|
10696
|
-
NODE_NO_WARNINGS: "1"
|
|
11038
|
+
NODE_NO_WARNINGS: "1",
|
|
11039
|
+
// Identity for the bridge calls the script will make. The async
|
|
11040
|
+
// context cannot cross a process boundary, so it travels as a
|
|
11041
|
+
// token instead. Absent when the run has no context — the bridge
|
|
11042
|
+
// then treats the queries as anonymous, which is the truth.
|
|
11043
|
+
...contextToken ? { SA_CONTEXT_TOKEN: contextToken } : {}
|
|
10697
11044
|
}
|
|
10698
11045
|
}
|
|
10699
11046
|
);
|
|
@@ -10701,6 +11048,7 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
|
|
|
10701
11048
|
let executedQueries = [];
|
|
10702
11049
|
let stderrBuffer = "";
|
|
10703
11050
|
const cleanup = () => {
|
|
11051
|
+
revokeContextToken(contextToken);
|
|
10704
11052
|
if (child.killed) return;
|
|
10705
11053
|
const pid = child.pid;
|
|
10706
11054
|
if (pid && process.platform !== "win32") {
|
|
@@ -10805,7 +11153,8 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
|
|
|
10805
11153
|
...analysisData ? { data: analysisData } : {},
|
|
10806
11154
|
...analysisDatasets && Object.keys(analysisDatasets).length ? { datasets: analysisDatasets } : {},
|
|
10807
11155
|
...fmtConfig ? { fmt: fmtConfig } : {},
|
|
10808
|
-
...entities && entities.length ? { entities } : {}
|
|
11156
|
+
...entities && entities.length ? { entities } : {},
|
|
11157
|
+
...appliedPolicies && appliedPolicies.length ? { appliedPolicies } : {}
|
|
10809
11158
|
});
|
|
10810
11159
|
child.stdin.write(init);
|
|
10811
11160
|
function handleChildMessage(msg) {
|
|
@@ -10890,7 +11239,12 @@ function enrichQueries(queries, externalTools) {
|
|
|
10890
11239
|
count: q.count,
|
|
10891
11240
|
totalCount: q.totalCount,
|
|
10892
11241
|
executionTimeMs: q.executionTimeMs,
|
|
10893
|
-
virtual: q.virtual
|
|
11242
|
+
virtual: q.virtual,
|
|
11243
|
+
// Carried through explicitly: this rebuilds each query field by field,
|
|
11244
|
+
// so anything not listed is silently dropped. Losing it here left
|
|
11245
|
+
// getAnalysis's `scope` empty while the query WAS restricted — the
|
|
11246
|
+
// analysis then described a branch-scoped count as system-wide.
|
|
11247
|
+
appliedPolicies: q.appliedPolicies
|
|
10894
11248
|
};
|
|
10895
11249
|
});
|
|
10896
11250
|
}
|
|
@@ -10982,7 +11336,8 @@ async function runComponentsOnly(recipe, scriptPath, params, data, options, data
|
|
|
10982
11336
|
"components",
|
|
10983
11337
|
data,
|
|
10984
11338
|
datasets,
|
|
10985
|
-
options.entities
|
|
11339
|
+
options.entities,
|
|
11340
|
+
options.appliedPolicies
|
|
10986
11341
|
);
|
|
10987
11342
|
if (result.kind === "ok") {
|
|
10988
11343
|
return { components: result.components, componentsError: result.componentsError };
|
|
@@ -11015,7 +11370,8 @@ async function runAnalysisOnly(recipe, scriptPath, params, data, options, datase
|
|
|
11015
11370
|
"analysis",
|
|
11016
11371
|
data,
|
|
11017
11372
|
datasets,
|
|
11018
|
-
options.entities
|
|
11373
|
+
options.entities,
|
|
11374
|
+
options.appliedPolicies
|
|
11019
11375
|
);
|
|
11020
11376
|
if (result.kind === "ok") {
|
|
11021
11377
|
return { analysis: result.analysis, analysisError: result.analysisError };
|
|
@@ -11212,7 +11568,7 @@ function resolveScriptRecipeStore(collections) {
|
|
|
11212
11568
|
// src/userResponse/scripts/script-store.ts
|
|
11213
11569
|
import * as fs6 from "fs";
|
|
11214
11570
|
import * as path6 from "path";
|
|
11215
|
-
import { createHash, randomBytes } from "crypto";
|
|
11571
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
11216
11572
|
var DEFAULT_STORE_DIR = "scripts-store";
|
|
11217
11573
|
function stripAnalysisExport(body) {
|
|
11218
11574
|
return stripNamedExport(body, "getAnalysis");
|
|
@@ -11424,7 +11780,7 @@ var ScriptStore = class {
|
|
|
11424
11780
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11425
11781
|
const existing = input.recipeId ? await this.store.getById(input.recipeId) : null;
|
|
11426
11782
|
const reuse = existing && existing.status === "draft" && existing.turnId === input.turnId;
|
|
11427
|
-
const id = reuse ? existing.id : `script_${Date.now()}_${
|
|
11783
|
+
const id = reuse ? existing.id : `script_${Date.now()}_${randomBytes2(3).toString("hex")}`;
|
|
11428
11784
|
const fileBase = reuse ? existing.fileBase : input.parentId ? `${this.toSlug(input.name)}-${id.slice(-6)}` : await this.computeFileBase(input.name, id);
|
|
11429
11785
|
let normalized = normalizeScriptBody(input.scriptBody);
|
|
11430
11786
|
if (reuse) {
|
|
@@ -11795,6 +12151,32 @@ ${carried.trim()}
|
|
|
11795
12151
|
* specs, a stale spec array beside a live program is how the wrong one gets
|
|
11796
12152
|
* read later. See backend/docs/COMPONENTS-AS-PROGRAM-DESIGN.md § 4.4.
|
|
11797
12153
|
*/
|
|
12154
|
+
/**
|
|
12155
|
+
* Body text as it stands now, for reverting an attach that turns out to be
|
|
12156
|
+
* invalid.
|
|
12157
|
+
*
|
|
12158
|
+
* getComponents can only be VALIDATED by running it, and it can only be run
|
|
12159
|
+
* once it is on disk — so attachComponents necessarily writes before anyone
|
|
12160
|
+
* knows whether the body is good. Without a revert, a rejected body stays
|
|
12161
|
+
* attached: promote() then sees the recipe "exports getComponents", clears
|
|
12162
|
+
* the specs the fallback generator produced, and the recipe is verified with
|
|
12163
|
+
* component code that was already rejected. Confirmed live on
|
|
12164
|
+
* "Total Bookings by Branch", which shipped a sourceRef of 'computed:_final'
|
|
12165
|
+
* that validateSpecs had refused.
|
|
12166
|
+
*/
|
|
12167
|
+
async snapshotBody(recipeId) {
|
|
12168
|
+
const recipe = await this.get(recipeId);
|
|
12169
|
+
if (!recipe?.fileBase) return null;
|
|
12170
|
+
return this.readBody(recipe.fileBase) || null;
|
|
12171
|
+
}
|
|
12172
|
+
/** Put back a body captured by {@link snapshotBody}. */
|
|
12173
|
+
async restoreBody(recipeId, body) {
|
|
12174
|
+
const recipe = await this.get(recipeId);
|
|
12175
|
+
if (!recipe?.fileBase) return false;
|
|
12176
|
+
this.writeBody(recipe.fileBase, body);
|
|
12177
|
+
logger.info(`[ScriptStore] Reverted body of "${recipe.name}" (${recipeId}) after a rejected attach`);
|
|
12178
|
+
return true;
|
|
12179
|
+
}
|
|
11798
12180
|
async attachComponents(recipeId, componentsBody) {
|
|
11799
12181
|
const recipe = await this.get(recipeId);
|
|
11800
12182
|
if (!recipe) {
|
|
@@ -11873,7 +12255,7 @@ ${analysisBody.trim()}
|
|
|
11873
12255
|
writeArchive(fileBase, version, body) {
|
|
11874
12256
|
if (!fs6.existsSync(this.archiveDir)) fs6.mkdirSync(this.archiveDir, { recursive: true });
|
|
11875
12257
|
const finalPath = path6.join(this.archiveDir, `${fileBase}.v${version}.ts`);
|
|
11876
|
-
const tmpPath = `${finalPath}.tmp-${
|
|
12258
|
+
const tmpPath = `${finalPath}.tmp-${randomBytes2(4).toString("hex")}`;
|
|
11877
12259
|
fs6.writeFileSync(tmpPath, body);
|
|
11878
12260
|
fs6.renameSync(tmpPath, finalPath);
|
|
11879
12261
|
}
|
|
@@ -11900,7 +12282,7 @@ ${analysisBody.trim()}
|
|
|
11900
12282
|
writeBody(fileBase, normalizedBody) {
|
|
11901
12283
|
if (!fs6.existsSync(this.storeDir)) fs6.mkdirSync(this.storeDir, { recursive: true });
|
|
11902
12284
|
const finalPath = this.bodyPath(fileBase);
|
|
11903
|
-
const tmpPath = `${finalPath}.tmp-${
|
|
12285
|
+
const tmpPath = `${finalPath}.tmp-${randomBytes2(4).toString("hex")}`;
|
|
11904
12286
|
fs6.writeFileSync(tmpPath, normalizedBody);
|
|
11905
12287
|
fs6.renameSync(tmpPath, finalPath);
|
|
11906
12288
|
}
|
|
@@ -12725,7 +13107,7 @@ ${catalog}`,
|
|
|
12725
13107
|
return "write_components requires data: run write_script then execute_script successfully first.";
|
|
12726
13108
|
}
|
|
12727
13109
|
if (this.renderComponentAttempts >= MAX_RENDER_COMPONENT_ATTEMPTS) {
|
|
12728
|
-
return `Reached the max of ${MAX_RENDER_COMPONENT_ATTEMPTS} write_components attempts. Stop calling it
|
|
13110
|
+
return `Reached the max of ${MAX_RENDER_COMPONENT_ATTEMPTS} write_components attempts. Stop calling it \u2014 the system will build the dashboard. This limit is write_components only; if you have not called write_analysis, call it now.`;
|
|
12729
13111
|
}
|
|
12730
13112
|
this.renderComponentAttempts++;
|
|
12731
13113
|
const hardcodedAggregates = findHardcodedAggregates(body);
|
|
@@ -12753,6 +13135,10 @@ STILL UNCHANGED FROM THE LAST ATTEMPT: ${repeatedList}. Restating the same reaso
|
|
|
12753
13135
|
const recipeId = this.scriptState.recipeId;
|
|
12754
13136
|
const draft = recipeId ? await this.scriptStore.get(recipeId) : null;
|
|
12755
13137
|
if (!recipeId || !draft) return "Draft was lost from the store. Call write_script again.";
|
|
13138
|
+
const snapshot = await this.scriptStore.snapshotBody(recipeId);
|
|
13139
|
+
const revert = async () => {
|
|
13140
|
+
if (snapshot) await this.scriptStore.restoreBody(recipeId, snapshot);
|
|
13141
|
+
};
|
|
12756
13142
|
const attached = await this.scriptStore.attachComponents(recipeId, body);
|
|
12757
13143
|
if (!attached) return "Could not attach getComponents to the draft. Call render-free: write your answer and the system will build the dashboard.";
|
|
12758
13144
|
const datasets = {};
|
|
@@ -12766,12 +13152,13 @@ STILL UNCHANGED FROM THE LAST ATTEMPT: ${repeatedList}. Restating the same reaso
|
|
|
12766
13152
|
this.scriptStore.getScriptPath(draft),
|
|
12767
13153
|
{},
|
|
12768
13154
|
result.data,
|
|
12769
|
-
{ externalTools: this.externalTools, signal: this.abortSignal, entities: this.resolvedEntities },
|
|
13155
|
+
{ externalTools: this.externalTools, signal: this.abortSignal, entities: this.resolvedEntities, appliedPolicies: this.observedPolicies() },
|
|
12770
13156
|
datasets
|
|
12771
13157
|
);
|
|
12772
13158
|
if (componentsError || !components?.length) {
|
|
12773
13159
|
const why = componentsError || "it returned no components";
|
|
12774
13160
|
logger.warn(`[MainAgent] write_components failed: ${why}`);
|
|
13161
|
+
await revert();
|
|
12775
13162
|
return `getComponents failed: ${why}
|
|
12776
13163
|
|
|
12777
13164
|
Fix the body and call write_components again. It receives (data, params, fmt, datasets) and must RETURN an array of component specs.`;
|
|
@@ -12780,6 +13167,14 @@ Fix the body and call write_components again. It receives (data, params, fmt, da
|
|
|
12780
13167
|
if (findings.length > 0) {
|
|
12781
13168
|
logger.warn(`[MainAgent] write_components rejected (attempt ${this.renderComponentAttempts}): ${findings.length} finding(s)`);
|
|
12782
13169
|
findings.forEach((f) => logger.warn(`[MainAgent] ${f.code} ${f.componentType}: ${f.message}`));
|
|
13170
|
+
await revert();
|
|
13171
|
+
if (this.renderComponentAttempts >= MAX_RENDER_COMPONENT_ATTEMPTS && accepted.length > 0) {
|
|
13172
|
+
this.componentSpecs = accepted;
|
|
13173
|
+
this.componentLayout = { title: input?.layoutTitle || "Dashboard", description: input?.layoutDescription };
|
|
13174
|
+
const dropped = findings.map((f) => `${f.componentType} (${f.code})`).join(", ");
|
|
13175
|
+
logger.warn(`[MainAgent] write_components out of attempts \u2014 keeping ${accepted.length} valid spec(s), dropping ${findings.length}: ${dropped}`);
|
|
13176
|
+
return `Out of write_components attempts. Kept ${accepted.length} valid component(s); dropped ${findings.length} (${dropped}). Stop calling write_components and write your answer.`;
|
|
13177
|
+
}
|
|
12783
13178
|
return formatFindingsForAgent(findings);
|
|
12784
13179
|
}
|
|
12785
13180
|
this.componentSpecs = accepted;
|
|
@@ -12795,6 +13190,20 @@ Now write your answer for the user. Do not restate the component list.`;
|
|
|
12795
13190
|
// ============================================
|
|
12796
13191
|
// Script-authoring tool handlers
|
|
12797
13192
|
// ============================================
|
|
13193
|
+
/**
|
|
13194
|
+
* Restrictions the seam reported while this run's queries executed.
|
|
13195
|
+
*
|
|
13196
|
+
* getData does NOT re-run for write_components/write_analysis, so the child
|
|
13197
|
+
* cannot observe these itself — the parent hands them across, exactly as it
|
|
13198
|
+
* does for `entities`.
|
|
13199
|
+
*/
|
|
13200
|
+
observedPolicies() {
|
|
13201
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13202
|
+
for (const q of this.scriptState.lastSuccessfulResult?.executedQueries ?? []) {
|
|
13203
|
+
for (const p of q?.appliedPolicies ?? []) seen.add(p);
|
|
13204
|
+
}
|
|
13205
|
+
return [...seen];
|
|
13206
|
+
}
|
|
12798
13207
|
/**
|
|
12799
13208
|
* Author the answer as a program. Runs after execute_script, so the body is
|
|
12800
13209
|
* written with the result already known and can react to what it found.
|
|
@@ -12828,14 +13237,16 @@ Now write your answer for the user. Do not restate the component list.`;
|
|
|
12828
13237
|
const lintError = formatLintError(violations);
|
|
12829
13238
|
if (lintError) {
|
|
12830
13239
|
const named = violations.map((v) => `"${v.text}"`).join(", ");
|
|
13240
|
+
const budget = `
|
|
13241
|
+
Attempt ${this.scriptState.analysisAttempts} of ${MAX_ANALYSIS_ATTEMPTS}. Fix the value(s) and call write_analysis again \u2014 do not switch to prose while attempts remain.`;
|
|
12831
13242
|
if (this.scriptState.analysisAttempts < MAX_ANALYSIS_ATTEMPTS) {
|
|
12832
13243
|
logger.warn(`[MainAgent] write_analysis rejected \u2014 ${violations.length} hardcoded value(s): ${named}`);
|
|
12833
13244
|
if (repeatedTexts.length > 0) {
|
|
12834
13245
|
const repeatedList = repeatedTexts.map((t) => `"${t}"`).join(", ");
|
|
12835
13246
|
return `${lintError}
|
|
12836
|
-
STILL UNCHANGED FROM THE LAST ATTEMPT: ${repeatedList}. Restating the same reason won't help a second time \u2014 this is almost certainly a parameter or an already-resolved value, not something to compute fresh: if it's an id, use \`params.<name>\` directly; if it's a name, read it from \`data\`/\`datasets\` (e.g. \`datasets.summary?.[0]?.<field>\`). Replace the literal itself, not the wording around it
|
|
13247
|
+
STILL UNCHANGED FROM THE LAST ATTEMPT: ${repeatedList}. Restating the same reason won't help a second time \u2014 this is almost certainly a parameter or an already-resolved value, not something to compute fresh: if it's an id, use \`params.<name>\` directly; if it's a name, read it from \`data\`/\`datasets\` (e.g. \`datasets.summary?.[0]?.<field>\`). Replace the literal itself, not the wording around it.` + budget;
|
|
12837
13248
|
}
|
|
12838
|
-
return lintError;
|
|
13249
|
+
return lintError + budget;
|
|
12839
13250
|
}
|
|
12840
13251
|
logger.warn(`[MainAgent] write_analysis accepted on the final attempt DESPITE ${violations.length} hardcoded value(s): ${named} \u2014 a computed answer with a frozen figure beats LLM prose. Those value(s) will not track the data on replay.`);
|
|
12841
13252
|
}
|
|
@@ -12855,7 +13266,7 @@ STILL UNCHANGED FROM THE LAST ATTEMPT: ${repeatedList}. Restating the same reaso
|
|
|
12855
13266
|
this.scriptStore.getScriptPath(draft),
|
|
12856
13267
|
{},
|
|
12857
13268
|
verified.data,
|
|
12858
|
-
{ externalTools: this.externalTools, signal: this.abortSignal, entities: this.resolvedEntities },
|
|
13269
|
+
{ externalTools: this.externalTools, signal: this.abortSignal, entities: this.resolvedEntities, appliedPolicies: this.observedPolicies() },
|
|
12859
13270
|
datasets
|
|
12860
13271
|
);
|
|
12861
13272
|
if (analysisError || !analysis?.analysis) {
|
|
@@ -13156,11 +13567,26 @@ The verified script is intact. You can still call write_analysis and write_compo
|
|
|
13156
13567
|
*/
|
|
13157
13568
|
buildResolvedEntitiesBlock() {
|
|
13158
13569
|
if (!this.resolvedEntities.length) return "";
|
|
13159
|
-
const
|
|
13570
|
+
const render = (e) => {
|
|
13160
13571
|
const scopeSql = e.attrs?.scopeSql;
|
|
13161
13572
|
const scopePart = typeof scopeSql === "string" && scopeSql.trim() ? ` \u2014 READY-MADE SCOPE (use this directly, do not re-derive it): ${scopeSql}` : "";
|
|
13162
13573
|
return `- "${e.mention}" \u2192 ${e.entityType} id ${e.instanceId} (name in DB: ${e.displayName})${scopePart}`;
|
|
13163
|
-
}
|
|
13574
|
+
};
|
|
13575
|
+
const byMention = /* @__PURE__ */ new Map();
|
|
13576
|
+
for (const e of this.resolvedEntities) {
|
|
13577
|
+
const group = byMention.get(e.mention) ?? [];
|
|
13578
|
+
group.push(e);
|
|
13579
|
+
byMention.set(e.mention, group);
|
|
13580
|
+
}
|
|
13581
|
+
const lines = [];
|
|
13582
|
+
for (const [mention, group] of byMention) {
|
|
13583
|
+
if (group.length === 1) {
|
|
13584
|
+
lines.push(render(group[0]));
|
|
13585
|
+
continue;
|
|
13586
|
+
}
|
|
13587
|
+
lines.push(`- "${mention}" matched ${group.length} entity types at the same confidence \u2014 CHOOSE the one the question means and filter on that id ALONE, never on more than one:`);
|
|
13588
|
+
for (const e of group) lines.push(` ${render(e).slice(2)}`);
|
|
13589
|
+
}
|
|
13164
13590
|
return `
|
|
13165
13591
|
|
|
13166
13592
|
RESOLVED ENTITIES \u2014 these were looked up already. Filter on the ids; do NOT search for them by name. Where a name must appear in output or a comparison, use the "name in DB" spelling EXACTLY as given. Where a READY-MADE SCOPE is given, paste it into the query as-is instead of writing your own join/city-match/lookup for the same thing:
|
|
@@ -13480,9 +13906,25 @@ ${r.body}`).join("\n\n");
|
|
|
13480
13906
|
logger.info(
|
|
13481
13907
|
`[MainAgent] resolve_entities: ${res?.resolved?.length ?? 0} resolved, ${res?.unresolved?.length ?? 0} unresolved, ${Object.keys(res?.entityMap || {}).length} concept(s)`
|
|
13482
13908
|
);
|
|
13909
|
+
const TIE_EPSILON = 0.05;
|
|
13910
|
+
const key = (e) => `${e.entityType}::${e.instanceId}`;
|
|
13911
|
+
const add = (mention, c) => {
|
|
13912
|
+
if (!c?.instanceId) return;
|
|
13913
|
+
if (this.resolvedEntities.some((e) => key(e) === key(c))) return;
|
|
13914
|
+
this.resolvedEntities.push({
|
|
13915
|
+
mention: String(mention ?? ""),
|
|
13916
|
+
entityType: String(c.entityType ?? ""),
|
|
13917
|
+
instanceId: String(c.instanceId),
|
|
13918
|
+
displayName: String(c.displayName ?? mention ?? ""),
|
|
13919
|
+
attrs: c.attrs && typeof c.attrs === "object" ? c.attrs : void 0
|
|
13920
|
+
});
|
|
13921
|
+
};
|
|
13483
13922
|
for (const r of Array.isArray(res?.resolved) ? res.resolved : []) {
|
|
13484
13923
|
if (!r?.instanceId) continue;
|
|
13485
|
-
const
|
|
13924
|
+
for (const a of Array.isArray(r.alternatives) ? r.alternatives : []) {
|
|
13925
|
+
const tied = typeof r.score !== "number" || typeof a?.score !== "number" ? false : r.score - a.score <= TIE_EPSILON;
|
|
13926
|
+
if (tied) add(r.mention, a);
|
|
13927
|
+
}
|
|
13486
13928
|
if (this.resolvedEntities.some((e) => key(e) === key(r))) continue;
|
|
13487
13929
|
this.resolvedEntities.push({
|
|
13488
13930
|
mention: String(r.mention ?? ""),
|
|
@@ -13741,7 +14183,7 @@ ${propLines}` : ""
|
|
|
13741
14183
|
const scriptBodyContract = await promptLoader.loadScriptBodyContract({
|
|
13742
14184
|
MAX_ROWS_RENDERED: String(MAX_ROWS_RENDERED),
|
|
13743
14185
|
MAX_ROWS_FETCHED: String(MAX_ROWS_FETCHED)
|
|
13744
|
-
});
|
|
14186
|
+
}) + buildAccessBodyContract(this.config.userAccessContext ?? "");
|
|
13745
14187
|
try {
|
|
13746
14188
|
if (this.config.trace) this.config.trace.scriptBodyContractText = scriptBodyContract;
|
|
13747
14189
|
} catch (traceErr) {
|
|
@@ -13759,7 +14201,9 @@ ${propLines}` : ""
|
|
|
13759
14201
|
GLOBAL_KNOWLEDGE_BASE: this.config.globalKnowledgeBase || "No global knowledge base available.",
|
|
13760
14202
|
CURRENT_DATETIME: getCurrentDateTimeForPrompt(),
|
|
13761
14203
|
KNOWLEDGE_BASE_CONTEXT: this.config.knowledgeBaseContext || "No additional knowledge base context available.",
|
|
13762
|
-
|
|
14204
|
+
// '' when the user has no policy — the whole section disappears rather
|
|
14205
|
+
// than rendering a heading over "no restrictions". See access-section.ts.
|
|
14206
|
+
USER_ACCESS_CONTEXT: buildAccessSection(this.config.userAccessContext ?? "", this.editMode ? "edit" : "main"),
|
|
13763
14207
|
ENTITY_CATALOG: entityCatalogText || "No entity catalog available \u2014 resolve_entities may still be called with mentions.",
|
|
13764
14208
|
CONCEPT_CATALOG: conceptCatalogText || "No concept catalog available \u2014 derive definitions from the schema and say that you did.",
|
|
13765
14209
|
// Bodies, not names: an invariant is injected precisely because it
|
|
@@ -13964,8 +14408,20 @@ Execution time: ${metadata.executionTimeMs}ms
|
|
|
13964
14408
|
rows: t?.result?._mainAgentRows ?? t?.result?._sampleData ?? [],
|
|
13965
14409
|
summary: t?.result?._summary,
|
|
13966
14410
|
total: t?.result?._totalRecords,
|
|
13967
|
-
shown: t?.result?._recordsShown
|
|
14411
|
+
shown: t?.result?._recordsShown,
|
|
14412
|
+
policy: t?.result?._ACCESS_POLICY_APPLIED
|
|
13968
14413
|
})).filter((q) => Boolean(q.sql));
|
|
14414
|
+
const policyNotes = [...new Set(queries.map((q) => q.policy).filter(Boolean))];
|
|
14415
|
+
if (policyNotes.length) {
|
|
14416
|
+
output += `
|
|
14417
|
+
\u{1F512} ACCESS POLICY \u2014 applies to EVERY figure below:
|
|
14418
|
+
${policyNotes.map((n) => `- ${n}`).join("\n")}
|
|
14419
|
+
Reflect this in the answer: name the restriction in component titles, the analysis, and the method line \u2014 phrased naturally as part of the sentence, never pasted in as a raw filter expression. Do not describe these numbers as complete, overall, or covering everything.
|
|
14420
|
+
If a query came back EMPTY, that is NOT evidence the records do not exist \u2014 under a restriction it usually means they are outside this user's access. Say so; never write "no data found" or "there are none".
|
|
14421
|
+
Do NOT copy the restriction into the script's SQL: it is enforced automatically on every run, and a value written into a recipe goes stale the moment this user's access changes. getAnalysis and getComponents receive what was actually applied as their 6th argument, \`scope: string[]\` \u2014 use that to name it, in BOTH functions.
|
|
14422
|
+
|
|
14423
|
+
`;
|
|
14424
|
+
}
|
|
13969
14425
|
if (queries.length === 0) {
|
|
13970
14426
|
output += "No data returned.";
|
|
13971
14427
|
return output;
|
|
@@ -17404,16 +17860,37 @@ function storeScriptDatasetQuery(q, ctx) {
|
|
|
17404
17860
|
return queryCache.storeQuery(q.sql, { data, count });
|
|
17405
17861
|
}
|
|
17406
17862
|
function buildUserAccessContext(userConfig) {
|
|
17407
|
-
|
|
17408
|
-
const
|
|
17409
|
-
const
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17863
|
+
const entries = Array.isArray(userConfig) ? userConfig : [userConfig];
|
|
17864
|
+
const restrictions = [];
|
|
17865
|
+
const prompts = [];
|
|
17866
|
+
let anyRestriction = false;
|
|
17867
|
+
for (const e of entries) {
|
|
17868
|
+
if (!e || typeof e !== "object") continue;
|
|
17869
|
+
const rowFilters = Array.isArray(e.rowFilters) ? e.rowFilters : [];
|
|
17870
|
+
const readableTables = Array.isArray(e.readableTables) ? e.readableTables.filter((t) => typeof t === "string" && !!t) : [];
|
|
17871
|
+
const realFilters = rowFilters.filter(
|
|
17872
|
+
(f) => f && typeof f === "object" && f.column && Array.isArray(f.values) && f.values.length > 0
|
|
17873
|
+
);
|
|
17874
|
+
if (realFilters.length || readableTables.length) {
|
|
17875
|
+
anyRestriction = true;
|
|
17876
|
+
if (enforcementApplies()) {
|
|
17877
|
+
const clean = (v) => typeof v === "string" ? v.replace(/\s+/g, " ").trim() : "";
|
|
17878
|
+
const labels = [
|
|
17879
|
+
.../* @__PURE__ */ new Set([
|
|
17880
|
+
...realFilters.map((f) => clean(f.label)).filter(Boolean),
|
|
17881
|
+
...clean(e.label) ? [clean(e.label)] : []
|
|
17882
|
+
])
|
|
17883
|
+
];
|
|
17884
|
+
restrictions.push(labels.length ? labels.join("; ") : "This user's data access is restricted.");
|
|
17885
|
+
} else {
|
|
17886
|
+
const { prompt: _p, ...rest } = e;
|
|
17887
|
+
restrictions.push(JSON.stringify(rest));
|
|
17888
|
+
}
|
|
17889
|
+
}
|
|
17890
|
+
if (typeof e.prompt === "string" && e.prompt.trim()) prompts.push(e.prompt.trim());
|
|
17415
17891
|
}
|
|
17416
|
-
return
|
|
17892
|
+
if (!anyRestriction && prompts.length === 0) return "";
|
|
17893
|
+
return [...restrictions, ...prompts].join("\n\n");
|
|
17417
17894
|
}
|
|
17418
17895
|
var get_agent_user_response = async (prompt, components, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, conversationHistory, streamCallback, collections, externalTools, userId, mainAgentModel, sourceAgentModel, workflows, signal, activeBindings, userConfig) => {
|
|
17419
17896
|
const startTime = Date.now();
|
|
@@ -19176,7 +19653,7 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19176
19653
|
};
|
|
19177
19654
|
};
|
|
19178
19655
|
async function handleUserPromptRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools, mainAgentModel, sourceAgentModel, workflows) {
|
|
19179
|
-
const response = await get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools, mainAgentModel, sourceAgentModel, workflows);
|
|
19656
|
+
const response = await runWithMessageIdentity(data, () => get_user_request(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, externalTools, mainAgentModel, sourceAgentModel, workflows));
|
|
19180
19657
|
if (response.data?.component?.props?.config?.components) {
|
|
19181
19658
|
response.data.component.props.config.components = response.data.component.props.config.components.map((comp) => ({
|
|
19182
19659
|
...comp,
|
|
@@ -22277,9 +22754,9 @@ async function loadDatabaseRulesForTools(tools) {
|
|
|
22277
22754
|
}
|
|
22278
22755
|
const sections = await Promise.all(
|
|
22279
22756
|
dbTypes.map(async (dbType) => {
|
|
22280
|
-
const
|
|
22757
|
+
const rules2 = await promptLoader.loadDatabaseRulesForType(dbType);
|
|
22281
22758
|
return `## ${dbType.toUpperCase()} Rules (apply ONLY to ${dbType} tools \u2014 check each tool's ID prefix)
|
|
22282
|
-
${
|
|
22759
|
+
${rules2}`;
|
|
22283
22760
|
})
|
|
22284
22761
|
);
|
|
22285
22762
|
return sections.join("\n\n---\n\n");
|
|
@@ -23330,7 +23807,7 @@ var processDashCompRequest = async (data, components, _sendMessage, anthropicApi
|
|
|
23330
23807
|
};
|
|
23331
23808
|
};
|
|
23332
23809
|
async function handleDashCompRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, dashCompModels) {
|
|
23333
|
-
const response = await processDashCompRequest(
|
|
23810
|
+
const response = await runWithMessageIdentity(data, () => processDashCompRequest(
|
|
23334
23811
|
data,
|
|
23335
23812
|
components,
|
|
23336
23813
|
sendMessage,
|
|
@@ -23342,7 +23819,7 @@ async function handleDashCompRequest(data, components, sendMessage, anthropicApi
|
|
|
23342
23819
|
collections,
|
|
23343
23820
|
tools,
|
|
23344
23821
|
dashCompModels
|
|
23345
|
-
);
|
|
23822
|
+
));
|
|
23346
23823
|
sendDashCompResponse(
|
|
23347
23824
|
response.id || data.id,
|
|
23348
23825
|
{
|
|
@@ -25324,7 +25801,7 @@ var processReportCompRequest = async (data, components, _sendMessage, anthropicA
|
|
|
25324
25801
|
};
|
|
25325
25802
|
};
|
|
25326
25803
|
async function handleReportCompRequest(data, components, sendMessage, anthropicApiKey, groqApiKey, geminiApiKey, openaiApiKey, llmProviders, collections, tools, modelConfig) {
|
|
25327
|
-
const response = await processReportCompRequest(
|
|
25804
|
+
const response = await runWithMessageIdentity(data, () => processReportCompRequest(
|
|
25328
25805
|
data,
|
|
25329
25806
|
components,
|
|
25330
25807
|
sendMessage,
|
|
@@ -25336,7 +25813,7 @@ async function handleReportCompRequest(data, components, sendMessage, anthropicA
|
|
|
25336
25813
|
collections,
|
|
25337
25814
|
tools,
|
|
25338
25815
|
modelConfig
|
|
25339
|
-
);
|
|
25816
|
+
));
|
|
25340
25817
|
sendReportCompResponse(
|
|
25341
25818
|
response.id || data.id,
|
|
25342
25819
|
{
|
|
@@ -27988,6 +28465,7 @@ export {
|
|
|
27988
28465
|
DEFAULT_AGENT_CONFIG,
|
|
27989
28466
|
LLM,
|
|
27990
28467
|
MainAgent,
|
|
28468
|
+
MissingRequestContextError,
|
|
27991
28469
|
STORAGE_CONFIG,
|
|
27992
28470
|
ScriptMatcher,
|
|
27993
28471
|
ScriptStore,
|
|
@@ -27999,36 +28477,53 @@ export {
|
|
|
27999
28477
|
UserManager,
|
|
28000
28478
|
anthropicLLM,
|
|
28001
28479
|
assertReadOnlySql,
|
|
28480
|
+
bindContext,
|
|
28481
|
+
captureContext,
|
|
28482
|
+
clearContextTokens,
|
|
28483
|
+
contextTokenCount,
|
|
28002
28484
|
createCodexSession,
|
|
28003
28485
|
createCodingAgentSession,
|
|
28004
28486
|
dashboardConversationHistory,
|
|
28005
28487
|
dataFingerprint,
|
|
28488
|
+
describeContext,
|
|
28006
28489
|
describeTriggers,
|
|
28007
28490
|
emitAgentEvent,
|
|
28008
28491
|
extractNamedExport,
|
|
28009
28492
|
geminiLLM,
|
|
28010
28493
|
getCodingAgent,
|
|
28011
28494
|
getCodingAgentsConfig,
|
|
28495
|
+
getRequestContext,
|
|
28012
28496
|
groqLLM,
|
|
28013
28497
|
hybridRerank,
|
|
28498
|
+
isUserContext,
|
|
28014
28499
|
listCodingAgents,
|
|
28015
28500
|
llmUsageLogger,
|
|
28016
28501
|
logger,
|
|
28502
|
+
mintContextToken,
|
|
28017
28503
|
normalizeScriptBody,
|
|
28018
28504
|
onAgentRun,
|
|
28019
28505
|
openaiLLM,
|
|
28506
|
+
policiesFromConfig,
|
|
28020
28507
|
queryCache,
|
|
28021
28508
|
registerCodingAgentCollection,
|
|
28022
28509
|
registerCodingAgents,
|
|
28023
28510
|
registerDashboardAgentCollection,
|
|
28511
|
+
requireRequestContext,
|
|
28024
28512
|
rerankChromaResults,
|
|
28025
28513
|
rerankConversationResults,
|
|
28514
|
+
resolveContextToken,
|
|
28026
28515
|
resolveScriptRecipeStore,
|
|
28516
|
+
revokeContextToken,
|
|
28517
|
+
runAsSystem,
|
|
28518
|
+
runAsUser,
|
|
28027
28519
|
runCodingAgent,
|
|
28028
28520
|
runScript,
|
|
28521
|
+
runWithContext,
|
|
28522
|
+
runWithMessageIdentity,
|
|
28029
28523
|
startAgentTriggers,
|
|
28030
28524
|
stopAgentTriggers,
|
|
28031
28525
|
userPromptErrorLogger,
|
|
28526
|
+
withCapturedContext,
|
|
28032
28527
|
writeBridgeShims
|
|
28033
28528
|
};
|
|
28034
28529
|
//# sourceMappingURL=index.mjs.map
|