@superatomai/sdk-node 0.0.69-dsp → 0.0.70-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 +130 -50
- package/dist/index.d.ts +130 -50
- package/dist/index.js +754 -528
- package/dist/index.mjs +810 -584
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -282,7 +282,13 @@ var UserPromptRequestPayloadSchema = z3.object({
|
|
|
282
282
|
threadId: z3.string(),
|
|
283
283
|
uiBlockId: z3.string()
|
|
284
284
|
}).optional(),
|
|
285
|
-
responseMode: z3.enum(["component", "text"]).optional()
|
|
285
|
+
responseMode: z3.enum(["component", "text"]).optional(),
|
|
286
|
+
/**
|
|
287
|
+
* Where the question was asked from — e.g. the dashboard being viewed. See
|
|
288
|
+
* `PromptContext`. Accepted as anything so a client sending an unexpected shape
|
|
289
|
+
* is never rejected; the handler keeps only the fields it recognises.
|
|
290
|
+
*/
|
|
291
|
+
context: z3.unknown().optional()
|
|
286
292
|
});
|
|
287
293
|
var MessageAuthContextSchema = z3.object({
|
|
288
294
|
userId: z3.string(),
|
|
@@ -989,9 +995,25 @@ var SchemaResponseMessageSchema = z3.object({
|
|
|
989
995
|
|
|
990
996
|
// src/utils/logger.ts
|
|
991
997
|
import fs from "fs";
|
|
998
|
+
import path from "path";
|
|
992
999
|
var PREFIX = "[SuperatomSDK]";
|
|
993
|
-
var
|
|
994
|
-
var
|
|
1000
|
+
var LEGACY_LOG_FILE_PATH = "superatom-sdk.log";
|
|
1001
|
+
var LOG_DIR = "logs";
|
|
1002
|
+
function slugifyLabel(label) {
|
|
1003
|
+
return label.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 60);
|
|
1004
|
+
}
|
|
1005
|
+
function buildLogFilePath(label) {
|
|
1006
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1007
|
+
const slug2 = label ? slugifyLabel(label) : "";
|
|
1008
|
+
const filename = slug2 ? `sdk-${stamp}-${slug2}.log` : `sdk-${stamp}.log`;
|
|
1009
|
+
return path.join(LOG_DIR, filename);
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
var currentLogFilePath = LEGACY_LOG_FILE_PATH;
|
|
1016
|
+
var LOGSTREAM = fs.createWriteStream(LEGACY_LOG_FILE_PATH, { flags: "a" });
|
|
995
1017
|
var LOG_LEVEL_PRIORITY = {
|
|
996
1018
|
errors: 0,
|
|
997
1019
|
warnings: 1,
|
|
@@ -1076,17 +1098,31 @@ var Logger = class {
|
|
|
1076
1098
|
}
|
|
1077
1099
|
}
|
|
1078
1100
|
/**
|
|
1079
|
-
* Write to log file
|
|
1101
|
+
* Write to log file — each line timestamped so a request that made many
|
|
1102
|
+
* calls (LLM turns, tool dispatches, direct-tool runs) can be read back in
|
|
1103
|
+
* order, matching "know what happened when" rather than just "what
|
|
1104
|
+
* happened".
|
|
1080
1105
|
*/
|
|
1081
1106
|
file(...args) {
|
|
1082
|
-
LOGSTREAM.write(args.join(" ")
|
|
1107
|
+
LOGSTREAM.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${args.join(" ")}
|
|
1108
|
+
`);
|
|
1109
|
+
}
|
|
1110
|
+
/** The log file the current request is writing to — print/return this so it's easy to find afterward. */
|
|
1111
|
+
getCurrentLogFilePath() {
|
|
1112
|
+
return currentLogFilePath;
|
|
1083
1113
|
}
|
|
1084
1114
|
/**
|
|
1085
|
-
*
|
|
1115
|
+
* Start a fresh, uniquely-named log file for a new top-level request
|
|
1116
|
+
* (chat turn, dashComp request, reportComp request, ...) instead of
|
|
1117
|
+
* truncating one shared file — pass a short label (a prompt snippet, a
|
|
1118
|
+
* request type) so the resulting filename is identifiable at a glance.
|
|
1119
|
+
* Falls back to the untimestamped legacy path when `label` is omitted, for
|
|
1120
|
+
* any caller not yet updated to pass one.
|
|
1086
1121
|
*/
|
|
1087
|
-
clearFile() {
|
|
1122
|
+
clearFile(label) {
|
|
1088
1123
|
LOGSTREAM.end();
|
|
1089
|
-
|
|
1124
|
+
currentLogFilePath = label ? buildLogFilePath(label) : LEGACY_LOG_FILE_PATH;
|
|
1125
|
+
LOGSTREAM = fs.createWriteStream(currentLogFilePath, { flags: "w" });
|
|
1090
1126
|
LOGSTREAM.write(`
|
|
1091
1127
|
${"=".repeat(80)}
|
|
1092
1128
|
`);
|
|
@@ -1095,6 +1131,7 @@ ${"=".repeat(80)}
|
|
|
1095
1131
|
LOGSTREAM.write(`${"=".repeat(80)}
|
|
1096
1132
|
|
|
1097
1133
|
`);
|
|
1134
|
+
this.info(`Logging this request to ${currentLogFilePath}`);
|
|
1098
1135
|
}
|
|
1099
1136
|
/**
|
|
1100
1137
|
* Log LLM method prompts with clear labeling
|
|
@@ -1102,7 +1139,7 @@ ${"=".repeat(80)}
|
|
|
1102
1139
|
logLLMPrompt(methodName, promptType, content) {
|
|
1103
1140
|
const header = `
|
|
1104
1141
|
${"#".repeat(80)}
|
|
1105
|
-
[LLM METHOD: ${methodName}] - ${promptType.toUpperCase()} PROMPT
|
|
1142
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] [LLM METHOD: ${methodName}] - ${promptType.toUpperCase()} PROMPT
|
|
1106
1143
|
${"#".repeat(80)}
|
|
1107
1144
|
`;
|
|
1108
1145
|
LOGSTREAM.write(header);
|
|
@@ -1827,6 +1864,7 @@ var QueryCache = class {
|
|
|
1827
1864
|
this.maxCacheSize = 5e3;
|
|
1828
1865
|
// Max data cache entries
|
|
1829
1866
|
this.cleanupInterval = null;
|
|
1867
|
+
this.resultCacheEnabled = true;
|
|
1830
1868
|
// Encryption for queryId tokens
|
|
1831
1869
|
this.algorithm = "aes-256-gcm";
|
|
1832
1870
|
const keySource = process.env.QUERY_TOKEN_SECRET || "superatom-query-cache-default-key-v1";
|
|
@@ -1850,6 +1888,15 @@ var QueryCache = class {
|
|
|
1850
1888
|
getTTL() {
|
|
1851
1889
|
return this.ttlMs / 60 / 1e3;
|
|
1852
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Stop keeping result data in memory, for a host that caches results itself.
|
|
1893
|
+
* `set` stores nothing and `get` finds nothing; queryId tokens are unaffected.
|
|
1894
|
+
*/
|
|
1895
|
+
disableResultCache() {
|
|
1896
|
+
this.resultCacheEnabled = false;
|
|
1897
|
+
this.cache.clear();
|
|
1898
|
+
logger.info("[QueryCache] Result cache disabled \u2014 the host caches query results");
|
|
1899
|
+
}
|
|
1853
1900
|
/**
|
|
1854
1901
|
* Which access bucket this call reads and writes.
|
|
1855
1902
|
*
|
|
@@ -1882,6 +1929,7 @@ var QueryCache = class {
|
|
|
1882
1929
|
* a genuinely new key past the size limit.
|
|
1883
1930
|
*/
|
|
1884
1931
|
set(query, data) {
|
|
1932
|
+
if (!this.resultCacheEnabled) return;
|
|
1885
1933
|
const key = this.scopedKey(query);
|
|
1886
1934
|
if (key === null) return;
|
|
1887
1935
|
this.cache.delete(key);
|
|
@@ -1902,6 +1950,7 @@ var QueryCache = class {
|
|
|
1902
1950
|
* iteration order — turning FIFO eviction into true LRU.
|
|
1903
1951
|
*/
|
|
1904
1952
|
get(query) {
|
|
1953
|
+
if (!this.resultCacheEnabled) return null;
|
|
1905
1954
|
const key = this.scopedKey(query);
|
|
1906
1955
|
if (key === null) return null;
|
|
1907
1956
|
const entry = this.cache.get(key);
|
|
@@ -2680,7 +2729,7 @@ function sendDataResponse(id, collection, op, data, meta, sendMessage) {
|
|
|
2680
2729
|
|
|
2681
2730
|
// src/bundle.ts
|
|
2682
2731
|
import * as fs2 from "fs";
|
|
2683
|
-
import * as
|
|
2732
|
+
import * as path2 from "path";
|
|
2684
2733
|
function getBundleDir(configDir) {
|
|
2685
2734
|
const bundleDir = configDir || process.env.SA_BUNDLE_DIR;
|
|
2686
2735
|
if (!bundleDir) {
|
|
@@ -2713,7 +2762,7 @@ function getJS(bundleDir) {
|
|
|
2713
2762
|
`Could not find index-*.js file in ${bundleDir}. Expected a file matching pattern: index-*.js`
|
|
2714
2763
|
);
|
|
2715
2764
|
}
|
|
2716
|
-
const filePath =
|
|
2765
|
+
const filePath = path2.join(bundleDir, indexFile);
|
|
2717
2766
|
logger.info(`Loading bundle from ${filePath}`);
|
|
2718
2767
|
try {
|
|
2719
2768
|
return fs2.readFileSync(filePath, "utf8");
|
|
@@ -3152,12 +3201,12 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
|
|
|
3152
3201
|
|
|
3153
3202
|
// src/utils/user-prompt-error-logger.ts
|
|
3154
3203
|
import fs3 from "fs";
|
|
3155
|
-
import
|
|
3204
|
+
import path3 from "path";
|
|
3156
3205
|
var UserPromptErrorLogger = class {
|
|
3157
3206
|
constructor() {
|
|
3158
3207
|
this.logStream = null;
|
|
3159
3208
|
this.hasErrors = false;
|
|
3160
|
-
this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH ||
|
|
3209
|
+
this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || path3.join(process.cwd(), "user-prompt-req-errors");
|
|
3161
3210
|
this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
|
|
3162
3211
|
}
|
|
3163
3212
|
/**
|
|
@@ -3170,7 +3219,7 @@ var UserPromptErrorLogger = class {
|
|
|
3170
3219
|
this.logStream.end();
|
|
3171
3220
|
this.logStream = null;
|
|
3172
3221
|
}
|
|
3173
|
-
const dir =
|
|
3222
|
+
const dir = path3.dirname(this.logPath);
|
|
3174
3223
|
if (dir !== "." && !fs3.existsSync(dir)) {
|
|
3175
3224
|
fs3.mkdirSync(dir, { recursive: true });
|
|
3176
3225
|
}
|
|
@@ -3650,7 +3699,8 @@ var findExactMatch = async ({
|
|
|
3650
3699
|
userPrompt,
|
|
3651
3700
|
collections,
|
|
3652
3701
|
userId,
|
|
3653
|
-
maxAgeHours
|
|
3702
|
+
maxAgeHours,
|
|
3703
|
+
statuses
|
|
3654
3704
|
}) => {
|
|
3655
3705
|
try {
|
|
3656
3706
|
if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["exactMatch"]) {
|
|
@@ -3660,7 +3710,8 @@ var findExactMatch = async ({
|
|
|
3660
3710
|
const result = await collections["conversation-history"]["exactMatch"]({
|
|
3661
3711
|
userPrompt,
|
|
3662
3712
|
userId,
|
|
3663
|
-
maxAgeHours
|
|
3713
|
+
maxAgeHours,
|
|
3714
|
+
statuses
|
|
3664
3715
|
});
|
|
3665
3716
|
if (!result || !result.uiBlock) {
|
|
3666
3717
|
logger.info("[ConversationSearch] No exact match found");
|
|
@@ -3823,7 +3874,7 @@ var TaggedStreamBuffer = class extends StreamBuffer {
|
|
|
3823
3874
|
}
|
|
3824
3875
|
};
|
|
3825
3876
|
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
3826
|
-
return new Promise((
|
|
3877
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3827
3878
|
}
|
|
3828
3879
|
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
3829
3880
|
if (!streamBuffer.hasCallback()) {
|
|
@@ -4016,7 +4067,7 @@ import { jsonrepair } from "jsonrepair";
|
|
|
4016
4067
|
|
|
4017
4068
|
// src/utils/llm-usage-logger.ts
|
|
4018
4069
|
import fs4 from "fs";
|
|
4019
|
-
import
|
|
4070
|
+
import path4 from "path";
|
|
4020
4071
|
var PRICING = {
|
|
4021
4072
|
// Anthropic (December 2025)
|
|
4022
4073
|
"claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
@@ -4080,7 +4131,7 @@ var LLMUsageLogger = class {
|
|
|
4080
4131
|
totalCostUSD: 0,
|
|
4081
4132
|
totalDurationMs: 0
|
|
4082
4133
|
};
|
|
4083
|
-
this.logPath = process.env.LLM_USAGE_LOG_PATH ||
|
|
4134
|
+
this.logPath = process.env.LLM_USAGE_LOG_PATH || path4.join(process.cwd(), "llm-usage-logs.log");
|
|
4084
4135
|
this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
|
|
4085
4136
|
if (this.enabled) {
|
|
4086
4137
|
this.initLogStream();
|
|
@@ -4088,7 +4139,7 @@ var LLMUsageLogger = class {
|
|
|
4088
4139
|
}
|
|
4089
4140
|
initLogStream() {
|
|
4090
4141
|
try {
|
|
4091
|
-
const dir =
|
|
4142
|
+
const dir = path4.dirname(this.logPath);
|
|
4092
4143
|
if (!fs4.existsSync(dir)) {
|
|
4093
4144
|
fs4.mkdirSync(dir, { recursive: true });
|
|
4094
4145
|
}
|
|
@@ -4754,7 +4805,7 @@ var LLM = class {
|
|
|
4754
4805
|
iterations++;
|
|
4755
4806
|
const iterationStartTime = Date.now();
|
|
4756
4807
|
const requestId = llmUsageLogger.generateRequestId();
|
|
4757
|
-
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? options.firstIterationToolChoice : void 0;
|
|
4808
|
+
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? options.firstIterationToolChoice : iterations === 2 && options.secondIterationToolChoice ? options.secondIterationToolChoice : void 0;
|
|
4758
4809
|
let stream;
|
|
4759
4810
|
try {
|
|
4760
4811
|
stream = await client.messages.create({
|
|
@@ -5712,7 +5763,7 @@ var LLM = class {
|
|
|
5712
5763
|
const iterStart = Date.now();
|
|
5713
5764
|
const requestId = llmUsageLogger.generateRequestId();
|
|
5714
5765
|
let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0;
|
|
5715
|
-
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? this._openaiToolChoice(options.firstIterationToolChoice) : void 0;
|
|
5766
|
+
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? this._openaiToolChoice(options.firstIterationToolChoice) : iterations === 2 && options.secondIterationToolChoice ? this._openaiToolChoice(options.secondIterationToolChoice) : void 0;
|
|
5716
5767
|
const createParams = {
|
|
5717
5768
|
model: modelName,
|
|
5718
5769
|
messages: conversationMessages,
|
|
@@ -5903,7 +5954,7 @@ var LLM = class {
|
|
|
5903
5954
|
|
|
5904
5955
|
// src/userResponse/prompt-loader.ts
|
|
5905
5956
|
import fs5 from "fs";
|
|
5906
|
-
import
|
|
5957
|
+
import path5 from "path";
|
|
5907
5958
|
|
|
5908
5959
|
// src/userResponse/prompts.ts
|
|
5909
5960
|
var PROMPTS = {
|
|
@@ -9317,7 +9368,7 @@ var PromptLoader = class {
|
|
|
9317
9368
|
this.databaseRulesCache = /* @__PURE__ */ new Map();
|
|
9318
9369
|
this.isInitialized = false;
|
|
9319
9370
|
this.databaseType = "postgresql";
|
|
9320
|
-
this.promptsDir = config2?.promptsDir ||
|
|
9371
|
+
this.promptsDir = config2?.promptsDir || path5.join(process.cwd(), ".prompts");
|
|
9321
9372
|
}
|
|
9322
9373
|
/**
|
|
9323
9374
|
* Load a prompt template from file system OR fallback to hardcoded prompts
|
|
@@ -9326,8 +9377,8 @@ var PromptLoader = class {
|
|
|
9326
9377
|
*/
|
|
9327
9378
|
loadPromptTemplate(promptName) {
|
|
9328
9379
|
try {
|
|
9329
|
-
const systemPath =
|
|
9330
|
-
const userPath =
|
|
9380
|
+
const systemPath = path5.join(this.promptsDir, promptName, "system.md");
|
|
9381
|
+
const userPath = path5.join(this.promptsDir, promptName, "user.md");
|
|
9331
9382
|
if (fs5.existsSync(systemPath) && fs5.existsSync(userPath)) {
|
|
9332
9383
|
const system = fs5.readFileSync(systemPath, "utf-8");
|
|
9333
9384
|
const user = fs5.readFileSync(userPath, "utf-8");
|
|
@@ -9477,7 +9528,7 @@ var PromptLoader = class {
|
|
|
9477
9528
|
if (this.databaseRulesCache.has(this.databaseType)) {
|
|
9478
9529
|
return this.databaseRulesCache.get(this.databaseType);
|
|
9479
9530
|
}
|
|
9480
|
-
const rulesPath =
|
|
9531
|
+
const rulesPath = path5.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
|
|
9481
9532
|
try {
|
|
9482
9533
|
if (fs5.existsSync(rulesPath)) {
|
|
9483
9534
|
const rules2 = fs5.readFileSync(rulesPath, "utf-8");
|
|
@@ -9503,6 +9554,8 @@ var PromptLoader = class {
|
|
|
9503
9554
|
"postgres": "postgresql",
|
|
9504
9555
|
"postgresql": "postgresql",
|
|
9505
9556
|
"mssql": "mssql",
|
|
9557
|
+
"fabric": "mssql",
|
|
9558
|
+
// Microsoft Fabric SQL endpoints speak T-SQL
|
|
9506
9559
|
"mysql": "postgresql",
|
|
9507
9560
|
// MySQL uses similar rules to PostgreSQL
|
|
9508
9561
|
"excel": "duckdb",
|
|
@@ -9520,7 +9573,7 @@ var PromptLoader = class {
|
|
|
9520
9573
|
if (this.databaseRulesCache.has(dbType)) {
|
|
9521
9574
|
return this.databaseRulesCache.get(dbType);
|
|
9522
9575
|
}
|
|
9523
|
-
const rulesPath =
|
|
9576
|
+
const rulesPath = path5.join(this.promptsDir, "database-rules", `${dbType}.md`);
|
|
9524
9577
|
try {
|
|
9525
9578
|
if (fs5.existsSync(rulesPath)) {
|
|
9526
9579
|
const rules2 = fs5.readFileSync(rulesPath, "utf-8");
|
|
@@ -9550,7 +9603,7 @@ var PromptLoader = class {
|
|
|
9550
9603
|
const cacheKey = "__script_body_contract__";
|
|
9551
9604
|
let raw = this.databaseRulesCache.get(cacheKey);
|
|
9552
9605
|
if (raw === void 0) {
|
|
9553
|
-
const contractPath =
|
|
9606
|
+
const contractPath = path5.join(this.promptsDir, "script-rules", "body-contract.md");
|
|
9554
9607
|
try {
|
|
9555
9608
|
if (fs5.existsSync(contractPath)) {
|
|
9556
9609
|
raw = fs5.readFileSync(contractPath, "utf-8");
|
|
@@ -9582,7 +9635,7 @@ var PromptLoader = class {
|
|
|
9582
9635
|
return DATABASE_RULES[dbType] ?? DATABASE_RULES["postgresql"];
|
|
9583
9636
|
}
|
|
9584
9637
|
};
|
|
9585
|
-
var defaultPromptsPath = process.env.PROMPTS_DIR ||
|
|
9638
|
+
var defaultPromptsPath = process.env.PROMPTS_DIR || path5.join(process.cwd(), ".prompts");
|
|
9586
9639
|
var promptLoader = new PromptLoader({
|
|
9587
9640
|
promptsDir: defaultPromptsPath
|
|
9588
9641
|
});
|
|
@@ -10011,7 +10064,7 @@ function getDatabaseType() {
|
|
|
10011
10064
|
function getDbTypeFromToolId(toolId) {
|
|
10012
10065
|
if (!toolId) return getDatabaseType();
|
|
10013
10066
|
const prefix = toolId.split("-")[0]?.toLowerCase();
|
|
10014
|
-
if (prefix.startsWith("mssql")) return "mssql";
|
|
10067
|
+
if (prefix.startsWith("mssql") || prefix === "fabric") return "mssql";
|
|
10015
10068
|
if (prefix.startsWith("postgres")) return "postgresql";
|
|
10016
10069
|
if (prefix.startsWith("mysql")) return "mysql";
|
|
10017
10070
|
if (prefix.startsWith("snowflake")) return "snowflake";
|
|
@@ -10332,6 +10385,7 @@ var SourceAgent = class {
|
|
|
10332
10385
|
if (toolName.endsWith("_search_schema") && schemaSearchFn) {
|
|
10333
10386
|
const keywords = toolInput.keywords || [];
|
|
10334
10387
|
logger.info(`[SourceAgent:${this.tool.name}] Schema search: ${keywords.join(", ")}`);
|
|
10388
|
+
logger.file(`[SourceAgent:${this.tool.name}] SCHEMA SEARCH:`, keywords.join(", "));
|
|
10335
10389
|
if (this.streamBuffer.hasCallback()) {
|
|
10336
10390
|
this.streamBuffer.write(`\u{1F50D} **Looking up tables:** ${keywords.join(", ")}
|
|
10337
10391
|
|
|
@@ -10344,6 +10398,7 @@ var SourceAgent = class {
|
|
|
10344
10398
|
} catch (traceErr) {
|
|
10345
10399
|
logger.warn(`[SourceAgent:${this.tool.name}] Trace capture failed for search_schema lookup (non-blocking): ${traceErr}`);
|
|
10346
10400
|
}
|
|
10401
|
+
logger.file(`[SourceAgent:${this.tool.name}] SCHEMA SEARCH RESULT:`, result);
|
|
10347
10402
|
return `Schema search results:
|
|
10348
10403
|
|
|
10349
10404
|
${result}`;
|
|
@@ -10387,6 +10442,7 @@ ${result}`;
|
|
|
10387
10442
|
cappedInput.query = ensureQueryLimit(cappedInput.query, this.config.maxRowsFetched, this.config.maxRowsFetched, _st);
|
|
10388
10443
|
}
|
|
10389
10444
|
queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
|
|
10445
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY (attempt ${this.attempts}):`, queryExecuted);
|
|
10390
10446
|
if (this.streamBuffer.hasCallback() && queryExecuted) {
|
|
10391
10447
|
const queryDisplay = cappedInput.sql || cappedInput.query;
|
|
10392
10448
|
if (queryDisplay) {
|
|
@@ -10415,6 +10471,7 @@ ${queryDisplay}
|
|
|
10415
10471
|
if (result && result.error) {
|
|
10416
10472
|
const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
|
|
10417
10473
|
logger.warn(`[SourceAgent:${this.tool.name}] Tool returned error (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
|
|
10474
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY ERROR (attempt ${this.attempts}):`, errorMsg);
|
|
10418
10475
|
if (this.streamBuffer.hasCallback()) {
|
|
10419
10476
|
this.streamBuffer.write(`\u274C **Query failed:** ${errorMsg}
|
|
10420
10477
|
|
|
@@ -10428,6 +10485,7 @@ Analyze the error and try again with a corrected query.`;
|
|
|
10428
10485
|
totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
|
|
10429
10486
|
querySucceeded = true;
|
|
10430
10487
|
successfulQueries++;
|
|
10488
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY RESULT (attempt ${this.attempts}):`, `${resultData.length} rows (${totalRowsMatched} total)`, "metadata:", JSON.stringify(result.metadata ?? {}));
|
|
10431
10489
|
if (this.streamBuffer.hasCallback()) {
|
|
10432
10490
|
const totalInfo = totalRowsMatched > resultData.length ? ` of ${totalRowsMatched} total` : "";
|
|
10433
10491
|
this.streamBuffer.write(`\u2705 **${resultData.length} rows${totalInfo} from ${this.tool.name}**
|
|
@@ -10835,7 +10893,7 @@ Even if a table appears in the detailed schema above, search_schema returns samp
|
|
|
10835
10893
|
const match = this.tool.id.match(/^(\w+)-[a-f0-9]+_/);
|
|
10836
10894
|
if (!match) return "unknown";
|
|
10837
10895
|
const raw = match[1].toLowerCase();
|
|
10838
|
-
if (raw.startsWith("mssql")) return "mssql";
|
|
10896
|
+
if (raw.startsWith("mssql") || raw === "fabric") return "mssql";
|
|
10839
10897
|
if (raw.startsWith("postgres")) return "postgres";
|
|
10840
10898
|
if (raw.startsWith("mysql")) return "mysql";
|
|
10841
10899
|
if (raw.startsWith("snowflake")) return "snowflake";
|
|
@@ -10861,7 +10919,7 @@ Even if a table appears in the detailed schema above, search_schema returns samp
|
|
|
10861
10919
|
|
|
10862
10920
|
// src/userResponse/scripts/script-runner.ts
|
|
10863
10921
|
import { spawn } from "child_process";
|
|
10864
|
-
import * as
|
|
10922
|
+
import * as path6 from "path";
|
|
10865
10923
|
import * as os from "os";
|
|
10866
10924
|
|
|
10867
10925
|
// src/userResponse/scripts/script-ipc.ts
|
|
@@ -11004,7 +11062,7 @@ var Semaphore = class {
|
|
|
11004
11062
|
`Script execution at capacity (${this.max} running, ${this.waiters.length} queued). Try again shortly.`
|
|
11005
11063
|
);
|
|
11006
11064
|
}
|
|
11007
|
-
return new Promise((
|
|
11065
|
+
return new Promise((resolve4) => this.waiters.push(resolve4));
|
|
11008
11066
|
}
|
|
11009
11067
|
release() {
|
|
11010
11068
|
const next = this.waiters.shift();
|
|
@@ -11022,8 +11080,8 @@ function resolveTsxBinary() {
|
|
|
11022
11080
|
if (tsxBinaryPath) return tsxBinaryPath;
|
|
11023
11081
|
try {
|
|
11024
11082
|
const tsxPkg = __require.resolve("tsx/package.json");
|
|
11025
|
-
const tsxDir =
|
|
11026
|
-
tsxBinaryPath =
|
|
11083
|
+
const tsxDir = path6.dirname(tsxPkg);
|
|
11084
|
+
tsxBinaryPath = path6.join(tsxDir, "dist", "cli.mjs");
|
|
11027
11085
|
return tsxBinaryPath;
|
|
11028
11086
|
} catch (err) {
|
|
11029
11087
|
throw new Error(
|
|
@@ -11032,7 +11090,7 @@ function resolveTsxBinary() {
|
|
|
11032
11090
|
}
|
|
11033
11091
|
}
|
|
11034
11092
|
function resolveBootstrapPath() {
|
|
11035
|
-
return
|
|
11093
|
+
return path6.resolve(__dirname, "userResponse/scripts/script-bootstrap.js");
|
|
11036
11094
|
}
|
|
11037
11095
|
async function runScript(recipe, scriptPath, params, options) {
|
|
11038
11096
|
const startedAt = Date.now();
|
|
@@ -11134,7 +11192,7 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
|
|
|
11134
11192
|
}
|
|
11135
11193
|
}
|
|
11136
11194
|
function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities, appliedPolicies) {
|
|
11137
|
-
return new Promise((
|
|
11195
|
+
return new Promise((resolve4) => {
|
|
11138
11196
|
const tsxBin = resolveTsxBinary();
|
|
11139
11197
|
const bootstrap = resolveBootstrapPath();
|
|
11140
11198
|
const contextToken = mintContextToken();
|
|
@@ -11190,7 +11248,7 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
|
|
|
11190
11248
|
clearTimeout(timer);
|
|
11191
11249
|
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
11192
11250
|
cleanup();
|
|
11193
|
-
|
|
11251
|
+
resolve4(r);
|
|
11194
11252
|
};
|
|
11195
11253
|
const timer = setTimeout(() => {
|
|
11196
11254
|
finish({
|
|
@@ -11540,6 +11598,7 @@ function codeOnly(source) {
|
|
|
11540
11598
|
var BARE_CONST_RE = /\bconst\s+(\w+)\s*=\s*(\d{2,}(?:\.\d+)?)\s*[;,)]/g;
|
|
11541
11599
|
var FMT_CALL_RE = /\bfmt\.\w+\(([^)]*)\)/g;
|
|
11542
11600
|
var DIRECT_BARE_ARG_RE = /(?<![\w.\[])(\d{2,}(?:\.\d+)?)(?![\w.\]])/;
|
|
11601
|
+
var OBJECT_PROP_RE = /(?<![\w.])([A-Za-z_]\w*)\s*:\s*(\d{2,}(?:\.\d+)?)\s*[,}]/g;
|
|
11543
11602
|
function interpolationContents(source) {
|
|
11544
11603
|
const out = [];
|
|
11545
11604
|
for (const tpl of source.matchAll(/`(?:\\.|[^`\\])*`/g)) {
|
|
@@ -11571,6 +11630,24 @@ function findHardcodedAggregates(source) {
|
|
|
11571
11630
|
reason: `a bare number ("${m[1]}") is passed directly into a fmt.*() call \u2014 it will report this SAME figure on every future replay no matter how much data actually exists then. Compute it from the data instead (e.g. \`data.length\`, a value read from \`datasets\`, or a real SQL aggregate).`
|
|
11572
11631
|
});
|
|
11573
11632
|
}
|
|
11633
|
+
const propCandidates = [...code.matchAll(OBJECT_PROP_RE)].map((m) => ({ name: m[1], text: m[0].trim().replace(/[,}]$/, "") }));
|
|
11634
|
+
for (const c of propCandidates) {
|
|
11635
|
+
const accessPattern = `\\??\\.${c.name}\\b`;
|
|
11636
|
+
const accessRe = new RegExp(accessPattern);
|
|
11637
|
+
let flows = fmtArgLists.some((args) => accessRe.test(args));
|
|
11638
|
+
if (!flows) {
|
|
11639
|
+
const hop = code.match(new RegExp(`\\bconst\\s+(\\w+)\\s*=\\s*\\w+${accessPattern}`));
|
|
11640
|
+
if (hop) {
|
|
11641
|
+
const varRe = new RegExp(`\\b${hop[1]}\\b`);
|
|
11642
|
+
flows = fmtArgLists.some((args) => varRe.test(args));
|
|
11643
|
+
}
|
|
11644
|
+
}
|
|
11645
|
+
if (!flows) continue;
|
|
11646
|
+
violations.push({
|
|
11647
|
+
text: c.text,
|
|
11648
|
+
reason: `"${c.name}" is an object-literal property bound directly to a bare number, later read back via .${c.name} and passed to a fmt.*() call \u2014 it will report this SAME figure on every future replay no matter how much data actually exists then. Compute it from the data (e.g. \`data.length\`, a real SQL aggregate, or a value derived at read time), never type it in as a property value.`
|
|
11649
|
+
});
|
|
11650
|
+
}
|
|
11574
11651
|
return violations;
|
|
11575
11652
|
}
|
|
11576
11653
|
function literalChunks(source) {
|
|
@@ -11688,7 +11765,7 @@ function resolveScriptRecipeStore(collections) {
|
|
|
11688
11765
|
|
|
11689
11766
|
// src/userResponse/scripts/script-store.ts
|
|
11690
11767
|
import * as fs6 from "fs";
|
|
11691
|
-
import * as
|
|
11768
|
+
import * as path7 from "path";
|
|
11692
11769
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
11693
11770
|
var DEFAULT_STORE_DIR = "scripts-store";
|
|
11694
11771
|
function stripAnalysisExport(body) {
|
|
@@ -11782,7 +11859,7 @@ export { getData };
|
|
|
11782
11859
|
var ScriptStore = class {
|
|
11783
11860
|
constructor(opts) {
|
|
11784
11861
|
this.store = opts?.store ?? resolveScriptRecipeStore(opts?.collections) ?? null;
|
|
11785
|
-
this.storeDir = opts?.baseDir ||
|
|
11862
|
+
this.storeDir = opts?.baseDir || path7.join(process.cwd(), DEFAULT_STORE_DIR);
|
|
11786
11863
|
this.projectId = opts?.projectId;
|
|
11787
11864
|
if (!this.store) {
|
|
11788
11865
|
logger.warn("[ScriptStore] No script-recipes metadata store injected \u2014 script reuse disabled this run.");
|
|
@@ -11852,7 +11929,7 @@ var ScriptStore = class {
|
|
|
11852
11929
|
if (version === row.version) {
|
|
11853
11930
|
return this.rowToRecipe(row, this.readBody(row.fileBase));
|
|
11854
11931
|
}
|
|
11855
|
-
const archivePath =
|
|
11932
|
+
const archivePath = path7.join(this.archiveDir, `${row.fileBase}.v${version}.ts`);
|
|
11856
11933
|
if (!fs6.existsSync(archivePath)) {
|
|
11857
11934
|
logger.warn(`[ScriptStore] getVersion(${id}, v${version}) \u2014 archived body not found, recipe is now at v${row.version}`);
|
|
11858
11935
|
return null;
|
|
@@ -11876,10 +11953,10 @@ var ScriptStore = class {
|
|
|
11876
11953
|
}
|
|
11877
11954
|
if (recipe.scriptBody) {
|
|
11878
11955
|
const normalized = normalizeScriptBody(recipe.scriptBody);
|
|
11879
|
-
const
|
|
11880
|
-
if (
|
|
11956
|
+
const hash2 = this.hash(normalized);
|
|
11957
|
+
if (hash2 !== recipe.bodyHash) {
|
|
11881
11958
|
this.writeBody(recipe.fileBase, normalized);
|
|
11882
|
-
recipe.bodyHash =
|
|
11959
|
+
recipe.bodyHash = hash2;
|
|
11883
11960
|
}
|
|
11884
11961
|
}
|
|
11885
11962
|
recipe.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12167,7 +12244,7 @@ ${carried.trim()}
|
|
|
12167
12244
|
/** Absolute path to the .ts body for a recipe (used by the runner/MainAgent). */
|
|
12168
12245
|
getScriptPath(recipe) {
|
|
12169
12246
|
const base = recipe.fileBase || this.toSlug(recipe.name);
|
|
12170
|
-
return
|
|
12247
|
+
return path7.join(this.storeDir, `${base}.ts`);
|
|
12171
12248
|
}
|
|
12172
12249
|
// ============================================
|
|
12173
12250
|
// Internals
|
|
@@ -12353,7 +12430,7 @@ ${analysisBody.trim()}
|
|
|
12353
12430
|
return true;
|
|
12354
12431
|
}
|
|
12355
12432
|
bodyPath(fileBase) {
|
|
12356
|
-
return
|
|
12433
|
+
return path7.join(this.storeDir, `${fileBase}.ts`);
|
|
12357
12434
|
}
|
|
12358
12435
|
readBody(fileBase) {
|
|
12359
12436
|
try {
|
|
@@ -12365,7 +12442,7 @@ ${analysisBody.trim()}
|
|
|
12365
12442
|
}
|
|
12366
12443
|
/** Directory holding superseded bodies. Dot-prefixed so IDEs/`ls` hide it. */
|
|
12367
12444
|
get archiveDir() {
|
|
12368
|
-
return
|
|
12445
|
+
return path7.join(this.storeDir, ".versions");
|
|
12369
12446
|
}
|
|
12370
12447
|
/**
|
|
12371
12448
|
* Archive a superseded body as `.versions/<fileBase>.v<n>.ts`.
|
|
@@ -12375,7 +12452,7 @@ ${analysisBody.trim()}
|
|
|
12375
12452
|
*/
|
|
12376
12453
|
writeArchive(fileBase, version, body) {
|
|
12377
12454
|
if (!fs6.existsSync(this.archiveDir)) fs6.mkdirSync(this.archiveDir, { recursive: true });
|
|
12378
|
-
const finalPath =
|
|
12455
|
+
const finalPath = path7.join(this.archiveDir, `${fileBase}.v${version}.ts`);
|
|
12379
12456
|
const tmpPath = `${finalPath}.tmp-${randomBytes2(4).toString("hex")}`;
|
|
12380
12457
|
fs6.writeFileSync(tmpPath, body);
|
|
12381
12458
|
fs6.renameSync(tmpPath, finalPath);
|
|
@@ -12393,7 +12470,7 @@ ${analysisBody.trim()}
|
|
|
12393
12470
|
for (const f of fs6.readdirSync(this.archiveDir)) {
|
|
12394
12471
|
if (!f.startsWith(prefix) || !f.endsWith(".ts")) continue;
|
|
12395
12472
|
const suffix = f.slice(oldBase.length);
|
|
12396
|
-
fs6.renameSync(
|
|
12473
|
+
fs6.renameSync(path7.join(this.archiveDir, f), path7.join(this.archiveDir, `${newBase}${suffix}`));
|
|
12397
12474
|
}
|
|
12398
12475
|
} catch (err) {
|
|
12399
12476
|
logger.warn(`[ScriptStore] failed to move archives ${oldBase} \u2192 ${newBase}: ${err}`);
|
|
@@ -12419,7 +12496,7 @@ ${analysisBody.trim()}
|
|
|
12419
12496
|
const prefix = `${fileBase}.v`;
|
|
12420
12497
|
for (const f of fs6.readdirSync(this.archiveDir)) {
|
|
12421
12498
|
if (f.startsWith(prefix) && f.endsWith(".ts")) {
|
|
12422
|
-
fs6.unlinkSync(
|
|
12499
|
+
fs6.unlinkSync(path7.join(this.archiveDir, f));
|
|
12423
12500
|
}
|
|
12424
12501
|
}
|
|
12425
12502
|
} catch (err) {
|
|
@@ -12704,7 +12781,7 @@ var WRITE_SCRIPT_TOOL_DEF = {
|
|
|
12704
12781
|
};
|
|
12705
12782
|
var RESOLVE_ENTITIES_TOOL_DEF = {
|
|
12706
12783
|
name: "resolve_entities",
|
|
12707
|
-
description: 'ALWAYS call this FIRST, before any source query, on every question. It resolves
|
|
12784
|
+
description: 'ALWAYS call this FIRST, before any source query, on every question. It resolves ONLY the things the user NAMED into exact database ids \u2014 proper nouns, identifiers, searchable values. It has no notion of formulas, computation, totals, or business logic \u2014 for anything the question asks you to compute, forecast, rank, or classify, use the separate concept-resolution tool instead, if one is available; never pass that kind of thing here. Pass `mentions` for any proper noun or identifier the user referred to \u2014 customer/company/project names, people, places, invoice or document numbers. Pass the user\'s wording VERBATIM; do not clean it up, expand abbreviations, or correct a spelling. Misspellings and partial names are matched for you. Set a mention\'s `entityType` ONLY when the question itself says what the value is ("project 314023", "customer Acme Ltd", "invoice CHL/895/26-27"). DO NOT GUESS IT. A wrong guess is worse than none: the lookup is confined to that type, and if anything there matches even weakly you silently get the wrong record instead of the right one from another type. Leaving it off searches every type and returns the best candidate from each, labelled \u2014 so you can choose from what actually exists rather than from a guess. NEVER write a WHERE clause on a name you have not resolved here, and NEVER invent an id. Reading the result: use the top pick when it is an exact or high-scoring match. When it is flagged `ambiguous`, OR when the top score is low, READ `alternatives` before choosing \u2014 each is labelled with its `entityType`, and the right record is often there under a type you would not have guessed. `entityMap` comes back automatically for every type your mentions resolved to: it gives the id column to filter on and the table/column the name lives in. Filter on the id, never on the name. A mention in `recognized` is NOT indexed but IS locatable \u2014 values like invoice or GCN numbers change daily, so only where they live is registered. If it carries `searchSql`, RUN THAT QUERY AS GIVEN; otherwise use its `field` with the ready-made `match` predicate. Do not rewrite either comparison as `=`, because the stored data is irregular. A mention in `unresolved` is not in the index: say so for that item and still answer the rest, rather than dropping it or inventing an id. Call it again later in the turn if query results reveal another name you need an id for.',
|
|
12708
12785
|
input_schema: {
|
|
12709
12786
|
type: "object",
|
|
12710
12787
|
properties: {
|
|
@@ -12722,30 +12799,10 @@ var RESOLVE_ENTITIES_TOOL_DEF = {
|
|
|
12722
12799
|
},
|
|
12723
12800
|
required: ["text"]
|
|
12724
12801
|
}
|
|
12725
|
-
},
|
|
12726
|
-
concepts: {
|
|
12727
|
-
type: "array",
|
|
12728
|
-
items: { type: "string" },
|
|
12729
|
-
description: "Names from the CONCEPT CATALOG for anything the question asks you to compute, total, forecast, rank or classify \u2014 including when the concept is not named literally. Empty only when the question asks for no computation at all. Prerequisites come back automatically."
|
|
12730
12802
|
}
|
|
12731
12803
|
}
|
|
12732
12804
|
}
|
|
12733
12805
|
};
|
|
12734
|
-
var RESOLVE_CONCEPTS_TOOL_DEF = {
|
|
12735
|
-
name: "resolve_concepts",
|
|
12736
|
-
description: 'Fetch the project\'s OWN definition of a business concept \u2014 the formula, filters and joins that define it \u2014 BEFORE computing that concept yourself. Call this whenever the question asks you to compute, calculate, total, forecast, or classify something, even when the concept is not named literally in the question. Pass names from the CONCEPT CATALOG in your system prompt; ask for the SPECIFIC name that fits, since a vague single word matches whichever entry is closest by string. Anything a concept depends on is returned automatically alongside it \u2014 you do not need to work out or request its prerequisites. A clear misspelling is corrected for you and reported in `corrected`. But when a name is too vague or too close to several catalog entries ("project", when the catalog holds project name / project budget / project type), NOTHING is resolved: you get `didYouMean` candidates instead. Those are candidates, NOT an answer \u2014 choose the one the question means and call again with that exact name, never assume the first. When a concept comes back, USE ITS BODY AS WRITTEN: it overrides whatever the schema, column names or your own reasoning suggest, and every filter it states is REQUIRED, not optional. Honour its `returns` contract too \u2014 if it declares a unit or grain (e.g. "per-currency, unconverted"), do not collapse across that unit without saying so. A name that comes back in `unmatched` has no definition here: derive it yourself and say plainly in your answer that you did.',
|
|
12737
|
-
input_schema: {
|
|
12738
|
-
type: "object",
|
|
12739
|
-
properties: {
|
|
12740
|
-
names: {
|
|
12741
|
-
type: "array",
|
|
12742
|
-
items: { type: "string" },
|
|
12743
|
-
description: "Concept names from the CONCEPT CATALOG. Ask only for what this question needs \u2014 dependencies come back on their own."
|
|
12744
|
-
}
|
|
12745
|
-
},
|
|
12746
|
-
required: ["names"]
|
|
12747
|
-
}
|
|
12748
|
-
};
|
|
12749
12806
|
var EXECUTE_SCRIPT_TOOL_DEF = {
|
|
12750
12807
|
name: "execute_script",
|
|
12751
12808
|
description: "Run the current draft script in a subprocess and return { ok, sampleRows, executedQueries, error?, errorPhase? }. Call this after write_script. If ok is false, fix the draft with a fresh write_script (max 3 attempts per turn). If ok is true AND the sample rows answer the user question, proceed to your final analysis.",
|
|
@@ -12801,9 +12858,11 @@ var MainAgent = class {
|
|
|
12801
12858
|
*/
|
|
12802
12859
|
this.resolvedEntities = [];
|
|
12803
12860
|
/**
|
|
12804
|
-
* Concept bodies resolved this turn (
|
|
12805
|
-
*
|
|
12806
|
-
*
|
|
12861
|
+
* Concept bodies resolved this turn (currently: invariants only, via
|
|
12862
|
+
* loadConceptInvariants — concept resolution is no longer part of
|
|
12863
|
+
* resolve_entities or any other MainAgent-owned tool) — retained the same
|
|
12864
|
+
* way as resolvedEntities above, so they can be attached to the SourceAgent
|
|
12865
|
+
* dispatch.
|
|
12807
12866
|
*
|
|
12808
12867
|
* Without this, a concept resolved HERE (MainAgent's own tool loop) never
|
|
12809
12868
|
* reaches SourceAgent at all: SourceAgent only gets the natural-language
|
|
@@ -12879,7 +12938,6 @@ var MainAgent = class {
|
|
|
12879
12938
|
const entitySearchActive = this.entitySearchEnabled && entityCatalog.count > 0;
|
|
12880
12939
|
if (entityCatalog.count) logger.info(`[MainAgent] Entity catalog: ${entityCatalog.count} type(s) \u2014 resolve_entities pinned to iteration 1`);
|
|
12881
12940
|
const conceptCatalog = this.conceptsEnabled ? await this.loadConceptCatalog() : { text: "", count: 0 };
|
|
12882
|
-
const conceptsActive = this.conceptsEnabled && conceptCatalog.count > 0;
|
|
12883
12941
|
const conceptInvariants = this.conceptsEnabled ? await this.loadConceptInvariants() : { text: "", count: 0 };
|
|
12884
12942
|
if (conceptCatalog.count) {
|
|
12885
12943
|
logger.info(
|
|
@@ -12920,7 +12978,6 @@ var MainAgent = class {
|
|
|
12920
12978
|
...directToolDefs,
|
|
12921
12979
|
...workflowToolDefs,
|
|
12922
12980
|
...entitySearchActive ? [RESOLVE_ENTITIES_TOOL_DEF] : [],
|
|
12923
|
-
...conceptsActive ? [RESOLVE_CONCEPTS_TOOL_DEF] : [],
|
|
12924
12981
|
...this.scriptingEnabled ? [WRITE_SCRIPT_TOOL_DEF, EXECUTE_SCRIPT_TOOL_DEF, WRITE_ANALYSIS_TOOL_DEF] : [],
|
|
12925
12982
|
// Only offered when there is a catalog to choose from; without one the
|
|
12926
12983
|
// flow falls back to generateScriptComponents exactly as before.
|
|
@@ -12936,7 +12993,7 @@ var MainAgent = class {
|
|
|
12936
12993
|
let sourceCallCounter = 0;
|
|
12937
12994
|
const toolIdCallCounts = {};
|
|
12938
12995
|
let selectedWorkflow;
|
|
12939
|
-
const
|
|
12996
|
+
const toolHandlerInner = async (toolName, toolInput) => {
|
|
12940
12997
|
const workflow = this.workflows.find((w) => w.id === toolName);
|
|
12941
12998
|
if (workflow) {
|
|
12942
12999
|
return this.handleWorkflow(workflow, toolInput, (w) => {
|
|
@@ -12944,10 +13001,7 @@ var MainAgent = class {
|
|
|
12944
13001
|
});
|
|
12945
13002
|
}
|
|
12946
13003
|
if (toolName === "resolve_entities") {
|
|
12947
|
-
return this.handleResolveEntities(toolInput
|
|
12948
|
-
}
|
|
12949
|
-
if (toolName === "resolve_concepts") {
|
|
12950
|
-
return this.handleResolveConcepts(toolInput);
|
|
13004
|
+
return this.handleResolveEntities(toolInput);
|
|
12951
13005
|
}
|
|
12952
13006
|
if (toolName === "write_components") {
|
|
12953
13007
|
if (!this.scriptingEnabled) return "Component authoring is not enabled for this request.";
|
|
@@ -12975,7 +13029,8 @@ var MainAgent = class {
|
|
|
12975
13029
|
}
|
|
12976
13030
|
const sourceInput = {
|
|
12977
13031
|
intent: toolInput.intent || toolInput.query || JSON.stringify(toolInput),
|
|
12978
|
-
aggregation: toolInput.aggregation || "raw"
|
|
13032
|
+
aggregation: toolInput.aggregation || "raw",
|
|
13033
|
+
concepts: Array.isArray(toolInput.concepts) ? toolInput.concepts.filter((c) => typeof c === "string") : void 0
|
|
12979
13034
|
};
|
|
12980
13035
|
logger.info(`[MainAgent] Dispatching SourceAgent for "${externalTool.name}" | intent: "${sourceInput.intent}"`);
|
|
12981
13036
|
const preResolved = await this.preResolveSchema(externalTool, sourceInput.intent);
|
|
@@ -13004,10 +13059,20 @@ var MainAgent = class {
|
|
|
13004
13059
|
if (entityBlock) {
|
|
13005
13060
|
logger.info(`[MainAgent] Attached ${this.resolvedEntities.length} resolved entity id(s) to the dispatch: ${this.resolvedEntities.map((e) => `${e.entityType}=${e.instanceId}`).join(", ")}`);
|
|
13006
13061
|
}
|
|
13007
|
-
const conceptBlock = this.buildResolvedConceptsBlock();
|
|
13062
|
+
const conceptBlock = this.buildResolvedConceptsBlock(sourceInput.concepts);
|
|
13063
|
+
const wantedConcepts = new Set(sourceInput.concepts ?? []);
|
|
13064
|
+
const attachedConcepts = this.resolvedConcepts.filter((c) => c.alwaysInclude || wantedConcepts.has(c.name));
|
|
13008
13065
|
if (conceptBlock) {
|
|
13009
|
-
logger.info(`[MainAgent] Attached ${
|
|
13010
|
-
}
|
|
13066
|
+
logger.info(`[MainAgent] Attached ${attachedConcepts.length} resolved concept(s) to the dispatch: ${attachedConcepts.map((c) => c.name).join(", ")}`);
|
|
13067
|
+
}
|
|
13068
|
+
logger.file(
|
|
13069
|
+
`[DISPATCH] ${externalTool.name} | sourceInput.concepts requested:`,
|
|
13070
|
+
JSON.stringify(sourceInput.concepts ?? []),
|
|
13071
|
+
"\n entityBlock:",
|
|
13072
|
+
entityBlock || "(none)",
|
|
13073
|
+
"\n conceptBlock:",
|
|
13074
|
+
conceptBlock || "(none)"
|
|
13075
|
+
);
|
|
13011
13076
|
const result = await sourceAgent.execute({
|
|
13012
13077
|
...sourceInput,
|
|
13013
13078
|
intent: sourceInput.intent + entityBlock + conceptBlock
|
|
@@ -13036,7 +13101,7 @@ var MainAgent = class {
|
|
|
13036
13101
|
const entry = sourceAgent.getTraceEntry();
|
|
13037
13102
|
if (entry) {
|
|
13038
13103
|
entry.result = formatted;
|
|
13039
|
-
if (
|
|
13104
|
+
if (attachedConcepts.length) entry.resolvedConcepts = [...attachedConcepts];
|
|
13040
13105
|
entry.aggregation = sourceInput.aggregation;
|
|
13041
13106
|
}
|
|
13042
13107
|
} catch (traceErr) {
|
|
@@ -13044,8 +13109,21 @@ var MainAgent = class {
|
|
|
13044
13109
|
}
|
|
13045
13110
|
return formatted;
|
|
13046
13111
|
};
|
|
13112
|
+
const toolHandler = async (toolName, toolInput) => {
|
|
13113
|
+
logger.file(`[TOOL CALL] ${toolName} | input:`, JSON.stringify(toolInput));
|
|
13114
|
+
try {
|
|
13115
|
+
const result = await toolHandlerInner(toolName, toolInput);
|
|
13116
|
+
logger.file(`[TOOL RESULT] ${toolName} | result:`, typeof result === "string" ? result.slice(0, 4e3) : JSON.stringify(result).slice(0, 4e3));
|
|
13117
|
+
return result;
|
|
13118
|
+
} catch (err) {
|
|
13119
|
+
logger.file(`[TOOL ERROR] ${toolName} | error:`, err instanceof Error ? err.message : String(err));
|
|
13120
|
+
throw err;
|
|
13121
|
+
}
|
|
13122
|
+
};
|
|
13047
13123
|
let text = "";
|
|
13048
13124
|
const firstIterationToolChoice = entitySearchActive ? { type: "tool", name: "resolve_entities" } : (this.forkMode || this.editMode) && this.scriptingEnabled ? { type: "any" } : void 0;
|
|
13125
|
+
const conceptToolRegistered = this.externalTools.some((t) => t.id === "concept");
|
|
13126
|
+
const secondIterationToolChoice = conceptToolRegistered ? { type: "tool", name: "concept" } : void 0;
|
|
13049
13127
|
try {
|
|
13050
13128
|
text = await LLM.streamWithTools(
|
|
13051
13129
|
{
|
|
@@ -13064,7 +13142,8 @@ var MainAgent = class {
|
|
|
13064
13142
|
apiKey: apiKey || this.config.apiKey,
|
|
13065
13143
|
partial: streamCallback,
|
|
13066
13144
|
signal,
|
|
13067
|
-
...firstIterationToolChoice ? { firstIterationToolChoice } : {}
|
|
13145
|
+
...firstIterationToolChoice ? { firstIterationToolChoice } : {},
|
|
13146
|
+
...secondIterationToolChoice ? { secondIterationToolChoice } : {}
|
|
13068
13147
|
},
|
|
13069
13148
|
this.config.maxIterations
|
|
13070
13149
|
);
|
|
@@ -13235,24 +13314,6 @@ ${catalog}`,
|
|
|
13235
13314
|
const hardcodedComponentText = findHardcodedComponentText(body);
|
|
13236
13315
|
const hardcodedEntityNames = findHardcodedEntityNames(body, this.resolvedEntities.map((e) => e.displayName));
|
|
13237
13316
|
const componentViolations = [...hardcodedAggregates, ...hardcodedComponentText, ...hardcodedEntityNames];
|
|
13238
|
-
if (componentViolations.length > 0) {
|
|
13239
|
-
const named = componentViolations.map((v) => `"${v.text}"`).join(", ");
|
|
13240
|
-
const repeatedTexts = componentViolations.map((v) => v.text).filter((t) => this.lastComponentsViolationTexts.includes(t));
|
|
13241
|
-
this.lastComponentsViolationTexts = componentViolations.map((v) => v.text);
|
|
13242
|
-
logger.warn(`[MainAgent] write_components rejected \u2014 ${componentViolations.length} hardcoded value(s): ${named}`);
|
|
13243
|
-
const lines2 = componentViolations.map((v) => ` \u2022 "${v.text}" \u2014 ${v.reason}`).join("\n");
|
|
13244
|
-
let message = `write_components rejected: getComponents hardcodes ${componentViolations.length} value(s) that belong to this run's data, not fixed text:
|
|
13245
|
-
${lines2}
|
|
13246
|
-
|
|
13247
|
-
FIX: derive every such value from \`data\`/\`datasets\` at render time via \`fmt\` (e.g. \`Total: \${fmt.money(datasets.summary[0].total)}\`), never a number typed directly into the string.`;
|
|
13248
|
-
if (repeatedTexts.length > 0) {
|
|
13249
|
-
const repeatedList = repeatedTexts.map((t) => `"${t}"`).join(", ");
|
|
13250
|
-
message += `
|
|
13251
|
-
|
|
13252
|
-
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: if it's an id, use \`params.<name>\` directly; if it's a name or a period, read it from \`data\`/\`datasets\`/\`fmt.range\`. Replace the literal itself, not the wording around it.`;
|
|
13253
|
-
}
|
|
13254
|
-
return message;
|
|
13255
|
-
}
|
|
13256
13317
|
const recipeId = this.scriptState.recipeId;
|
|
13257
13318
|
const draft = recipeId ? await this.scriptStore.get(recipeId) : null;
|
|
13258
13319
|
if (!recipeId || !draft) return "Draft was lost from the store. Call write_script again.";
|
|
@@ -13713,12 +13774,19 @@ The verified script is intact. You can still call write_analysis and write_compo
|
|
|
13713
13774
|
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:
|
|
13714
13775
|
${lines.join("\n")}`;
|
|
13715
13776
|
}
|
|
13716
|
-
/**
|
|
13717
|
-
|
|
13777
|
+
/**
|
|
13778
|
+
* Dedupe-and-retain helper shared by loadConceptInvariants and the generic
|
|
13779
|
+
* direct-tool `resolvedConcepts` opt-in hook. `alwaysInclude` marks entries
|
|
13780
|
+
* that buildResolvedConceptsBlock must attach to EVERY dispatch regardless
|
|
13781
|
+
* of that dispatch's `concepts` param — true only for invariants, which are
|
|
13782
|
+
* meant to apply everywhere by definition. Concepts retained without it
|
|
13783
|
+
* (e.g. via the `concept` tool's `get`) are scoped per-dispatch instead.
|
|
13784
|
+
*/
|
|
13785
|
+
retainResolvedConcepts(resolved, opts) {
|
|
13718
13786
|
for (const c of resolved) {
|
|
13719
13787
|
if (!c?.name || !c?.body) continue;
|
|
13720
13788
|
if (this.resolvedConcepts.some((existing) => existing.name === c.name)) continue;
|
|
13721
|
-
this.resolvedConcepts.push({ name: String(c.name), kind: String(c.kind ?? ""), body: String(c.body) });
|
|
13789
|
+
this.resolvedConcepts.push({ name: String(c.name), kind: String(c.kind ?? ""), body: String(c.body), alwaysInclude: !!opts?.alwaysInclude });
|
|
13722
13790
|
}
|
|
13723
13791
|
}
|
|
13724
13792
|
/**
|
|
@@ -13726,10 +13794,29 @@ ${lines.join("\n")}`;
|
|
|
13726
13794
|
* entity ids — appended to the dispatch intent so SourceAgent's own,
|
|
13727
13795
|
* independent LLM call receives the resolved definitions verbatim rather
|
|
13728
13796
|
* than only MainAgent's paraphrased intent text.
|
|
13797
|
+
*
|
|
13798
|
+
* Unlike buildResolvedEntitiesBlock, non-invariant entries here are SCOPED
|
|
13799
|
+
* to `names` (this dispatch's SourceToolInput.concepts) rather than
|
|
13800
|
+
* dumping the whole-turn `this.resolvedConcepts` accumulator into every
|
|
13801
|
+
* dispatch. A concept body is phrased as an authoritative override
|
|
13802
|
+
* ("overrides the schema... your own reasoning") — fetching several via
|
|
13803
|
+
* `get` to compare/discard, or a turn needing different concepts for
|
|
13804
|
+
* different sub-questions, must not leak an irrelevant concept's filters
|
|
13805
|
+
* into an unrelated dispatch. An entry not named in `names` and not
|
|
13806
|
+
* `alwaysInclude` is left out — a concept the model forgot to name is a
|
|
13807
|
+
* visible, fixable miss; one that leaks in uninvited is not.
|
|
13808
|
+
*
|
|
13809
|
+
* `alwaysInclude` entries (invariants — see loadConceptInvariants) bypass
|
|
13810
|
+
* `names` entirely and attach to every dispatch regardless, same as before
|
|
13811
|
+
* this method took a `names` param — they are meant to apply everywhere,
|
|
13812
|
+
* not just when the model happens to ask for them.
|
|
13729
13813
|
*/
|
|
13730
|
-
buildResolvedConceptsBlock() {
|
|
13814
|
+
buildResolvedConceptsBlock(names) {
|
|
13731
13815
|
if (!this.resolvedConcepts.length) return "";
|
|
13732
|
-
const
|
|
13816
|
+
const wanted = new Set(names ?? []);
|
|
13817
|
+
const scoped = this.resolvedConcepts.filter((c) => c.alwaysInclude || wanted.has(c.name));
|
|
13818
|
+
if (!scoped.length) return "";
|
|
13819
|
+
const lines = scoped.map((c) => `- ${c.name}${c.kind ? ` (${c.kind})` : ""}: ${c.body}`);
|
|
13733
13820
|
return `
|
|
13734
13821
|
|
|
13735
13822
|
RESOLVED CONCEPTS \u2014 authoritative definitions for this turn. Use each body EXACTLY as written; it overrides the schema, the knowledge base, and your own reasoning:
|
|
@@ -13744,11 +13831,21 @@ ${lines.join("\n")}`;
|
|
|
13744
13831
|
return !!this.config.collections?.["concepts"]?.["resolve"];
|
|
13745
13832
|
}
|
|
13746
13833
|
/**
|
|
13747
|
-
*
|
|
13834
|
+
* The concept catalog for the system prompt: one line per concept — name,
|
|
13835
|
+
* aliases, and what it is for. No bodies — the body comes back from
|
|
13748
13836
|
* resolve_concepts once the LLM has named one, exactly as with the entity
|
|
13749
13837
|
* catalog. Putting bodies here would rebuild the always-on context problem
|
|
13750
13838
|
* concepts exist to solve.
|
|
13751
13839
|
*
|
|
13840
|
+
* The description is what makes the list usable. It used to be names only,
|
|
13841
|
+
* which asked the model to judge from a name alone whether `ref-data null
|
|
13842
|
+
* sentinel` bears on the question in front of it — and a wrong judgement is
|
|
13843
|
+
* silent, since it simply does not fetch. A line of prose per concept costs
|
|
13844
|
+
* a few tokens and is read rather than matched.
|
|
13845
|
+
*
|
|
13846
|
+
* Aliases stay, because they are how a user is likely to phrase it, and
|
|
13847
|
+
* showing them is what stops the LLM inventing a near-miss name.
|
|
13848
|
+
*
|
|
13752
13849
|
* Not cached, for the same reason loadEntityCatalog isn't: unchanged rows
|
|
13753
13850
|
* render byte-identical text so the prompt-cache prefix still hits, and a
|
|
13754
13851
|
* process cache would only buy a window where a new concept is invisible.
|
|
@@ -13761,7 +13858,8 @@ ${lines.join("\n")}`;
|
|
|
13761
13858
|
const rows = res?.data || [];
|
|
13762
13859
|
const text = rows.map((r) => {
|
|
13763
13860
|
const aka = r.aliases?.length ? ` (also: ${r.aliases.join(", ")})` : "";
|
|
13764
|
-
|
|
13861
|
+
const what = r.description?.trim() ? ` \u2014 ${r.description.trim()}` : "";
|
|
13862
|
+
return `- ${r.name}${aka}${what}`;
|
|
13765
13863
|
}).join("\n");
|
|
13766
13864
|
return { text, count: rows.length };
|
|
13767
13865
|
} catch (err) {
|
|
@@ -13779,10 +13877,14 @@ ${lines.join("\n")}`;
|
|
|
13779
13877
|
* Bodies ARE included here — unlike the catalog — because there is nothing to
|
|
13780
13878
|
* request: being unrequestable is what makes them invariants.
|
|
13781
13879
|
*
|
|
13782
|
-
* Also retained into resolvedConcepts
|
|
13783
|
-
*
|
|
13784
|
-
*
|
|
13785
|
-
*
|
|
13880
|
+
* Also retained into resolvedConcepts so these bodies reach SourceAgent's
|
|
13881
|
+
* dispatch too — an invariant is meant to apply everywhere, not just
|
|
13882
|
+
* wherever the model happens to ask for something whose `requires` chain
|
|
13883
|
+
* reaches it. This is now the ONLY way a concept body reaches
|
|
13884
|
+
* resolvedConcepts/SourceAgent's dispatch automatically — a non-invariant
|
|
13885
|
+
* concept fetched through a project's own concept-lookup tool (if any) is
|
|
13886
|
+
* NOT retained here, and will not reach a later SourceAgent dispatch unless
|
|
13887
|
+
* the model itself restates it in the intent it writes.
|
|
13786
13888
|
*/
|
|
13787
13889
|
async loadConceptInvariants() {
|
|
13788
13890
|
const fn = this.config.collections?.["concepts"]?.["invariants"];
|
|
@@ -13790,7 +13892,7 @@ ${lines.join("\n")}`;
|
|
|
13790
13892
|
try {
|
|
13791
13893
|
const res = await fn({});
|
|
13792
13894
|
const rows = res?.data || [];
|
|
13793
|
-
this.retainResolvedConcepts(rows);
|
|
13895
|
+
this.retainResolvedConcepts(rows, { alwaysInclude: true });
|
|
13794
13896
|
const text = rows.map((r) => `### ${r.name}
|
|
13795
13897
|
${r.body}`).join("\n\n");
|
|
13796
13898
|
return { text, count: rows.length };
|
|
@@ -13799,161 +13901,11 @@ ${r.body}`).join("\n\n");
|
|
|
13799
13901
|
return { text: "", count: 0 };
|
|
13800
13902
|
}
|
|
13801
13903
|
}
|
|
13802
|
-
/**
|
|
13803
|
-
* Resolve the concepts for a turn, with a SERVER-SIDE FLOOR.
|
|
13804
|
-
*
|
|
13805
|
-
* Pinning a tool guarantees it is called; it does not guarantee the model
|
|
13806
|
-
* fills in the arguments. Observed: a question containing the exact words
|
|
13807
|
-
* "revenue forecast" produced a call with no concepts at all, and the agent
|
|
13808
|
-
* then used an incomplete copy of the formula from the knowledge base — the
|
|
13809
|
-
* resulting query filtered a customer id against a project column and
|
|
13810
|
-
* returned zero rows on every attempt.
|
|
13811
|
-
*
|
|
13812
|
-
* So when the model names nothing, scan the question itself for catalog names
|
|
13813
|
-
* and aliases on word boundaries. Precise (a literal name is evidence, not a
|
|
13814
|
-
* guess) and asymmetric in the safe direction: an unneeded definition costs a
|
|
13815
|
-
* few hundred ignorable tokens, a missing one costs a wrong answer.
|
|
13816
|
-
*/
|
|
13817
|
-
/**
|
|
13818
|
-
* Returns the tool-facing fields to spread into the resolve_entities JSON
|
|
13819
|
-
* response, PLUS a `_trace` field with everything the trace needs that the
|
|
13820
|
-
* model does not (asked/viaFloor/unmatched). Callers must pull `_trace` out
|
|
13821
|
-
* before spreading the rest into what the model sees — see handleResolveEntities.
|
|
13822
|
-
*/
|
|
13823
|
-
async resolveConceptsForTurn(asked, userPrompt) {
|
|
13824
|
-
const resolveFn = this.config.collections?.["concepts"]?.["resolve"];
|
|
13825
|
-
if (!resolveFn) return {};
|
|
13826
|
-
let names = [...asked];
|
|
13827
|
-
let viaFloor = [];
|
|
13828
|
-
if (!names.length && userPrompt) {
|
|
13829
|
-
try {
|
|
13830
|
-
const catFn = this.config.collections?.["concepts"]?.["catalog"];
|
|
13831
|
-
const cat = catFn ? await catFn({}) : null;
|
|
13832
|
-
const haystack = ` ${userPrompt.toLowerCase().replace(/\s+/g, " ")} `;
|
|
13833
|
-
for (const row of cat?.data || []) {
|
|
13834
|
-
const labels = [row.name, ...row.aliases || []].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
13835
|
-
for (const label of labels) {
|
|
13836
|
-
const l = String(label).toLowerCase().trim();
|
|
13837
|
-
if (l.length < 4) continue;
|
|
13838
|
-
if (haystack.includes(` ${l} `) || haystack.includes(` ${l},`) || haystack.includes(` ${l}?`) || haystack.includes(` ${l}.`)) {
|
|
13839
|
-
names.push(row.name);
|
|
13840
|
-
viaFloor.push(row.name);
|
|
13841
|
-
break;
|
|
13842
|
-
}
|
|
13843
|
-
}
|
|
13844
|
-
}
|
|
13845
|
-
} catch (err) {
|
|
13846
|
-
logger.warn(`[MainAgent] concept floor scan failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
13847
|
-
}
|
|
13848
|
-
}
|
|
13849
|
-
if (!names.length) return {};
|
|
13850
|
-
try {
|
|
13851
|
-
const res = await resolveFn({ names });
|
|
13852
|
-
const resolved = res?.resolved || [];
|
|
13853
|
-
const impliedKeys = new Set((res?.implied || []).map((s) => s.toLowerCase()));
|
|
13854
|
-
this.retainResolvedConcepts(resolved);
|
|
13855
|
-
logger.info(
|
|
13856
|
-
`[MainAgent] concepts (in resolve_entities): asked ${asked.length}` + (viaFloor.length ? `, ${viaFloor.length} found in the question text (${viaFloor.join(", ")})` : "") + ` \u2192 ${resolved.length} returned` + (res?.implied?.length ? `, ${res.implied.length} via requires` : "") + (res?.corrected?.length ? `, corrected: ${res.corrected.map((c) => `${c.asked}\u2192${c.matched}`).join(", ")}` : "") + (res?.suggestions?.length ? `, ambiguous: ${res.suggestions.map((s) => s.asked).join(", ")}` : "")
|
|
13857
|
-
);
|
|
13858
|
-
const _trace = {
|
|
13859
|
-
asked,
|
|
13860
|
-
viaFloor,
|
|
13861
|
-
returned: resolved.map((c) => ({
|
|
13862
|
-
name: c.name,
|
|
13863
|
-
kind: c.kind,
|
|
13864
|
-
definition: c.body,
|
|
13865
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
13866
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
13867
|
-
})),
|
|
13868
|
-
implied: res?.implied || [],
|
|
13869
|
-
unmatched: res?.unmatched || [],
|
|
13870
|
-
corrected: res?.corrected || [],
|
|
13871
|
-
suggestions: res?.suggestions || []
|
|
13872
|
-
};
|
|
13873
|
-
if (!resolved.length) return { _trace };
|
|
13874
|
-
return {
|
|
13875
|
-
concepts: resolved.map((c) => ({
|
|
13876
|
-
name: c.name,
|
|
13877
|
-
kind: c.kind,
|
|
13878
|
-
definition: c.body,
|
|
13879
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
13880
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
13881
|
-
})),
|
|
13882
|
-
...res?.corrected?.length ? { conceptsCorrected: res.corrected } : {},
|
|
13883
|
-
...res?.suggestions?.length ? { conceptsDidYouMean: res.suggestions } : {},
|
|
13884
|
-
conceptNote: "These definitions are AUTHORITATIVE. Use each body as written \u2014 it overrides the knowledge base, the column names in the schema, and your own reasoning, and every filter it states is required. Entries marked viaRequires are prerequisites returned automatically; they apply just as much as the ones asked for." + (viaFloor.length ? ` Included because the question mentions them: ${viaFloor.join(", ")}.` : ""),
|
|
13885
|
-
_trace
|
|
13886
|
-
};
|
|
13887
|
-
} catch (err) {
|
|
13888
|
-
logger.warn(`[MainAgent] concept resolution inside resolve_entities failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
13889
|
-
return { _trace: { asked, viaFloor, error: err instanceof Error ? err.message : String(err) } };
|
|
13890
|
-
}
|
|
13891
|
-
}
|
|
13892
13904
|
/**
|
|
13893
13905
|
* Never throws: a concept-store outage must degrade to "no definition found"
|
|
13894
13906
|
* and leave the agent deriving the formula as it does today, not take the
|
|
13895
13907
|
* whole answer down.
|
|
13896
13908
|
*/
|
|
13897
|
-
async handleResolveConcepts(toolInput) {
|
|
13898
|
-
const fn = this.config.collections?.["concepts"]?.["resolve"];
|
|
13899
|
-
const names = Array.isArray(toolInput?.names) ? toolInput.names.filter(Boolean) : [];
|
|
13900
|
-
if (!fn) {
|
|
13901
|
-
return JSON.stringify({ concepts: [], unmatched: names, note: "Concept definitions are not available; derive from the schema and say so." });
|
|
13902
|
-
}
|
|
13903
|
-
if (!names.length) {
|
|
13904
|
-
return JSON.stringify({ concepts: [], unmatched: [], note: "No concept names given \u2014 continue." });
|
|
13905
|
-
}
|
|
13906
|
-
try {
|
|
13907
|
-
const res = await fn({ names });
|
|
13908
|
-
const resolved = res?.resolved || [];
|
|
13909
|
-
const impliedKeys = new Set((res?.implied || []).map((s) => s.toLowerCase()));
|
|
13910
|
-
this.retainResolvedConcepts(resolved);
|
|
13911
|
-
logger.info(
|
|
13912
|
-
`[MainAgent] resolve_concepts: asked ${names.length} \u2192 ${resolved.length} returned` + (res?.implied?.length ? `, ${res.implied.length} pulled in as dependencies` : "") + (res?.corrected?.length ? `, corrected: ${res.corrected.map((c) => `${c.asked}\u2192${c.matched}`).join(", ")}` : "") + (res?.suggestions?.length ? `, ambiguous: ${res.suggestions.map((s) => s.asked).join(", ")}` : "") + (res?.unmatched?.length ? `, unmatched: ${res.unmatched.join(", ")}` : "")
|
|
13913
|
-
);
|
|
13914
|
-
const suggestions = res?.suggestions || [];
|
|
13915
|
-
const corrected = res?.corrected || [];
|
|
13916
|
-
const notes = [];
|
|
13917
|
-
if (resolved.length) notes.push("Use these definitions VERBATIM \u2014 every filter stated is required. Entries marked viaRequires are prerequisites returned automatically; they apply just as much as the ones you asked for.");
|
|
13918
|
-
if (corrected.length) notes.push(`Read as a corrected spelling: ${corrected.map((c) => `"${c.asked}" \u2192 "${c.matched}"`).join(", ")}. If that is not what you meant, ask again with the exact catalog name.`);
|
|
13919
|
-
if (suggestions.length) notes.push(`No confident match for ${suggestions.map((s) => `"${s.asked}"`).join(", ")} \u2014 these are CANDIDATES, not answers. Pick the one the question actually means and call resolve_concepts again with that exact name; do NOT assume the first.`);
|
|
13920
|
-
if (!resolved.length && !suggestions.length) notes.push("No definition on file \u2014 derive it yourself and say so in your answer.");
|
|
13921
|
-
const seenByLLM = {
|
|
13922
|
-
concepts: resolved.map((c) => ({
|
|
13923
|
-
name: c.name,
|
|
13924
|
-
kind: c.kind,
|
|
13925
|
-
definition: c.body,
|
|
13926
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
13927
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
13928
|
-
})),
|
|
13929
|
-
...corrected.length ? { corrected } : {},
|
|
13930
|
-
...suggestions.length ? { didYouMean: suggestions } : {},
|
|
13931
|
-
unmatched: res?.unmatched || [],
|
|
13932
|
-
note: notes.join(" ")
|
|
13933
|
-
};
|
|
13934
|
-
try {
|
|
13935
|
-
if (this.config.trace) {
|
|
13936
|
-
this.config.trace.concepts.calls.push({
|
|
13937
|
-
via: "resolve_concepts",
|
|
13938
|
-
asked: names,
|
|
13939
|
-
implied: res?.implied || [],
|
|
13940
|
-
returned: seenByLLM.concepts,
|
|
13941
|
-
...corrected.length ? { corrected } : {},
|
|
13942
|
-
...suggestions.length ? { suggestions } : {},
|
|
13943
|
-
unmatched: res?.unmatched || [],
|
|
13944
|
-
note: notes.join(" ")
|
|
13945
|
-
});
|
|
13946
|
-
}
|
|
13947
|
-
} catch (traceErr) {
|
|
13948
|
-
logger.warn(`[MainAgent] Trace capture failed for resolve_concepts (non-blocking): ${traceErr}`);
|
|
13949
|
-
}
|
|
13950
|
-
return JSON.stringify(seenByLLM);
|
|
13951
|
-
} catch (err) {
|
|
13952
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
13953
|
-
logger.warn(`[MainAgent] resolve_concepts failed (non-blocking): ${msg}`);
|
|
13954
|
-
return JSON.stringify({ concepts: [], unmatched: names, note: "Concept lookup failed; derive from the schema and say so." });
|
|
13955
|
-
}
|
|
13956
|
-
}
|
|
13957
13909
|
/**
|
|
13958
13910
|
* Compact catalog (type + gloss) for the system prompt. This is what lets the
|
|
13959
13911
|
* LLM NAME a concept instead of guessing at our vocabulary — it can infer
|
|
@@ -13984,20 +13936,18 @@ ${r.body}`).join("\n\n");
|
|
|
13984
13936
|
* Never throws: a resolver outage must degrade to "nothing resolved" and
|
|
13985
13937
|
* leave the agent exactly as it was, not take the whole answer down.
|
|
13986
13938
|
*/
|
|
13987
|
-
async handleResolveEntities(toolInput
|
|
13939
|
+
async handleResolveEntities(toolInput) {
|
|
13988
13940
|
const fn = this.config.collections?.["entity-search"]?.["resolveEntities"];
|
|
13989
13941
|
const mentions = Array.isArray(toolInput?.mentions) ? toolInput.mentions.filter((m) => m?.text) : [];
|
|
13990
13942
|
const entityTypes = Array.isArray(toolInput?.entityTypes) ? toolInput.entityTypes.filter(Boolean) : [];
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
logger.warn(`[MainAgent] Trace capture failed for concepts-in-resolve_entities (non-blocking): ${traceErr}`);
|
|
14000
|
-
}
|
|
13943
|
+
if (this.streamBuffer.hasCallback()) {
|
|
13944
|
+
const named = mentions.map((m) => `"${m.text}"`).join(", ");
|
|
13945
|
+
this.streamBuffer.write(`
|
|
13946
|
+
|
|
13947
|
+
\u{1F50D} **Resolving entities${named ? `: ${named}` : ""}...**
|
|
13948
|
+
|
|
13949
|
+
`);
|
|
13950
|
+
await streamDelay();
|
|
14001
13951
|
}
|
|
14002
13952
|
const traceEarlyReturn = (note) => {
|
|
14003
13953
|
try {
|
|
@@ -14010,23 +13960,48 @@ ${r.body}`).join("\n\n");
|
|
|
14010
13960
|
};
|
|
14011
13961
|
if (!fn) {
|
|
14012
13962
|
traceEarlyReturn("Entity search unavailable");
|
|
14013
|
-
|
|
13963
|
+
if (this.streamBuffer.hasCallback()) {
|
|
13964
|
+
this.streamBuffer.write(`\u26A0\uFE0F **Entity search unavailable** \u2014 proceeding on schema alone.
|
|
13965
|
+
|
|
13966
|
+
`);
|
|
13967
|
+
}
|
|
13968
|
+
return JSON.stringify({ resolved: [], unresolved: [], entityMap: {}, note: "Entity search is not available; proceed using the schema." });
|
|
14014
13969
|
}
|
|
14015
13970
|
if (!mentions.length && !entityTypes.length) {
|
|
14016
13971
|
traceEarlyReturn("Nothing named in the question to resolve");
|
|
13972
|
+
if (this.streamBuffer.hasCallback()) {
|
|
13973
|
+
this.streamBuffer.write(`\u2705 **Entity search:** nothing to resolve for this question.
|
|
13974
|
+
|
|
13975
|
+
`);
|
|
13976
|
+
}
|
|
14017
13977
|
return JSON.stringify({
|
|
14018
13978
|
resolved: [],
|
|
14019
13979
|
unresolved: [],
|
|
14020
13980
|
entityMap: {},
|
|
14021
|
-
|
|
14022
|
-
note: conceptBlock.concepts?.length ? "The question named nothing to look up, but the definitions below apply \u2014 use them as written." : "Nothing to resolve for this question \u2014 continue."
|
|
13981
|
+
note: "Nothing to resolve for this question \u2014 continue."
|
|
14023
13982
|
});
|
|
14024
13983
|
}
|
|
14025
13984
|
try {
|
|
14026
13985
|
const res = await fn({ mentions, entityTypes });
|
|
14027
13986
|
logger.info(
|
|
14028
|
-
`[MainAgent] resolve_entities: ${res?.resolved?.length ?? 0} resolved, ${res?.unresolved?.length ?? 0} unresolved, ${Object.keys(res?.entityMap || {}).length}
|
|
13987
|
+
`[MainAgent] resolve_entities: ${res?.resolved?.length ?? 0} resolved, ${res?.unresolved?.length ?? 0} unresolved, ${Object.keys(res?.entityMap || {}).length} type(s)`
|
|
14029
13988
|
);
|
|
13989
|
+
if (this.streamBuffer.hasCallback()) {
|
|
13990
|
+
const resolvedCount = res?.resolved?.length ?? 0;
|
|
13991
|
+
const unresolvedCount = res?.unresolved?.length ?? 0;
|
|
13992
|
+
const names = (Array.isArray(res?.resolved) ? res.resolved : []).map((r) => {
|
|
13993
|
+
const label = r?.displayName || r?.mention;
|
|
13994
|
+
if (!label) return null;
|
|
13995
|
+
return r?.entityType ? `${label} (${r.entityType})` : label;
|
|
13996
|
+
}).filter(Boolean).join(", ");
|
|
13997
|
+
const unresolvedNames = (Array.isArray(res?.unresolved) ? res.unresolved : []).map((u) => typeof u === "string" ? u : u?.text).filter(Boolean).join(", ");
|
|
13998
|
+
let line = `\u2705 **Entity search:** ${resolvedCount} resolved`;
|
|
13999
|
+
if (names) line += ` (${names})`;
|
|
14000
|
+
if (unresolvedCount) line += `, ${unresolvedCount} unresolved${unresolvedNames ? ` (${unresolvedNames})` : ""}`;
|
|
14001
|
+
this.streamBuffer.write(`${line}
|
|
14002
|
+
|
|
14003
|
+
`);
|
|
14004
|
+
}
|
|
14030
14005
|
const TIE_EPSILON = 0.05;
|
|
14031
14006
|
const key = (e) => `${e.entityType}::${e.instanceId}`;
|
|
14032
14007
|
const add = (mention, c) => {
|
|
@@ -14062,7 +14037,7 @@ ${r.body}`).join("\n\n");
|
|
|
14062
14037
|
attrs: r.attrs && typeof r.attrs === "object" ? r.attrs : void 0
|
|
14063
14038
|
});
|
|
14064
14039
|
}
|
|
14065
|
-
const seenByLLM = { ...res
|
|
14040
|
+
const seenByLLM = { ...res };
|
|
14066
14041
|
try {
|
|
14067
14042
|
if (this.config.trace) {
|
|
14068
14043
|
this.config.trace.entityResolution.calls.push({ mentions, entityTypes, ...seenByLLM });
|
|
@@ -14074,6 +14049,13 @@ ${r.body}`).join("\n\n");
|
|
|
14074
14049
|
} catch (err) {
|
|
14075
14050
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14076
14051
|
logger.warn(`[MainAgent] resolve_entities failed (non-blocking): ${msg}`);
|
|
14052
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14053
|
+
this.streamBuffer.write(`
|
|
14054
|
+
|
|
14055
|
+
\u274C **Entity search failed:** ${msg}
|
|
14056
|
+
|
|
14057
|
+
`);
|
|
14058
|
+
}
|
|
14077
14059
|
try {
|
|
14078
14060
|
if (this.config.trace) {
|
|
14079
14061
|
this.config.trace.entityResolution.calls.push({
|
|
@@ -14092,8 +14074,7 @@ ${r.body}`).join("\n\n");
|
|
|
14092
14074
|
resolved: [],
|
|
14093
14075
|
entityMap: {},
|
|
14094
14076
|
error: msg,
|
|
14095
|
-
unresolved: mentions.map((m) => m.text)
|
|
14096
|
-
...conceptBlock
|
|
14077
|
+
unresolved: mentions.map((m) => m.text)
|
|
14097
14078
|
});
|
|
14098
14079
|
}
|
|
14099
14080
|
}
|
|
@@ -14186,7 +14167,7 @@ ${lines.join("\n")}`;
|
|
|
14186
14167
|
if (this.streamBuffer.hasCallback()) {
|
|
14187
14168
|
this.streamBuffer.write(`
|
|
14188
14169
|
|
|
14189
|
-
\u{1F527} **Running ${tool
|
|
14170
|
+
\u{1F527} **Running ${describeDirectToolCall(tool, toolInput)}...**
|
|
14190
14171
|
|
|
14191
14172
|
`);
|
|
14192
14173
|
await streamDelay();
|
|
@@ -14197,19 +14178,24 @@ ${lines.join("\n")}`;
|
|
|
14197
14178
|
if (result && result.error) {
|
|
14198
14179
|
const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
|
|
14199
14180
|
logger.warn(`[MainAgent] Direct tool "${tool.name}" returned error: ${errorMsg}`);
|
|
14181
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, error: errorMsg, durationMs: executionTimeMs });
|
|
14200
14182
|
return `\u274C Tool "${tool.name}" error: ${errorMsg}`;
|
|
14201
14183
|
}
|
|
14184
|
+
if (Array.isArray(result?.resolvedConcepts)) {
|
|
14185
|
+
this.retainResolvedConcepts(result.resolvedConcepts, { alwaysInclude: true });
|
|
14186
|
+
}
|
|
14202
14187
|
const resultData = result.data || result;
|
|
14203
14188
|
const rowCount = Array.isArray(resultData) ? resultData.length : 1;
|
|
14204
14189
|
if (this.streamBuffer.hasCallback()) {
|
|
14205
|
-
this.streamBuffer.write(`\u2705 **${tool
|
|
14190
|
+
this.streamBuffer.write(`\u2705 **${describeDirectToolCall(tool, toolInput)}** completed (${rowCount} results, ${executionTimeMs}ms)
|
|
14206
14191
|
|
|
14207
14192
|
`);
|
|
14208
14193
|
}
|
|
14194
|
+
const isConceptTool = tool.id === "concept";
|
|
14209
14195
|
const formattedResult = formatToolResultForLLM(result, {
|
|
14210
14196
|
toolName: tool.name,
|
|
14211
|
-
maxRows: 5,
|
|
14212
|
-
maxCharsPerField: 200
|
|
14197
|
+
maxRows: isConceptTool ? 25 : 5,
|
|
14198
|
+
maxCharsPerField: isConceptTool ? 4e3 : 200
|
|
14213
14199
|
});
|
|
14214
14200
|
const sampleData = Array.isArray(resultData) ? resultData.slice(0, TOOL_TRACKING_SAMPLE_ROWS) : Array.isArray(formattedResult.data) ? formattedResult.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS) : [];
|
|
14215
14201
|
executedTools.push({
|
|
@@ -14225,6 +14211,7 @@ ${lines.join("\n")}`;
|
|
|
14225
14211
|
outputSchema: tool.outputSchema
|
|
14226
14212
|
});
|
|
14227
14213
|
const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
|
|
14214
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, output: result, durationMs: executionTimeMs });
|
|
14228
14215
|
return `\u2705 Tool "${tool.name}" executed successfully. ${rowCount} results returned.
|
|
14229
14216
|
|
|
14230
14217
|
${formatted}`;
|
|
@@ -14235,10 +14222,11 @@ ${formatted}`;
|
|
|
14235
14222
|
if (this.streamBuffer.hasCallback()) {
|
|
14236
14223
|
this.streamBuffer.write(`
|
|
14237
14224
|
|
|
14238
|
-
\u274C **${tool
|
|
14225
|
+
\u274C **${describeDirectToolCall(tool, toolInput)} failed:** ${errorMsg}
|
|
14239
14226
|
|
|
14240
14227
|
`);
|
|
14241
14228
|
}
|
|
14229
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, error: errorMsg, durationMs: executionTimeMs });
|
|
14242
14230
|
return `\u274C Tool "${tool.name}" failed: ${errorMsg}`;
|
|
14243
14231
|
}
|
|
14244
14232
|
}
|
|
@@ -14391,6 +14379,11 @@ ${propLines}` : ""
|
|
|
14391
14379
|
type: "string",
|
|
14392
14380
|
enum: ["raw", "pre-aggregate", "summary"],
|
|
14393
14381
|
description: 'How to return data. "pre-aggregate": use GROUP BY/SUM/COUNT for totals. "summary": high-level metrics. "raw": individual records.'
|
|
14382
|
+
},
|
|
14383
|
+
concepts: {
|
|
14384
|
+
type: "array",
|
|
14385
|
+
items: { type: "string" },
|
|
14386
|
+
description: `Exact concept name(s), already fetched this turn via the concept tool's "get" method, that THIS query needs. Only names given here get attached as authoritative definitions to this dispatch \u2014 omit a concept you looked at but are not using for this specific query. Leave empty/omit entirely when this query needs none.`
|
|
14394
14387
|
}
|
|
14395
14388
|
},
|
|
14396
14389
|
required: ["intent"]
|
|
@@ -14614,6 +14607,22 @@ ${scopeWarning} The script re-runs them, so you can include more than one \`ctx.
|
|
|
14614
14607
|
return buildSourceSummaries(this.externalTools);
|
|
14615
14608
|
}
|
|
14616
14609
|
};
|
|
14610
|
+
function describeDirectToolCall(tool, toolInput) {
|
|
14611
|
+
const method = toolInput?.method;
|
|
14612
|
+
if (typeof method !== "string") return tool.name;
|
|
14613
|
+
switch (method) {
|
|
14614
|
+
case "find":
|
|
14615
|
+
return typeof toolInput.query === "string" ? `${tool.name} (find: "${toolInput.query}")` : `${tool.name} (find)`;
|
|
14616
|
+
case "get": {
|
|
14617
|
+
const names = Array.isArray(toolInput.names) ? toolInput.names.join(", ") : void 0;
|
|
14618
|
+
return names ? `${tool.name} (get: ${names})` : `${tool.name} (get)`;
|
|
14619
|
+
}
|
|
14620
|
+
case "run":
|
|
14621
|
+
return typeof toolInput.name === "string" ? `${tool.name} (run: ${toolInput.name})` : `${tool.name} (run)`;
|
|
14622
|
+
default:
|
|
14623
|
+
return `${tool.name} (${method})`;
|
|
14624
|
+
}
|
|
14625
|
+
}
|
|
14617
14626
|
function extractTablesFromSQL(sqls) {
|
|
14618
14627
|
const tables = /* @__PURE__ */ new Set();
|
|
14619
14628
|
const pattern = /(?:FROM|JOIN)\s+"?([a-zA-Z_][\w.]*)"?/gi;
|
|
@@ -14637,6 +14646,7 @@ function createRunTrace() {
|
|
|
14637
14646
|
scriptMatcher: null,
|
|
14638
14647
|
entityResolution: { calls: [] },
|
|
14639
14648
|
concepts: { calls: [] },
|
|
14649
|
+
directTools: { calls: [] },
|
|
14640
14650
|
componentGen: []
|
|
14641
14651
|
};
|
|
14642
14652
|
}
|
|
@@ -15931,12 +15941,12 @@ function answerCatalogForBinding(components) {
|
|
|
15931
15941
|
import dotenv from "dotenv";
|
|
15932
15942
|
|
|
15933
15943
|
// src/userResponse/schema.ts
|
|
15934
|
-
import
|
|
15944
|
+
import path8 from "path";
|
|
15935
15945
|
import fs7 from "fs";
|
|
15936
15946
|
var Schema = class {
|
|
15937
15947
|
constructor(schemaFilePath) {
|
|
15938
15948
|
this.cachedSchema = null;
|
|
15939
|
-
this.schemaFilePath = schemaFilePath ||
|
|
15949
|
+
this.schemaFilePath = schemaFilePath || path8.join(process.cwd(), "../analysis/data/schema.json");
|
|
15940
15950
|
}
|
|
15941
15951
|
/**
|
|
15942
15952
|
* Gets the database schema from the schema file
|
|
@@ -15944,7 +15954,7 @@ var Schema = class {
|
|
|
15944
15954
|
*/
|
|
15945
15955
|
getDatabaseSchema() {
|
|
15946
15956
|
try {
|
|
15947
|
-
const dir =
|
|
15957
|
+
const dir = path8.dirname(this.schemaFilePath);
|
|
15948
15958
|
if (!fs7.existsSync(dir)) {
|
|
15949
15959
|
logger.info(`Creating directory structure: ${dir}`);
|
|
15950
15960
|
fs7.mkdirSync(dir, { recursive: true });
|
|
@@ -17548,7 +17558,7 @@ ${executedToolsText}`);
|
|
|
17548
17558
|
*/
|
|
17549
17559
|
async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
|
|
17550
17560
|
const startTime = Date.now();
|
|
17551
|
-
logger.clearFile();
|
|
17561
|
+
logger.clearFile(`legacy-${userPrompt}`);
|
|
17552
17562
|
logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
|
|
17553
17563
|
try {
|
|
17554
17564
|
const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
|
|
@@ -18081,13 +18091,28 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
18081
18091
|
let runTrace;
|
|
18082
18092
|
const answerComps = answerComponents(components);
|
|
18083
18093
|
const bindingComps = projectForDatasetBinding(answerComps);
|
|
18084
|
-
logger.clearFile();
|
|
18094
|
+
logger.clearFile(`chat-${prompt}`);
|
|
18085
18095
|
logger.logLLMPrompt("agentUserResponse", "user", `User Prompt: ${prompt}`);
|
|
18086
18096
|
logger.info(`[AgentFlow] Starting | provider: ${provider} | prompt: "${prompt.substring(0, 50)}..."`);
|
|
18087
18097
|
try {
|
|
18088
18098
|
const cached = await replayExactMatch({ prompt, collections, userId, streamCallback, method: `${provider}-agent-exact-match`, startTime });
|
|
18089
18099
|
if (cached?.response) return cached.response;
|
|
18090
|
-
|
|
18100
|
+
let directReplay = cached?.directReplay;
|
|
18101
|
+
if (!cached) {
|
|
18102
|
+
const supersededMatch = await conversation_search_default.findExactMatch({
|
|
18103
|
+
userPrompt: prompt,
|
|
18104
|
+
collections,
|
|
18105
|
+
userId,
|
|
18106
|
+
statuses: ["superseded"]
|
|
18107
|
+
});
|
|
18108
|
+
const boundBinding = supersededMatch?.uiBlock?.scriptBinding;
|
|
18109
|
+
if (boundBinding?.recipeId) {
|
|
18110
|
+
directReplay = { recipeId: boundBinding.recipeId, params: boundBinding.params || {} };
|
|
18111
|
+
logger.info(
|
|
18112
|
+
`[AgentFlow] Exact match found a SUPERSEDED row (recipe edited since) \u2014 will re-run current recipe ${directReplay.recipeId} directly (skip matcher LLM)`
|
|
18113
|
+
);
|
|
18114
|
+
}
|
|
18115
|
+
}
|
|
18091
18116
|
logger.info(`[AgentFlow] No exact match \u2014 proceeding with agent`);
|
|
18092
18117
|
runTrace = createRunTrace();
|
|
18093
18118
|
const apiKey = (() => {
|
|
@@ -18172,7 +18197,7 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
18172
18197
|
const boundRecipe = await scriptStore.get(directReplay.recipeId);
|
|
18173
18198
|
if (boundRecipe) {
|
|
18174
18199
|
scriptMatch = { tier: "high", recipe: boundRecipe, extractedParams: directReplay.params };
|
|
18175
|
-
logger.info(`[AgentFlow]
|
|
18200
|
+
logger.info(`[AgentFlow] Direct replay \u2014 running current recipe "${boundRecipe.name}" directly (skipped ScriptMatcher LLM)`);
|
|
18176
18201
|
} else {
|
|
18177
18202
|
logger.info(`[AgentFlow] Bound recipe ${directReplay.recipeId} no longer in store \u2014 falling back to ScriptMatcher LLM`);
|
|
18178
18203
|
}
|
|
@@ -18524,6 +18549,19 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${scriptMatch
|
|
|
18524
18549
|
}
|
|
18525
18550
|
};
|
|
18526
18551
|
}
|
|
18552
|
+
if (isDisplayEdit) {
|
|
18553
|
+
try {
|
|
18554
|
+
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
18555
|
+
if (invalidate) {
|
|
18556
|
+
const n = await invalidate({ recipeId: scriptMatch.recipe.id, userId });
|
|
18557
|
+
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to display-edited recipe ${scriptMatch.recipe.id}`);
|
|
18558
|
+
} else {
|
|
18559
|
+
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${scriptMatch.recipe.id} will replay the PRE-EDIT component set`);
|
|
18560
|
+
}
|
|
18561
|
+
} catch (invErr) {
|
|
18562
|
+
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
18563
|
+
}
|
|
18564
|
+
}
|
|
18527
18565
|
const elapsedTime2 = Date.now() - startTime;
|
|
18528
18566
|
logger.info(`[AgentFlow] Script flow complete | ${scriptResult.executedQueries.length} queries | ${matchedComponents2.length} components | ${elapsedTime2}ms`);
|
|
18529
18567
|
return {
|
|
@@ -18679,6 +18717,67 @@ BEGIN NOW. Your VERY FIRST action MUST be a \`write_script\` tool call containin
|
|
|
18679
18717
|
const textResponse = rawText.replace(/_?_?SB_END_?_?/g, "").replace(/__SB_\w+_(?:START|MSG)_?_?/g, "").replace(/__QUERY_TIMER_START_[^_]*__/g, "").replace(/__QUERY_TIMER_DONE_[\d.]+__/g, "").replace(/__TEXT_COMPLETE__COMPONENT_GENERATION_START__/g, "").replace(/\[COMPLEXITY:\s*(?:simple|medium|complex)\]/gi, "");
|
|
18680
18718
|
streamBuffer.flush();
|
|
18681
18719
|
if (signal?.aborted) throw new RequestAbortedError();
|
|
18720
|
+
if (agentResponse.savedScript && editContext) {
|
|
18721
|
+
try {
|
|
18722
|
+
const authored = agentResponse.savedScript;
|
|
18723
|
+
if (runTrace) {
|
|
18724
|
+
try {
|
|
18725
|
+
runTrace.script = {
|
|
18726
|
+
reused: false,
|
|
18727
|
+
recipeId: authored.recipeId,
|
|
18728
|
+
body: authored.scriptBody,
|
|
18729
|
+
reasoning: editContext.instruction ?? null,
|
|
18730
|
+
executionResult: authored.executionResult ?? null,
|
|
18731
|
+
failedAttempts: agentResponse.failedScriptAttempts
|
|
18732
|
+
};
|
|
18733
|
+
} catch (traceErr) {
|
|
18734
|
+
logger.warn(`[AgentFlow] Trace capture failed for edited script (non-blocking): ${traceErr}`);
|
|
18735
|
+
}
|
|
18736
|
+
}
|
|
18737
|
+
const summaryLine = (agentResponse.text || "").split("\n").map((l) => l.trim()).find((l) => l.startsWith("\u270F\uFE0F"));
|
|
18738
|
+
const changeSummary = (summaryLine || editContext.instruction).replace(/^✏️\s*/, "").replace(/^\**\s*Updated the script:\s*/i, "").replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
18739
|
+
const committed = await scriptStore.commitEdit(
|
|
18740
|
+
editContext.recipeId,
|
|
18741
|
+
{
|
|
18742
|
+
recipeId: authored.recipeId,
|
|
18743
|
+
name: authored.name,
|
|
18744
|
+
intentDescription: authored.intentDescription,
|
|
18745
|
+
tags: authored.tags,
|
|
18746
|
+
parameters: authored.parameters,
|
|
18747
|
+
scriptBody: authored.scriptBody,
|
|
18748
|
+
sourceIds: authored.sourceIds,
|
|
18749
|
+
tables: authored.tables
|
|
18750
|
+
},
|
|
18751
|
+
{ instruction: editContext.instruction, changeSummary, editedBy: userId }
|
|
18752
|
+
);
|
|
18753
|
+
if (committed) {
|
|
18754
|
+
agentResponse.savedScript.recipeId = committed.id;
|
|
18755
|
+
scriptBinding = {
|
|
18756
|
+
recipeId: committed.id,
|
|
18757
|
+
version: committed.version,
|
|
18758
|
+
params: {},
|
|
18759
|
+
name: committed.name,
|
|
18760
|
+
columns: authored.executedQueries?.[0]?.data?.[0] ? Object.keys(authored.executedQueries[0].data[0]) : void 0
|
|
18761
|
+
};
|
|
18762
|
+
try {
|
|
18763
|
+
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
18764
|
+
if (invalidate) {
|
|
18765
|
+
const n = await invalidate({ recipeId: committed.id, userId });
|
|
18766
|
+
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to edited recipe ${committed.id}`);
|
|
18767
|
+
} else {
|
|
18768
|
+
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${committed.id} will replay the PRE-EDIT result`);
|
|
18769
|
+
}
|
|
18770
|
+
} catch (invErr) {
|
|
18771
|
+
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
18772
|
+
}
|
|
18773
|
+
} else {
|
|
18774
|
+
logger.warn(`[AgentFlow] commitEdit failed for "${editedRecipeName}" \u2014 original recipe left unchanged`);
|
|
18775
|
+
}
|
|
18776
|
+
} catch (err) {
|
|
18777
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18778
|
+
logger.warn(`[AgentFlow] Failed to commit script edit (non-blocking): ${msg}`);
|
|
18779
|
+
}
|
|
18780
|
+
}
|
|
18682
18781
|
if (agentResponse.executedTools.length === 0 && agentResponse.savedScript?.executedQueries && agentResponse.savedScript.executedQueries.length > 0) {
|
|
18683
18782
|
for (const q of agentResponse.savedScript.executedQueries) {
|
|
18684
18783
|
agentResponse.executedTools.push({
|
|
@@ -18952,66 +19051,7 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${carriedRequ
|
|
|
18952
19051
|
}
|
|
18953
19052
|
};
|
|
18954
19053
|
}
|
|
18955
|
-
if (agentResponse.savedScript && editContext) {
|
|
18956
|
-
try {
|
|
18957
|
-
const authored = agentResponse.savedScript;
|
|
18958
|
-
if (runTrace) {
|
|
18959
|
-
try {
|
|
18960
|
-
runTrace.script = {
|
|
18961
|
-
reused: false,
|
|
18962
|
-
recipeId: authored.recipeId,
|
|
18963
|
-
body: authored.scriptBody,
|
|
18964
|
-
reasoning: editContext.instruction ?? null,
|
|
18965
|
-
executionResult: authored.executionResult ?? null,
|
|
18966
|
-
failedAttempts: agentResponse.failedScriptAttempts
|
|
18967
|
-
};
|
|
18968
|
-
} catch (traceErr) {
|
|
18969
|
-
logger.warn(`[AgentFlow] Trace capture failed for edited script (non-blocking): ${traceErr}`);
|
|
18970
|
-
}
|
|
18971
|
-
}
|
|
18972
|
-
const summaryLine = (agentResponse.text || "").split("\n").map((l) => l.trim()).find((l) => l.startsWith("\u270F\uFE0F"));
|
|
18973
|
-
const changeSummary = (summaryLine || editContext.instruction).replace(/^✏️\s*/, "").replace(/^\**\s*Updated the script:\s*/i, "").replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
18974
|
-
const committed = await scriptStore.commitEdit(
|
|
18975
|
-
editContext.recipeId,
|
|
18976
|
-
{
|
|
18977
|
-
recipeId: authored.recipeId,
|
|
18978
|
-
name: authored.name,
|
|
18979
|
-
intentDescription: authored.intentDescription,
|
|
18980
|
-
tags: authored.tags,
|
|
18981
|
-
parameters: authored.parameters,
|
|
18982
|
-
scriptBody: authored.scriptBody,
|
|
18983
|
-
sourceIds: authored.sourceIds,
|
|
18984
|
-
tables: authored.tables
|
|
18985
|
-
},
|
|
18986
|
-
{ instruction: editContext.instruction, changeSummary, editedBy: userId }
|
|
18987
|
-
);
|
|
18988
|
-
if (committed) {
|
|
18989
|
-
scriptBinding = {
|
|
18990
|
-
recipeId: committed.id,
|
|
18991
|
-
version: committed.version,
|
|
18992
|
-
params: {},
|
|
18993
|
-
name: committed.name,
|
|
18994
|
-
columns: authored.executedQueries?.[0]?.data?.[0] ? Object.keys(authored.executedQueries[0].data[0]) : void 0
|
|
18995
|
-
};
|
|
18996
|
-
try {
|
|
18997
|
-
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
18998
|
-
if (invalidate) {
|
|
18999
|
-
const n = await invalidate({ recipeId: committed.id, userId });
|
|
19000
|
-
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to edited recipe ${committed.id}`);
|
|
19001
|
-
} else {
|
|
19002
|
-
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${committed.id} will replay the PRE-EDIT result`);
|
|
19003
|
-
}
|
|
19004
|
-
} catch (invErr) {
|
|
19005
|
-
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
19006
|
-
}
|
|
19007
|
-
} else {
|
|
19008
|
-
logger.warn(`[AgentFlow] commitEdit failed for "${editedRecipeName}" \u2014 original recipe left unchanged`);
|
|
19009
|
-
}
|
|
19010
|
-
} catch (err) {
|
|
19011
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19012
|
-
logger.warn(`[AgentFlow] Failed to commit script edit (non-blocking): ${msg}`);
|
|
19013
|
-
}
|
|
19014
|
-
} else if (agentResponse.savedScript) {
|
|
19054
|
+
if (agentResponse.savedScript && !editContext) {
|
|
19015
19055
|
try {
|
|
19016
19056
|
const authored = agentResponse.savedScript;
|
|
19017
19057
|
if (runTrace) {
|
|
@@ -19240,7 +19280,8 @@ function transformUIBlockForDB(uiblock, userPrompt, uiBlockId) {
|
|
|
19240
19280
|
};
|
|
19241
19281
|
}
|
|
19242
19282
|
function hasTraceContent(trace) {
|
|
19243
|
-
|
|
19283
|
+
if (!trace || typeof trace !== "object") return false;
|
|
19284
|
+
return (trace.kbNodes?.global?.length ?? 0) > 0 || (trace.kbNodes?.query?.length ?? 0) > 0 || (trace.schemaNodes?.routing?.length ?? 0) > 0 || (trace.schemaNodes?.preResolve?.length ?? 0) > 0 || !!trace.schemaNodes?.mainAgentCatalog || (trace.schemaNodes?.sourceAgentSchemas?.length ?? 0) > 0 || trace.script != null || trace.scriptMatcher != null || !!trace.entityResolution?.catalogText || (trace.entityResolution?.calls?.length ?? 0) > 0 || !!trace.conversationHistory || !!trace.userAccessContext || !!trace.userKbContext || !!trace.concepts?.catalogText || !!trace.concepts?.invariantsText || (trace.concepts?.calls?.length ?? 0) > 0 || (trace.directTools?.calls?.length ?? 0) > 0 || (trace.componentGen?.length ?? 0) > 0;
|
|
19244
19285
|
}
|
|
19245
19286
|
async function saveConversation(params) {
|
|
19246
19287
|
const { userId, userPrompt, uiblock, uiBlockId, threadId, collections, status = "success", error: turnError, trace } = params;
|
|
@@ -19386,6 +19427,18 @@ var CONTEXT_CONFIG = {
|
|
|
19386
19427
|
function stripStreamMarkers(text) {
|
|
19387
19428
|
return text.replace(/__TEXT_COMPLETE__[\s\S]*/g, "").replace(/__ANSWER_COMPONENT_START__[\s\S]*?__ANSWER_COMPONENT_END__/g, "").replace(/__COMPONENT_GENERATION_START__/g, "").trim();
|
|
19388
19429
|
}
|
|
19430
|
+
function promptContextFrom(raw) {
|
|
19431
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
19432
|
+
const source = raw;
|
|
19433
|
+
const context = {};
|
|
19434
|
+
if (typeof source.dashboardId === "string" && source.dashboardId.trim()) {
|
|
19435
|
+
context.dashboardId = source.dashboardId.trim();
|
|
19436
|
+
}
|
|
19437
|
+
if (source.filters && typeof source.filters === "object" && !Array.isArray(source.filters)) {
|
|
19438
|
+
context.filters = source.filters;
|
|
19439
|
+
}
|
|
19440
|
+
return Object.keys(context).length ? context : void 0;
|
|
19441
|
+
}
|
|
19389
19442
|
var inFlightAborts = /* @__PURE__ */ new Map();
|
|
19390
19443
|
var recentlyAborted = /* @__PURE__ */ new Map();
|
|
19391
19444
|
var ABORT_TOMBSTONE_TTL_MS = 3e4;
|
|
@@ -19449,6 +19502,10 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19449
19502
|
const userConfig = payload.config;
|
|
19450
19503
|
const SA_RUNTIME = payload.SA_RUNTIME;
|
|
19451
19504
|
const wsId = userPromptRequest.from.id || "unknown";
|
|
19505
|
+
const reported = (response) => ({
|
|
19506
|
+
model: response?.data?.model,
|
|
19507
|
+
usage: response?.data?.usage
|
|
19508
|
+
});
|
|
19452
19509
|
const promptContext = `User Prompt: ${prompt?.substring(0, 50)}${(prompt?.length || 0) > 50 ? "..." : ""}`;
|
|
19453
19510
|
llmUsageLogger.resetLogFile(promptContext);
|
|
19454
19511
|
userPromptErrorLogger.resetLogFile(promptContext);
|
|
@@ -19572,7 +19629,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19572
19629
|
conversationHistory,
|
|
19573
19630
|
onStream: streamCallback,
|
|
19574
19631
|
onNarration: narrationCallback,
|
|
19575
|
-
signal: abortController.signal
|
|
19632
|
+
signal: abortController.signal,
|
|
19633
|
+
context: promptContextFrom(payload.context)
|
|
19576
19634
|
}) : await get_agent_user_response(
|
|
19577
19635
|
prompt,
|
|
19578
19636
|
components,
|
|
@@ -19629,7 +19687,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19629
19687
|
}
|
|
19630
19688
|
if (userId) {
|
|
19631
19689
|
const latencyMs = Date.now() - requestStartTime;
|
|
19632
|
-
const
|
|
19690
|
+
const turn = reported(userResponse);
|
|
19691
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19633
19692
|
const event = analyticsClient.buildEvent({
|
|
19634
19693
|
type: "chat_agent",
|
|
19635
19694
|
userId,
|
|
@@ -19642,7 +19701,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19642
19701
|
model,
|
|
19643
19702
|
errorMessage: "aborted",
|
|
19644
19703
|
conversationId: savedConversationId2,
|
|
19645
|
-
response: savedResponse2
|
|
19704
|
+
response: savedResponse2,
|
|
19705
|
+
usage: turn.usage
|
|
19646
19706
|
});
|
|
19647
19707
|
analyticsClient.track(event).catch(() => {
|
|
19648
19708
|
});
|
|
@@ -19696,7 +19756,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19696
19756
|
}
|
|
19697
19757
|
if (userId) {
|
|
19698
19758
|
const latencyMs = Date.now() - requestStartTime;
|
|
19699
|
-
const
|
|
19759
|
+
const turn = reported(userResponse);
|
|
19760
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19700
19761
|
const event = analyticsClient.buildEvent({
|
|
19701
19762
|
type: "chat_agent",
|
|
19702
19763
|
userId,
|
|
@@ -19709,7 +19770,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19709
19770
|
model,
|
|
19710
19771
|
errorMessage,
|
|
19711
19772
|
conversationId: savedConversationId2,
|
|
19712
|
-
response: savedResponse2
|
|
19773
|
+
response: savedResponse2,
|
|
19774
|
+
usage: turn.usage
|
|
19713
19775
|
});
|
|
19714
19776
|
analyticsClient.track(event).catch(() => {
|
|
19715
19777
|
});
|
|
@@ -19793,7 +19855,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19793
19855
|
llmUsageLogger.logSessionSummary(prompt?.substring(0, 50));
|
|
19794
19856
|
if (userId) {
|
|
19795
19857
|
const latencyMs = Date.now() - requestStartTime;
|
|
19796
|
-
const
|
|
19858
|
+
const turn = reported(userResponse);
|
|
19859
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19797
19860
|
const componentConfig = userResponse.data?.component?.props?.config;
|
|
19798
19861
|
const queryMap = componentConfig?._queryMap || {};
|
|
19799
19862
|
const sqlStatements = Object.values(queryMap).filter((v) => typeof v === "string");
|
|
@@ -19825,7 +19888,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19825
19888
|
sourcesUsed: sourcesUsed.length > 0 ? sourcesUsed : void 0,
|
|
19826
19889
|
sqlGenerated: sqlStatements.length > 0 ? sqlStatements.join(";\n") : void 0,
|
|
19827
19890
|
conversationId: savedConversationId,
|
|
19828
|
-
response: savedResponse
|
|
19891
|
+
response: savedResponse,
|
|
19892
|
+
usage: turn.usage
|
|
19829
19893
|
});
|
|
19830
19894
|
analyticsClient.track(event).catch(() => {
|
|
19831
19895
|
});
|
|
@@ -19848,7 +19912,7 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
|
|
|
19848
19912
|
id: `comp_${Math.random().toString(36).substring(2, 8)}`
|
|
19849
19913
|
}));
|
|
19850
19914
|
}
|
|
19851
|
-
const { trace: _trace, ...dataWithoutTrace } = response.data ?? {};
|
|
19915
|
+
const { trace: _trace, usage: _usage, model: _model, ...dataWithoutTrace } = response.data ?? {};
|
|
19852
19916
|
sendDataResponse4(
|
|
19853
19917
|
response.id || data.id,
|
|
19854
19918
|
{
|
|
@@ -23938,7 +24002,7 @@ var processDashCompRequest = async (data, components, _sendMessage, anthropicApi
|
|
|
23938
24002
|
const conversationHistory = dashboardId ? dashboardConversationHistory.getHistory(dashboardId, userId) : void 0;
|
|
23939
24003
|
const promptContext = `DASH_COMP[${reqType}]: ${prompt?.substring(0, 50)}${(prompt?.length || 0) > 50 ? "..." : ""}`;
|
|
23940
24004
|
llmUsageLogger.resetLogFile(promptContext);
|
|
23941
|
-
logger.clearFile();
|
|
24005
|
+
logger.clearFile(promptContext);
|
|
23942
24006
|
if (!prompt) {
|
|
23943
24007
|
errors.push("Prompt is required");
|
|
23944
24008
|
}
|
|
@@ -24469,6 +24533,9 @@ ${marker}
|
|
|
24469
24533
|
}
|
|
24470
24534
|
|
|
24471
24535
|
// src/codingAgent/drivers/pi.ts
|
|
24536
|
+
import * as crypto3 from "crypto";
|
|
24537
|
+
import * as fs8 from "fs";
|
|
24538
|
+
import * as path9 from "path";
|
|
24472
24539
|
var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
24473
24540
|
var piModulePromise = null;
|
|
24474
24541
|
function loadPi() {
|
|
@@ -24480,46 +24547,111 @@ function loadPi() {
|
|
|
24480
24547
|
}
|
|
24481
24548
|
return piModulePromise;
|
|
24482
24549
|
}
|
|
24483
|
-
var
|
|
24484
|
-
function
|
|
24485
|
-
if (!
|
|
24486
|
-
|
|
24487
|
-
|
|
24488
|
-
|
|
24550
|
+
var runtimePromise = null;
|
|
24551
|
+
function modelRuntime(pi) {
|
|
24552
|
+
if (!runtimePromise) {
|
|
24553
|
+
const agentDir = pi.getAgentDir();
|
|
24554
|
+
runtimePromise = pi.ModelRuntime.create({
|
|
24555
|
+
authPath: `${agentDir}/auth.json`,
|
|
24556
|
+
modelsStorePath: `${agentDir}/models-store.json`,
|
|
24557
|
+
allowModelNetwork: true
|
|
24558
|
+
}).catch((err) => {
|
|
24559
|
+
runtimePromise = null;
|
|
24560
|
+
throw err;
|
|
24489
24561
|
});
|
|
24490
24562
|
}
|
|
24491
|
-
return
|
|
24563
|
+
return runtimePromise;
|
|
24492
24564
|
}
|
|
24493
|
-
var DEFAULT_PROVIDER = "openai-codex";
|
|
24494
|
-
var DEFAULT_MODEL_ID = process.env.PI_AGENT_MODEL || "gpt-5.
|
|
24565
|
+
var DEFAULT_PROVIDER = process.env.PI_PROVIDER || "openai-codex";
|
|
24566
|
+
var DEFAULT_MODEL_ID = process.env.PI_MODEL || process.env.PI_AGENT_MODEL || "gpt-5.6-luna";
|
|
24495
24567
|
var KNOWN_UNUSABLE_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.3-codex-spark"]);
|
|
24496
|
-
async function resolveModel(
|
|
24497
|
-
const
|
|
24498
|
-
const
|
|
24499
|
-
const wanted =
|
|
24500
|
-
const
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
if (
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24568
|
+
async function resolveModel(runtime, spec) {
|
|
24569
|
+
const slash = spec?.indexOf("/") ?? -1;
|
|
24570
|
+
const provider = slash > 0 ? spec.slice(0, slash) : DEFAULT_PROVIDER;
|
|
24571
|
+
const wanted = slash > 0 ? spec.slice(slash + 1) : spec || DEFAULT_MODEL_ID;
|
|
24572
|
+
const available = await runtime.getAvailable(provider);
|
|
24573
|
+
const exact = available.find((m) => m.id === wanted);
|
|
24574
|
+
if (exact) return exact;
|
|
24575
|
+
const fallback = available.find((m) => !KNOWN_UNUSABLE_MODEL_IDS.has(m.id)) ?? available[0];
|
|
24576
|
+
if (fallback) {
|
|
24577
|
+
console.warn(`[pi] model ${provider}/${wanted} unavailable \u2014 using ${fallback.provider}/${fallback.id}`);
|
|
24578
|
+
return fallback;
|
|
24579
|
+
}
|
|
24580
|
+
throw new Error(`No model available from "${provider}". Authorise it in a terminal on this machine: run \`pi\`, then \`/login\`.`);
|
|
24581
|
+
}
|
|
24582
|
+
var SHELL_ENV_ALLOWLIST = [
|
|
24583
|
+
"PATH",
|
|
24584
|
+
"Path",
|
|
24585
|
+
"HOME",
|
|
24586
|
+
"USER",
|
|
24587
|
+
"USERNAME",
|
|
24588
|
+
"LOGNAME",
|
|
24589
|
+
"SHELL",
|
|
24590
|
+
"LANG",
|
|
24591
|
+
"LC_ALL",
|
|
24592
|
+
"TERM",
|
|
24593
|
+
"TMPDIR",
|
|
24594
|
+
"TEMP",
|
|
24595
|
+
"TMP",
|
|
24596
|
+
"NODE_EXTRA_CA_CERTS",
|
|
24597
|
+
// Windows: without these a shell cannot start programs.
|
|
24598
|
+
"SystemRoot",
|
|
24599
|
+
"SYSTEMROOT",
|
|
24600
|
+
"windir",
|
|
24601
|
+
"ComSpec",
|
|
24602
|
+
"COMSPEC",
|
|
24603
|
+
"PATHEXT",
|
|
24604
|
+
"USERPROFILE",
|
|
24605
|
+
"APPDATA",
|
|
24606
|
+
"LOCALAPPDATA",
|
|
24607
|
+
"HOMEDRIVE",
|
|
24608
|
+
"HOMEPATH",
|
|
24609
|
+
"ProgramFiles",
|
|
24610
|
+
"ProgramData",
|
|
24611
|
+
"MSYSTEM"
|
|
24612
|
+
];
|
|
24613
|
+
function shellEnv(run, inherit) {
|
|
24614
|
+
if (inherit) return { ...process.env, ...run };
|
|
24615
|
+
const env = {};
|
|
24616
|
+
for (const key of SHELL_ENV_ALLOWLIST) {
|
|
24617
|
+
if (process.env[key] !== void 0) env[key] = process.env[key];
|
|
24618
|
+
}
|
|
24619
|
+
return { ...env, ...run };
|
|
24620
|
+
}
|
|
24621
|
+
function bashTool(pi, cwd, env) {
|
|
24622
|
+
const local = pi.createLocalBashOperations();
|
|
24623
|
+
const definition = pi.createBashToolDefinition(cwd, {
|
|
24624
|
+
operations: {
|
|
24625
|
+
exec: (command, dir, options) => local.exec(command, dir, { ...options, timeout: void 0, env: env() })
|
|
24626
|
+
}
|
|
24627
|
+
});
|
|
24628
|
+
const parameters = definition.parameters;
|
|
24629
|
+
return {
|
|
24630
|
+
...definition,
|
|
24631
|
+
description: definition.description.replace(/\s*Optionally provide a timeout in seconds\./, ""),
|
|
24632
|
+
parameters: { ...parameters, properties: { command: parameters.properties.command }, required: ["command"] }
|
|
24633
|
+
};
|
|
24508
24634
|
}
|
|
24509
24635
|
function emptyUsage2() {
|
|
24510
24636
|
return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, toolCalls: 0 };
|
|
24511
24637
|
}
|
|
24512
24638
|
var BUFFER_CAP2 = 64e3;
|
|
24513
24639
|
var EVENT_CAP2 = 600;
|
|
24514
|
-
function
|
|
24515
|
-
|
|
24516
|
-
|
|
24517
|
-
|
|
24518
|
-
|
|
24519
|
-
|
|
24520
|
-
|
|
24521
|
-
|
|
24640
|
+
function textOf(content) {
|
|
24641
|
+
const blocks = content?.content ?? content;
|
|
24642
|
+
if (!Array.isArray(blocks)) return typeof content === "string" ? content : "";
|
|
24643
|
+
return blocks.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("").trim();
|
|
24644
|
+
}
|
|
24645
|
+
function assistantParts(message) {
|
|
24646
|
+
const record = message;
|
|
24647
|
+
if (record?.role !== "assistant" || !Array.isArray(record.content)) return { text: "", thinking: "" };
|
|
24648
|
+
let text = "";
|
|
24649
|
+
let thinking = "";
|
|
24650
|
+
for (const block of record.content) {
|
|
24651
|
+
if (block?.type === "text") text += String(block.text ?? "");
|
|
24652
|
+
else if (block?.type === "thinking" && !block.redacted) thinking += String(block.thinking ?? "");
|
|
24522
24653
|
}
|
|
24654
|
+
return { text, thinking };
|
|
24523
24655
|
}
|
|
24524
24656
|
function jsonPreview(v) {
|
|
24525
24657
|
if (v == null) return "";
|
|
@@ -24529,10 +24661,10 @@ function jsonPreview(v) {
|
|
|
24529
24661
|
return "";
|
|
24530
24662
|
}
|
|
24531
24663
|
}
|
|
24532
|
-
function
|
|
24533
|
-
|
|
24534
|
-
if (
|
|
24535
|
-
return
|
|
24664
|
+
function toolEvent(name, id, args) {
|
|
24665
|
+
if (name === "bash") return { kind: "command", id, command: String(args?.command ?? "") };
|
|
24666
|
+
if (name === "write" || name === "edit") return { kind: "file", id, text: String(args?.path ?? args?.file_path ?? "") };
|
|
24667
|
+
return { kind: "tool", id, tool: { name: name ?? "tool", args } };
|
|
24536
24668
|
}
|
|
24537
24669
|
var TRANSCRIPT_CAP = 400;
|
|
24538
24670
|
var sessionListeners = /* @__PURE__ */ new Set();
|
|
@@ -24550,9 +24682,6 @@ function publish(sessionKey, live, entry) {
|
|
|
24550
24682
|
for (const listener of sessionListeners) listener(sessionKey, entry);
|
|
24551
24683
|
}
|
|
24552
24684
|
var liveSessions = /* @__PURE__ */ new Map();
|
|
24553
|
-
function newSessionId() {
|
|
24554
|
-
return `pi_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
24555
|
-
}
|
|
24556
24685
|
var IDLE_TIMEOUT_MS = Number(process.env.PI_AGENT_IDLE_MS ?? 30 * 60 * 1e3);
|
|
24557
24686
|
var sweeper = null;
|
|
24558
24687
|
function startSweeper() {
|
|
@@ -24571,29 +24700,76 @@ function startSweeper() {
|
|
|
24571
24700
|
}, 6e4);
|
|
24572
24701
|
sweeper.unref?.();
|
|
24573
24702
|
}
|
|
24703
|
+
function readInstructions(opts) {
|
|
24704
|
+
if (!opts.instructionsFile) return void 0;
|
|
24705
|
+
const file = path9.resolve(opts.cwd, opts.instructionsFile);
|
|
24706
|
+
try {
|
|
24707
|
+
return { file, content: fs8.readFileSync(file, "utf-8") };
|
|
24708
|
+
} catch {
|
|
24709
|
+
return void 0;
|
|
24710
|
+
}
|
|
24711
|
+
}
|
|
24712
|
+
var hash = (s) => crypto3.createHash("sha256").update(s).digest("hex");
|
|
24574
24713
|
function createPiSession(opts) {
|
|
24575
24714
|
let running = false;
|
|
24576
|
-
let sessionKey = opts.resumeId
|
|
24577
|
-
async function
|
|
24578
|
-
const
|
|
24579
|
-
|
|
24580
|
-
const
|
|
24581
|
-
const
|
|
24582
|
-
const
|
|
24583
|
-
const
|
|
24715
|
+
let sessionKey = opts.resumeId;
|
|
24716
|
+
async function open(resumeFile) {
|
|
24717
|
+
const pi = await loadPi();
|
|
24718
|
+
const runtime = await modelRuntime(pi);
|
|
24719
|
+
const model = await resolveModel(runtime, opts.model);
|
|
24720
|
+
const agentDir = pi.getAgentDir();
|
|
24721
|
+
const instructions = readInstructions(opts);
|
|
24722
|
+
const resourceLoader = new pi.DefaultResourceLoader({
|
|
24723
|
+
cwd: opts.cwd,
|
|
24724
|
+
agentDir,
|
|
24725
|
+
// The workspace's own instructions, and only them — not whatever
|
|
24726
|
+
// AGENTS.md files the machine's user keeps in ~/.pi or a parent folder.
|
|
24727
|
+
agentsFilesOverride: (base) => instructions ? { agentsFiles: [{ path: instructions.file, content: instructions.content }] } : base
|
|
24728
|
+
});
|
|
24729
|
+
await resourceLoader.reload();
|
|
24730
|
+
const sessionManager = resumeFile && fs8.existsSync(resumeFile) ? pi.SessionManager.open(resumeFile) : pi.SessionManager.create(opts.cwd);
|
|
24731
|
+
const live = {
|
|
24732
|
+
running: false,
|
|
24733
|
+
transcript: [],
|
|
24734
|
+
lastActive: Date.now(),
|
|
24735
|
+
eventLog: [],
|
|
24736
|
+
buf: "",
|
|
24737
|
+
env: opts.env,
|
|
24738
|
+
inheritEnv: opts.inheritEnv,
|
|
24739
|
+
instructions: hash(instructions?.content ?? "")
|
|
24740
|
+
};
|
|
24741
|
+
const { session } = await pi.createAgentSession({
|
|
24584
24742
|
cwd: opts.cwd,
|
|
24585
|
-
|
|
24586
|
-
|
|
24587
|
-
modelRegistry,
|
|
24743
|
+
agentDir,
|
|
24744
|
+
modelRuntime: runtime,
|
|
24588
24745
|
model,
|
|
24589
|
-
tools: opts.tools
|
|
24746
|
+
...opts.tools ? { tools: opts.tools } : {},
|
|
24747
|
+
customTools: [bashTool(pi, opts.cwd, () => shellEnv(live.env, live.inheritEnv))],
|
|
24748
|
+
resourceLoader,
|
|
24749
|
+
sessionManager
|
|
24590
24750
|
});
|
|
24591
|
-
|
|
24592
|
-
const
|
|
24593
|
-
|
|
24751
|
+
live.session = session;
|
|
24752
|
+
const file = sessionManager.getSessionFile();
|
|
24753
|
+
if (!file) throw new Error("pi did not give this session a file, so it could not be resumed later.");
|
|
24754
|
+
sessionKey = file;
|
|
24755
|
+
liveSessions.set(file, live);
|
|
24594
24756
|
startSweeper();
|
|
24595
24757
|
return live;
|
|
24596
24758
|
}
|
|
24759
|
+
async function ensureSession() {
|
|
24760
|
+
const existing = sessionKey ? liveSessions.get(sessionKey) : void 0;
|
|
24761
|
+
if (existing) {
|
|
24762
|
+
if (existing.instructions !== hash(readInstructions(opts)?.content ?? "") && !existing.running) {
|
|
24763
|
+
existing.session.dispose();
|
|
24764
|
+
liveSessions.delete(sessionKey);
|
|
24765
|
+
return open(sessionKey);
|
|
24766
|
+
}
|
|
24767
|
+
existing.env = opts.env;
|
|
24768
|
+
existing.inheritEnv = opts.inheritEnv;
|
|
24769
|
+
return existing;
|
|
24770
|
+
}
|
|
24771
|
+
return open(sessionKey);
|
|
24772
|
+
}
|
|
24597
24773
|
async function runTurn(prompt, handlers, signal) {
|
|
24598
24774
|
const startedAt = Date.now();
|
|
24599
24775
|
const usage = emptyUsage2();
|
|
@@ -24625,29 +24801,70 @@ function createPiSession(opts) {
|
|
|
24625
24801
|
live.buf = (live.buf + chunk).slice(-BUFFER_CAP2);
|
|
24626
24802
|
handlers?.onOutput?.(chunk);
|
|
24627
24803
|
};
|
|
24804
|
+
const send = (event) => {
|
|
24805
|
+
live.eventLog.push(event);
|
|
24806
|
+
if (live.eventLog.length > EVENT_CAP2) live.eventLog.shift();
|
|
24807
|
+
handlers?.onEvent?.(event);
|
|
24808
|
+
publish(key, live, { at: Date.now(), kind: "activity", event });
|
|
24809
|
+
};
|
|
24810
|
+
let streamed = 0;
|
|
24811
|
+
let messageSeq = 0;
|
|
24812
|
+
const started = /* @__PURE__ */ new Map();
|
|
24628
24813
|
const unsubscribe = live.session.subscribe((ev) => {
|
|
24629
|
-
|
|
24630
|
-
|
|
24631
|
-
|
|
24632
|
-
|
|
24633
|
-
|
|
24814
|
+
switch (ev.type) {
|
|
24815
|
+
case "turn_start":
|
|
24816
|
+
send({ kind: "turn" });
|
|
24817
|
+
return;
|
|
24818
|
+
case "message_start":
|
|
24819
|
+
streamed = 0;
|
|
24820
|
+
return;
|
|
24821
|
+
case "message_update": {
|
|
24822
|
+
const { text } = assistantParts(ev.message);
|
|
24823
|
+
if (text.length > streamed) {
|
|
24824
|
+
const delta = text.slice(streamed);
|
|
24825
|
+
streamed = text.length;
|
|
24826
|
+
emit(delta);
|
|
24827
|
+
handlers?.onNarration?.(delta);
|
|
24828
|
+
}
|
|
24829
|
+
return;
|
|
24634
24830
|
}
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
|
|
24644
|
-
|
|
24645
|
-
|
|
24831
|
+
case "message_end": {
|
|
24832
|
+
const { text, thinking } = assistantParts(ev.message);
|
|
24833
|
+
const id = `msg_${++messageSeq}`;
|
|
24834
|
+
if (thinking.trim()) send({ kind: "reasoning", id: `${id}_r`, text: thinking.trim(), status: "completed", done: true });
|
|
24835
|
+
if (text.trim()) send({ kind: "message", id, text: text.trim(), status: "completed", done: true });
|
|
24836
|
+
streamed = 0;
|
|
24837
|
+
return;
|
|
24838
|
+
}
|
|
24839
|
+
case "tool_execution_start": {
|
|
24840
|
+
const event = { ...toolEvent(ev.toolName, ev.toolCallId, ev.args), status: "in_progress" };
|
|
24841
|
+
if (ev.toolCallId) started.set(ev.toolCallId, event);
|
|
24842
|
+
send(event);
|
|
24843
|
+
emit(`
|
|
24646
24844
|
\u2192 ${ev.toolName ?? "tool"} ${jsonPreview(ev.args)}
|
|
24647
24845
|
`);
|
|
24648
|
-
|
|
24846
|
+
return;
|
|
24847
|
+
}
|
|
24848
|
+
case "tool_execution_end": {
|
|
24849
|
+
usage.toolCalls++;
|
|
24850
|
+
const start = ev.toolCallId && started.get(ev.toolCallId) || toolEvent(ev.toolName, ev.toolCallId, void 0);
|
|
24851
|
+
send({ ...start, status: ev.isError ? "failed" : "completed", done: true, output: textOf(ev.result) });
|
|
24852
|
+
return;
|
|
24853
|
+
}
|
|
24854
|
+
default:
|
|
24855
|
+
return;
|
|
24649
24856
|
}
|
|
24650
24857
|
});
|
|
24858
|
+
let early = false;
|
|
24859
|
+
const poll = handlers?.doneWhen ? setInterval(async () => {
|
|
24860
|
+
try {
|
|
24861
|
+
if (!early && await handlers.doneWhen()) {
|
|
24862
|
+
early = true;
|
|
24863
|
+
void live.session.abort();
|
|
24864
|
+
}
|
|
24865
|
+
} catch {
|
|
24866
|
+
}
|
|
24867
|
+
}, 1e3) : null;
|
|
24651
24868
|
try {
|
|
24652
24869
|
if (signal?.aborted) {
|
|
24653
24870
|
status = "aborted";
|
|
@@ -24655,16 +24872,20 @@ function createPiSession(opts) {
|
|
|
24655
24872
|
}
|
|
24656
24873
|
await live.session.prompt(prompt);
|
|
24657
24874
|
const err = live.session.agent.state.errorMessage;
|
|
24658
|
-
if (
|
|
24659
|
-
|
|
24875
|
+
if (early) status = "early";
|
|
24876
|
+
else if (signal?.aborted) status = "aborted";
|
|
24877
|
+
else if (err) {
|
|
24878
|
+
status = "error";
|
|
24660
24879
|
error = err;
|
|
24661
|
-
} else if (signal?.aborted) {
|
|
24662
|
-
status = "aborted";
|
|
24663
24880
|
}
|
|
24664
24881
|
} catch (err) {
|
|
24665
|
-
status =
|
|
24666
|
-
|
|
24882
|
+
if (early) status = "early";
|
|
24883
|
+
else {
|
|
24884
|
+
status = signal?.aborted ? "aborted" : "error";
|
|
24885
|
+
error = err?.message ?? String(err);
|
|
24886
|
+
}
|
|
24667
24887
|
} finally {
|
|
24888
|
+
if (poll) clearInterval(poll);
|
|
24668
24889
|
unsubscribe();
|
|
24669
24890
|
signal?.removeEventListener("abort", onAbort);
|
|
24670
24891
|
live.running = false;
|
|
@@ -24705,8 +24926,8 @@ function createPiSession(opts) {
|
|
|
24705
24926
|
}
|
|
24706
24927
|
sessionKey = void 0;
|
|
24707
24928
|
},
|
|
24708
|
-
// Local-only, same as the codex driver: the live session stays in the
|
|
24709
|
-
//
|
|
24929
|
+
// Local-only, same as the codex driver: the live session stays in the map
|
|
24930
|
+
// so a later run with this sessionId resumes it.
|
|
24710
24931
|
dispose: () => {
|
|
24711
24932
|
}
|
|
24712
24933
|
};
|
|
@@ -24725,11 +24946,11 @@ function createCodingAgentSession(harness, opts) {
|
|
|
24725
24946
|
}
|
|
24726
24947
|
|
|
24727
24948
|
// src/codingAgent/registry.ts
|
|
24728
|
-
import * as
|
|
24949
|
+
import * as path10 from "path";
|
|
24729
24950
|
var agents = /* @__PURE__ */ new Map();
|
|
24730
24951
|
var config = {
|
|
24731
|
-
promptRoot:
|
|
24732
|
-
runtimeDir:
|
|
24952
|
+
promptRoot: path10.join(process.cwd(), ".prompts", "coding-agents"),
|
|
24953
|
+
runtimeDir: path10.join(process.cwd(), ".coding-agent-runtime"),
|
|
24733
24954
|
harness: "codex",
|
|
24734
24955
|
keepRuns: 20,
|
|
24735
24956
|
keepThreads: 200
|
|
@@ -24768,13 +24989,13 @@ function getCodingAgentsConfig() {
|
|
|
24768
24989
|
return config;
|
|
24769
24990
|
}
|
|
24770
24991
|
function resolvePromptDir(def) {
|
|
24771
|
-
return def.promptDir ||
|
|
24992
|
+
return def.promptDir || path10.join(config.promptRoot, def.id);
|
|
24772
24993
|
}
|
|
24773
24994
|
|
|
24774
24995
|
// src/codingAgent/runner.ts
|
|
24775
|
-
import * as
|
|
24996
|
+
import * as fs9 from "fs";
|
|
24776
24997
|
import * as fsp from "fs/promises";
|
|
24777
|
-
import * as
|
|
24998
|
+
import * as path11 from "path";
|
|
24778
24999
|
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
24779
25000
|
var inFlight = /* @__PURE__ */ new Set();
|
|
24780
25001
|
function slug(v) {
|
|
@@ -24784,11 +25005,11 @@ function resolveScope(def, opts) {
|
|
|
24784
25005
|
const scope = def.scope ?? (def.mode === "chat" ? "thread" : "project");
|
|
24785
25006
|
if (scope === "thread") {
|
|
24786
25007
|
const id = slug(opts.threadId || "default");
|
|
24787
|
-
return { key: `thread:${id}`, dirName:
|
|
25008
|
+
return { key: `thread:${id}`, dirName: path11.join("threads", id) };
|
|
24788
25009
|
}
|
|
24789
25010
|
if (scope === "user") {
|
|
24790
25011
|
const id = slug(opts.userId || "anonymous");
|
|
24791
|
-
return { key: `user:${id}`, dirName:
|
|
25012
|
+
return { key: `user:${id}`, dirName: path11.join("users", id) };
|
|
24792
25013
|
}
|
|
24793
25014
|
return { key: "project", dirName: "_project" };
|
|
24794
25015
|
}
|
|
@@ -24797,30 +25018,30 @@ function makeRunId() {
|
|
|
24797
25018
|
return `${t}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24798
25019
|
}
|
|
24799
25020
|
function statePath(scopeDir) {
|
|
24800
|
-
return
|
|
25021
|
+
return path11.join(scopeDir, "_state.json");
|
|
24801
25022
|
}
|
|
24802
25023
|
function loadState(scopeDir) {
|
|
24803
25024
|
try {
|
|
24804
|
-
return JSON.parse(
|
|
25025
|
+
return JSON.parse(fs9.readFileSync(statePath(scopeDir), "utf-8"));
|
|
24805
25026
|
} catch {
|
|
24806
25027
|
return { turns: 0, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
24807
25028
|
}
|
|
24808
25029
|
}
|
|
24809
25030
|
function saveState(scopeDir, state) {
|
|
24810
25031
|
try {
|
|
24811
|
-
|
|
24812
|
-
|
|
25032
|
+
fs9.mkdirSync(scopeDir, { recursive: true });
|
|
25033
|
+
fs9.writeFileSync(statePath(scopeDir), JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
24813
25034
|
} catch (err) {
|
|
24814
25035
|
logger.warn(`[CodingAgent] failed to persist session state: ${err?.message ?? err}`);
|
|
24815
25036
|
}
|
|
24816
25037
|
}
|
|
24817
25038
|
async function clearTransient(dir) {
|
|
24818
|
-
await fsp.rm(
|
|
25039
|
+
await fsp.rm(path11.join(dir, "input"), { recursive: true, force: true });
|
|
24819
25040
|
}
|
|
24820
25041
|
function createWorkspace(dir, runId) {
|
|
24821
|
-
const abs = (rel) =>
|
|
25042
|
+
const abs = (rel) => path11.join(dir, rel);
|
|
24822
25043
|
const ensureDir = async (rel) => {
|
|
24823
|
-
await fsp.mkdir(
|
|
25044
|
+
await fsp.mkdir(path11.dirname(abs(rel)), { recursive: true });
|
|
24824
25045
|
};
|
|
24825
25046
|
return {
|
|
24826
25047
|
dir,
|
|
@@ -24849,7 +25070,7 @@ function createWorkspace(dir, runId) {
|
|
|
24849
25070
|
}
|
|
24850
25071
|
var INSTRUCTIONS_FILE = "system.md";
|
|
24851
25072
|
async function installInstructions(def, ws) {
|
|
24852
|
-
const src =
|
|
25073
|
+
const src = path11.join(resolvePromptDir(def), "system.md");
|
|
24853
25074
|
try {
|
|
24854
25075
|
await ws.write(INSTRUCTIONS_FILE, await fsp.readFile(src, "utf-8"));
|
|
24855
25076
|
} catch {
|
|
@@ -24863,7 +25084,7 @@ async function pruneThreads(agentId, threadsDir, keep) {
|
|
|
24863
25084
|
const dirs = (await fsp.readdir(threadsDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
24864
25085
|
if (dirs.length <= keep) return;
|
|
24865
25086
|
const aged = await Promise.all(dirs.map(async (e) => {
|
|
24866
|
-
const dir =
|
|
25087
|
+
const dir = path11.join(threadsDir, e.name);
|
|
24867
25088
|
const stat2 = await fsp.stat(statePath(dir)).catch(() => fsp.stat(dir));
|
|
24868
25089
|
return { name: e.name, dir, used: stat2.mtimeMs };
|
|
24869
25090
|
}));
|
|
@@ -24883,7 +25104,7 @@ async function pruneRuns(agentDir, keep) {
|
|
|
24883
25104
|
try {
|
|
24884
25105
|
const entries = (await fsp.readdir(agentDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
24885
25106
|
for (const name of entries.slice(0, Math.max(0, entries.length - keep))) {
|
|
24886
|
-
await fsp.rm(
|
|
25107
|
+
await fsp.rm(path11.join(agentDir, name), { recursive: true, force: true });
|
|
24887
25108
|
}
|
|
24888
25109
|
} catch {
|
|
24889
25110
|
}
|
|
@@ -24898,17 +25119,17 @@ async function runCodingAgent(agentId, options = {}) {
|
|
|
24898
25119
|
}
|
|
24899
25120
|
const cfg = getCodingAgentsConfig();
|
|
24900
25121
|
const runId = makeRunId();
|
|
24901
|
-
const agentDir =
|
|
25122
|
+
const agentDir = path11.join(cfg.runtimeDir, def.id);
|
|
24902
25123
|
const policy = def.session ?? {};
|
|
24903
25124
|
const persistentWorkspace = policy.workspace === "persistent";
|
|
24904
25125
|
const persistentSession = policy.scope === "persistent";
|
|
24905
25126
|
const runAsJob = !(options.chat === true && def.mode === "task");
|
|
24906
25127
|
const { key: scopeKey, dirName } = resolveScope(def, { userId: options.userId, threadId: options.threadId });
|
|
24907
|
-
const scopeDir =
|
|
24908
|
-
const dir = options.workspaceDir || (persistentWorkspace ? scopeDir :
|
|
24909
|
-
|
|
25128
|
+
const scopeDir = path11.join(agentDir, dirName);
|
|
25129
|
+
const dir = options.workspaceDir || (persistentWorkspace ? scopeDir : path11.join(scopeDir, "runs", runId));
|
|
25130
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
24910
25131
|
if (persistentWorkspace && !options.workspaceDir) {
|
|
24911
|
-
|
|
25132
|
+
fs9.mkdirSync(path11.join(dir, "memory"), { recursive: true });
|
|
24912
25133
|
if (runAsJob) await clearTransient(dir);
|
|
24913
25134
|
}
|
|
24914
25135
|
const lockKey = persistentWorkspace || persistentSession ? `${def.id}:${scopeKey}` : `dir:${dir}`;
|
|
@@ -25009,7 +25230,7 @@ async function runCodingAgent(agentId, options = {}) {
|
|
|
25009
25230
|
onUsage: ctx.onUsage,
|
|
25010
25231
|
// Opt-in only: see stopOnDeliverable. Letting the turn end naturally
|
|
25011
25232
|
// is what makes usage reporting work at all.
|
|
25012
|
-
...outputPath && def.stopOnDeliverable ? { doneWhen: () =>
|
|
25233
|
+
...outputPath && def.stopOnDeliverable ? { doneWhen: () => fs9.existsSync(outputPath) } : {}
|
|
25013
25234
|
},
|
|
25014
25235
|
timeout.signal
|
|
25015
25236
|
);
|
|
@@ -25113,9 +25334,9 @@ Fix ONLY that, and rewrite the same file at the same path. Do not re-run any que
|
|
|
25113
25334
|
logger.info(
|
|
25114
25335
|
`[CodingAgent:${def.id}] ${runAsJob ? "run" : "chat"}=${runId} status=${record.status} ms=${record.ms} tokens=${record.usage.inputTokens}/${record.usage.outputTokens} tools=${record.usage.toolCalls}` + (record.applied ? ` applied="${record.applied.summary}"` : "") + (record.error ? ` error="${record.error}"` : "")
|
|
25115
25336
|
);
|
|
25116
|
-
if (!options.workspaceDir && !persistentWorkspace) await pruneRuns(
|
|
25117
|
-
if (!options.workspaceDir && dirName.startsWith(`threads${
|
|
25118
|
-
await pruneThreads(def.id,
|
|
25337
|
+
if (!options.workspaceDir && !persistentWorkspace) await pruneRuns(path11.join(scopeDir, "runs"), cfg.keepRuns);
|
|
25338
|
+
if (!options.workspaceDir && dirName.startsWith(`threads${path11.sep}`)) {
|
|
25339
|
+
await pruneThreads(def.id, path11.join(agentDir, "threads"), cfg.keepThreads);
|
|
25119
25340
|
}
|
|
25120
25341
|
}
|
|
25121
25342
|
}
|
|
@@ -25175,14 +25396,14 @@ ${assertReadOnlySql.toString().replace("export function", "function")}
|
|
|
25175
25396
|
`;
|
|
25176
25397
|
|
|
25177
25398
|
// src/codingAgent/tools/shims.ts
|
|
25178
|
-
import * as
|
|
25179
|
-
import * as
|
|
25180
|
-
var
|
|
25399
|
+
import * as fs10 from "fs";
|
|
25400
|
+
import * as path12 from "path";
|
|
25401
|
+
var resolve3 = (target, name) => typeof target === "string" ? path12.join(target, name) : target.pathTo(name);
|
|
25181
25402
|
function writeShims(defs, target) {
|
|
25182
25403
|
for (const def of defs) {
|
|
25183
|
-
const file =
|
|
25184
|
-
|
|
25185
|
-
|
|
25404
|
+
const file = resolve3(target, def.name);
|
|
25405
|
+
fs10.writeFileSync(file, def.body, "utf-8");
|
|
25406
|
+
fs10.chmodSync(file, 493);
|
|
25186
25407
|
}
|
|
25187
25408
|
return defs.map((def) => ({ name: `./${def.name}`, summary: def.summary }));
|
|
25188
25409
|
}
|
|
@@ -25441,17 +25662,17 @@ console.log(${JSON.stringify(
|
|
|
25441
25662
|
}
|
|
25442
25663
|
|
|
25443
25664
|
// src/codingAgent/triggers.ts
|
|
25444
|
-
import * as
|
|
25445
|
-
import * as
|
|
25665
|
+
import * as fs11 from "fs";
|
|
25666
|
+
import * as path13 from "path";
|
|
25446
25667
|
var EVENT_BURST_CAP = 200;
|
|
25447
25668
|
var capEvents = (events) => events.length > EVENT_BURST_CAP ? events.slice(-EVENT_BURST_CAP) : events;
|
|
25448
25669
|
function statePath2(agentId) {
|
|
25449
|
-
return
|
|
25670
|
+
return path13.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
|
|
25450
25671
|
}
|
|
25451
25672
|
function loadTriggerState(agentId) {
|
|
25452
25673
|
let raw;
|
|
25453
25674
|
try {
|
|
25454
|
-
raw = JSON.parse(
|
|
25675
|
+
raw = JSON.parse(fs11.readFileSync(statePath2(agentId), "utf-8"));
|
|
25455
25676
|
} catch {
|
|
25456
25677
|
return { counts: {}, pending: {} };
|
|
25457
25678
|
}
|
|
@@ -25471,8 +25692,8 @@ function loadTriggerState(agentId) {
|
|
|
25471
25692
|
function saveTriggerState(agentId, state) {
|
|
25472
25693
|
try {
|
|
25473
25694
|
const p = statePath2(agentId);
|
|
25474
|
-
|
|
25475
|
-
|
|
25695
|
+
fs11.mkdirSync(path13.dirname(p), { recursive: true });
|
|
25696
|
+
fs11.writeFileSync(p, JSON.stringify(state, null, 2));
|
|
25476
25697
|
} catch (err) {
|
|
25477
25698
|
logger.warn(`[CodingAgent:${agentId}] failed to persist trigger state: ${err?.message ?? err}`);
|
|
25478
25699
|
}
|
|
@@ -25608,13 +25829,13 @@ function describeTriggers() {
|
|
|
25608
25829
|
}
|
|
25609
25830
|
|
|
25610
25831
|
// src/codingAgent/collection.ts
|
|
25611
|
-
import * as
|
|
25612
|
-
import * as
|
|
25832
|
+
import * as fs12 from "fs";
|
|
25833
|
+
import * as path14 from "path";
|
|
25613
25834
|
function isChattable(agent) {
|
|
25614
25835
|
return agent.mode !== "task";
|
|
25615
25836
|
}
|
|
25616
25837
|
function registerCodingAgentCollection(sdk, config2 = {}) {
|
|
25617
|
-
const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR ||
|
|
25838
|
+
const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path14.join(process.cwd(), ".coding-agent-runtime");
|
|
25618
25839
|
const defaultHarness = config2.harness || process.env.CODING_AGENT_HARNESS || "codex";
|
|
25619
25840
|
const sessions = /* @__PURE__ */ new Map();
|
|
25620
25841
|
const running = /* @__PURE__ */ new Set();
|
|
@@ -25674,8 +25895,8 @@ function registerCodingAgentCollection(sdk, config2 = {}) {
|
|
|
25674
25895
|
...record.status === "skipped" ? { error: `${def.title} is in the middle of a run \u2014 try again in a moment.` } : record.error ? { error: record.error } : {}
|
|
25675
25896
|
};
|
|
25676
25897
|
}
|
|
25677
|
-
const cwd =
|
|
25678
|
-
|
|
25898
|
+
const cwd = path14.join(runtimeDir, "threads", threadId);
|
|
25899
|
+
fs12.mkdirSync(cwd, { recursive: true });
|
|
25679
25900
|
let session = sessions.get(threadId);
|
|
25680
25901
|
if (!session) {
|
|
25681
25902
|
session = createCodingAgentSession(harness || defaultHarness, {
|
|
@@ -26954,8 +27175,8 @@ function getLLMProviders() {
|
|
|
26954
27175
|
}
|
|
26955
27176
|
|
|
26956
27177
|
// src/auth/user-manager.ts
|
|
26957
|
-
import
|
|
26958
|
-
import
|
|
27178
|
+
import fs13 from "fs";
|
|
27179
|
+
import path15 from "path";
|
|
26959
27180
|
import os2 from "os";
|
|
26960
27181
|
var UserManager = class {
|
|
26961
27182
|
/**
|
|
@@ -26968,7 +27189,7 @@ var UserManager = class {
|
|
|
26968
27189
|
this.hasChanged = false;
|
|
26969
27190
|
this.syncInterval = null;
|
|
26970
27191
|
this.isInitialized = false;
|
|
26971
|
-
this.filePath =
|
|
27192
|
+
this.filePath = path15.join(os2.homedir(), ".superatom", "projects", projectId, "users.json");
|
|
26972
27193
|
this.syncIntervalMs = syncIntervalMs;
|
|
26973
27194
|
}
|
|
26974
27195
|
/**
|
|
@@ -26993,20 +27214,20 @@ var UserManager = class {
|
|
|
26993
27214
|
*/
|
|
26994
27215
|
async loadUsersFromFile() {
|
|
26995
27216
|
try {
|
|
26996
|
-
const dir =
|
|
26997
|
-
if (!
|
|
27217
|
+
const dir = path15.dirname(this.filePath);
|
|
27218
|
+
if (!fs13.existsSync(dir)) {
|
|
26998
27219
|
logger.info(`Creating directory structure: ${dir}`);
|
|
26999
|
-
|
|
27220
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
27000
27221
|
}
|
|
27001
|
-
if (!
|
|
27222
|
+
if (!fs13.existsSync(this.filePath)) {
|
|
27002
27223
|
logger.info(`Users file does not exist at ${this.filePath}, creating with empty users`);
|
|
27003
27224
|
const initialData = { users: [] };
|
|
27004
|
-
|
|
27225
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(initialData, null, 4));
|
|
27005
27226
|
this.users = [];
|
|
27006
27227
|
this.hasChanged = false;
|
|
27007
27228
|
return;
|
|
27008
27229
|
}
|
|
27009
|
-
const fileContent =
|
|
27230
|
+
const fileContent = fs13.readFileSync(this.filePath, "utf-8");
|
|
27010
27231
|
const rawData = JSON.parse(fileContent);
|
|
27011
27232
|
const validatedData = UsersDataSchema.parse(rawData);
|
|
27012
27233
|
this.users = validatedData.users;
|
|
@@ -27025,16 +27246,16 @@ var UserManager = class {
|
|
|
27025
27246
|
return;
|
|
27026
27247
|
}
|
|
27027
27248
|
try {
|
|
27028
|
-
const dir =
|
|
27029
|
-
if (!
|
|
27030
|
-
|
|
27249
|
+
const dir = path15.dirname(this.filePath);
|
|
27250
|
+
if (!fs13.existsSync(dir)) {
|
|
27251
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
27031
27252
|
}
|
|
27032
27253
|
const usersToSave = this.users.map((user) => {
|
|
27033
27254
|
const { wsIds, ...userWithoutWsIds } = user;
|
|
27034
27255
|
return userWithoutWsIds;
|
|
27035
27256
|
});
|
|
27036
27257
|
const data = { users: usersToSave };
|
|
27037
|
-
|
|
27258
|
+
fs13.writeFileSync(this.filePath, JSON.stringify(data, null, 4));
|
|
27038
27259
|
this.hasChanged = false;
|
|
27039
27260
|
logger.debug(`Synced ${this.users.length} users to file (wsIds excluded)`);
|
|
27040
27261
|
} catch (error) {
|
|
@@ -27252,8 +27473,8 @@ var UserManager = class {
|
|
|
27252
27473
|
};
|
|
27253
27474
|
|
|
27254
27475
|
// src/dashboards/dashboard-manager.ts
|
|
27255
|
-
import
|
|
27256
|
-
import
|
|
27476
|
+
import fs14 from "fs";
|
|
27477
|
+
import path16 from "path";
|
|
27257
27478
|
import os3 from "os";
|
|
27258
27479
|
var DashboardManager = class {
|
|
27259
27480
|
/**
|
|
@@ -27262,7 +27483,7 @@ var DashboardManager = class {
|
|
|
27262
27483
|
*/
|
|
27263
27484
|
constructor(projectId = "snowflake-dataset") {
|
|
27264
27485
|
this.projectId = projectId;
|
|
27265
|
-
this.dashboardsBasePath =
|
|
27486
|
+
this.dashboardsBasePath = path16.join(
|
|
27266
27487
|
os3.homedir(),
|
|
27267
27488
|
".superatom",
|
|
27268
27489
|
"projects",
|
|
@@ -27276,7 +27497,7 @@ var DashboardManager = class {
|
|
|
27276
27497
|
* @returns Full path to dashboard data.json file
|
|
27277
27498
|
*/
|
|
27278
27499
|
getDashboardPath(dashboardId) {
|
|
27279
|
-
return
|
|
27500
|
+
return path16.join(this.dashboardsBasePath, dashboardId, "data.json");
|
|
27280
27501
|
}
|
|
27281
27502
|
/**
|
|
27282
27503
|
* Create a new dashboard
|
|
@@ -27286,13 +27507,13 @@ var DashboardManager = class {
|
|
|
27286
27507
|
*/
|
|
27287
27508
|
createDashboard(dashboardId, dashboard) {
|
|
27288
27509
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27289
|
-
const dashboardDir =
|
|
27290
|
-
if (
|
|
27510
|
+
const dashboardDir = path16.dirname(dashboardPath);
|
|
27511
|
+
if (fs14.existsSync(dashboardPath)) {
|
|
27291
27512
|
throw new Error(`Dashboard '${dashboardId}' already exists`);
|
|
27292
27513
|
}
|
|
27293
27514
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
27294
|
-
|
|
27295
|
-
|
|
27515
|
+
fs14.mkdirSync(dashboardDir, { recursive: true });
|
|
27516
|
+
fs14.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
|
|
27296
27517
|
logger.info(`Dashboard created: ${dashboardId}`);
|
|
27297
27518
|
return validated;
|
|
27298
27519
|
}
|
|
@@ -27303,12 +27524,12 @@ var DashboardManager = class {
|
|
|
27303
27524
|
*/
|
|
27304
27525
|
getDashboard(dashboardId) {
|
|
27305
27526
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27306
|
-
if (!
|
|
27527
|
+
if (!fs14.existsSync(dashboardPath)) {
|
|
27307
27528
|
logger.warn(`Dashboard not found: ${dashboardId}`);
|
|
27308
27529
|
return null;
|
|
27309
27530
|
}
|
|
27310
27531
|
try {
|
|
27311
|
-
const fileContent =
|
|
27532
|
+
const fileContent = fs14.readFileSync(dashboardPath, "utf-8");
|
|
27312
27533
|
const dashboard = JSON.parse(fileContent);
|
|
27313
27534
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
27314
27535
|
return validated;
|
|
@@ -27322,16 +27543,16 @@ var DashboardManager = class {
|
|
|
27322
27543
|
* @returns Array of dashboard objects with their IDs
|
|
27323
27544
|
*/
|
|
27324
27545
|
getAllDashboards() {
|
|
27325
|
-
if (!
|
|
27326
|
-
|
|
27546
|
+
if (!fs14.existsSync(this.dashboardsBasePath)) {
|
|
27547
|
+
fs14.mkdirSync(this.dashboardsBasePath, { recursive: true });
|
|
27327
27548
|
return [];
|
|
27328
27549
|
}
|
|
27329
27550
|
const dashboards = [];
|
|
27330
27551
|
try {
|
|
27331
|
-
const dashboardDirs =
|
|
27552
|
+
const dashboardDirs = fs14.readdirSync(this.dashboardsBasePath);
|
|
27332
27553
|
for (const dashboardId of dashboardDirs) {
|
|
27333
27554
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27334
|
-
if (
|
|
27555
|
+
if (fs14.existsSync(dashboardPath)) {
|
|
27335
27556
|
const dashboard = this.getDashboard(dashboardId);
|
|
27336
27557
|
if (dashboard) {
|
|
27337
27558
|
dashboards.push({ dashboardId, dashboard });
|
|
@@ -27353,13 +27574,13 @@ var DashboardManager = class {
|
|
|
27353
27574
|
*/
|
|
27354
27575
|
updateDashboard(dashboardId, dashboard) {
|
|
27355
27576
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27356
|
-
if (!
|
|
27577
|
+
if (!fs14.existsSync(dashboardPath)) {
|
|
27357
27578
|
logger.warn(`Dashboard not found for update: ${dashboardId}`);
|
|
27358
27579
|
return null;
|
|
27359
27580
|
}
|
|
27360
27581
|
try {
|
|
27361
27582
|
const validated = DSLRendererPropsSchema.parse(dashboard);
|
|
27362
|
-
|
|
27583
|
+
fs14.writeFileSync(dashboardPath, JSON.stringify(validated, null, 4));
|
|
27363
27584
|
logger.info(`Dashboard updated: ${dashboardId}`);
|
|
27364
27585
|
return validated;
|
|
27365
27586
|
} catch (error) {
|
|
@@ -27374,13 +27595,13 @@ var DashboardManager = class {
|
|
|
27374
27595
|
*/
|
|
27375
27596
|
deleteDashboard(dashboardId) {
|
|
27376
27597
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27377
|
-
const dashboardDir =
|
|
27378
|
-
if (!
|
|
27598
|
+
const dashboardDir = path16.dirname(dashboardPath);
|
|
27599
|
+
if (!fs14.existsSync(dashboardPath)) {
|
|
27379
27600
|
logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
|
|
27380
27601
|
return false;
|
|
27381
27602
|
}
|
|
27382
27603
|
try {
|
|
27383
|
-
|
|
27604
|
+
fs14.rmSync(dashboardDir, { recursive: true, force: true });
|
|
27384
27605
|
logger.info(`Dashboard deleted: ${dashboardId}`);
|
|
27385
27606
|
return true;
|
|
27386
27607
|
} catch (error) {
|
|
@@ -27395,21 +27616,21 @@ var DashboardManager = class {
|
|
|
27395
27616
|
*/
|
|
27396
27617
|
dashboardExists(dashboardId) {
|
|
27397
27618
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27398
|
-
return
|
|
27619
|
+
return fs14.existsSync(dashboardPath);
|
|
27399
27620
|
}
|
|
27400
27621
|
/**
|
|
27401
27622
|
* Get dashboard count
|
|
27402
27623
|
* @returns Number of dashboards
|
|
27403
27624
|
*/
|
|
27404
27625
|
getDashboardCount() {
|
|
27405
|
-
if (!
|
|
27626
|
+
if (!fs14.existsSync(this.dashboardsBasePath)) {
|
|
27406
27627
|
return 0;
|
|
27407
27628
|
}
|
|
27408
27629
|
try {
|
|
27409
|
-
const dashboardDirs =
|
|
27630
|
+
const dashboardDirs = fs14.readdirSync(this.dashboardsBasePath);
|
|
27410
27631
|
return dashboardDirs.filter((dir) => {
|
|
27411
27632
|
const dashboardPath = this.getDashboardPath(dir);
|
|
27412
|
-
return
|
|
27633
|
+
return fs14.existsSync(dashboardPath);
|
|
27413
27634
|
}).length;
|
|
27414
27635
|
} catch (error) {
|
|
27415
27636
|
logger.error("Failed to get dashboard count:", error);
|
|
@@ -27419,8 +27640,8 @@ var DashboardManager = class {
|
|
|
27419
27640
|
};
|
|
27420
27641
|
|
|
27421
27642
|
// src/reports/report-manager.ts
|
|
27422
|
-
import
|
|
27423
|
-
import
|
|
27643
|
+
import fs15 from "fs";
|
|
27644
|
+
import path17 from "path";
|
|
27424
27645
|
import os4 from "os";
|
|
27425
27646
|
var ReportManager = class {
|
|
27426
27647
|
/**
|
|
@@ -27429,7 +27650,7 @@ var ReportManager = class {
|
|
|
27429
27650
|
*/
|
|
27430
27651
|
constructor(projectId = "snowflake-dataset") {
|
|
27431
27652
|
this.projectId = projectId;
|
|
27432
|
-
this.reportsBasePath =
|
|
27653
|
+
this.reportsBasePath = path17.join(
|
|
27433
27654
|
os4.homedir(),
|
|
27434
27655
|
".superatom",
|
|
27435
27656
|
"projects",
|
|
@@ -27443,7 +27664,7 @@ var ReportManager = class {
|
|
|
27443
27664
|
* @returns Full path to report data.json file
|
|
27444
27665
|
*/
|
|
27445
27666
|
getReportPath(reportId) {
|
|
27446
|
-
return
|
|
27667
|
+
return path17.join(this.reportsBasePath, reportId, "data.json");
|
|
27447
27668
|
}
|
|
27448
27669
|
/**
|
|
27449
27670
|
* Create a new report
|
|
@@ -27453,13 +27674,13 @@ var ReportManager = class {
|
|
|
27453
27674
|
*/
|
|
27454
27675
|
createReport(reportId, report) {
|
|
27455
27676
|
const reportPath = this.getReportPath(reportId);
|
|
27456
|
-
const reportDir =
|
|
27457
|
-
if (
|
|
27677
|
+
const reportDir = path17.dirname(reportPath);
|
|
27678
|
+
if (fs15.existsSync(reportPath)) {
|
|
27458
27679
|
throw new Error(`Report '${reportId}' already exists`);
|
|
27459
27680
|
}
|
|
27460
27681
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
27461
|
-
|
|
27462
|
-
|
|
27682
|
+
fs15.mkdirSync(reportDir, { recursive: true });
|
|
27683
|
+
fs15.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
|
|
27463
27684
|
logger.info(`Report created: ${reportId}`);
|
|
27464
27685
|
return validated;
|
|
27465
27686
|
}
|
|
@@ -27470,12 +27691,12 @@ var ReportManager = class {
|
|
|
27470
27691
|
*/
|
|
27471
27692
|
getReport(reportId) {
|
|
27472
27693
|
const reportPath = this.getReportPath(reportId);
|
|
27473
|
-
if (!
|
|
27694
|
+
if (!fs15.existsSync(reportPath)) {
|
|
27474
27695
|
logger.warn(`Report not found: ${reportId}`);
|
|
27475
27696
|
return null;
|
|
27476
27697
|
}
|
|
27477
27698
|
try {
|
|
27478
|
-
const fileContent =
|
|
27699
|
+
const fileContent = fs15.readFileSync(reportPath, "utf-8");
|
|
27479
27700
|
const report = JSON.parse(fileContent);
|
|
27480
27701
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
27481
27702
|
return validated;
|
|
@@ -27489,16 +27710,16 @@ var ReportManager = class {
|
|
|
27489
27710
|
* @returns Array of report objects with their IDs
|
|
27490
27711
|
*/
|
|
27491
27712
|
getAllReports() {
|
|
27492
|
-
if (!
|
|
27493
|
-
|
|
27713
|
+
if (!fs15.existsSync(this.reportsBasePath)) {
|
|
27714
|
+
fs15.mkdirSync(this.reportsBasePath, { recursive: true });
|
|
27494
27715
|
return [];
|
|
27495
27716
|
}
|
|
27496
27717
|
const reports = [];
|
|
27497
27718
|
try {
|
|
27498
|
-
const reportDirs =
|
|
27719
|
+
const reportDirs = fs15.readdirSync(this.reportsBasePath);
|
|
27499
27720
|
for (const reportId of reportDirs) {
|
|
27500
27721
|
const reportPath = this.getReportPath(reportId);
|
|
27501
|
-
if (
|
|
27722
|
+
if (fs15.existsSync(reportPath)) {
|
|
27502
27723
|
const report = this.getReport(reportId);
|
|
27503
27724
|
if (report) {
|
|
27504
27725
|
reports.push({ reportId, report });
|
|
@@ -27520,13 +27741,13 @@ var ReportManager = class {
|
|
|
27520
27741
|
*/
|
|
27521
27742
|
updateReport(reportId, report) {
|
|
27522
27743
|
const reportPath = this.getReportPath(reportId);
|
|
27523
|
-
if (!
|
|
27744
|
+
if (!fs15.existsSync(reportPath)) {
|
|
27524
27745
|
logger.warn(`Report not found for update: ${reportId}`);
|
|
27525
27746
|
return null;
|
|
27526
27747
|
}
|
|
27527
27748
|
try {
|
|
27528
27749
|
const validated = DSLRendererPropsSchema2.parse(report);
|
|
27529
|
-
|
|
27750
|
+
fs15.writeFileSync(reportPath, JSON.stringify(validated, null, 4));
|
|
27530
27751
|
logger.info(`Report updated: ${reportId}`);
|
|
27531
27752
|
return validated;
|
|
27532
27753
|
} catch (error) {
|
|
@@ -27541,13 +27762,13 @@ var ReportManager = class {
|
|
|
27541
27762
|
*/
|
|
27542
27763
|
deleteReport(reportId) {
|
|
27543
27764
|
const reportPath = this.getReportPath(reportId);
|
|
27544
|
-
const reportDir =
|
|
27545
|
-
if (!
|
|
27765
|
+
const reportDir = path17.dirname(reportPath);
|
|
27766
|
+
if (!fs15.existsSync(reportPath)) {
|
|
27546
27767
|
logger.warn(`Report not found for deletion: ${reportId}`);
|
|
27547
27768
|
return false;
|
|
27548
27769
|
}
|
|
27549
27770
|
try {
|
|
27550
|
-
|
|
27771
|
+
fs15.rmSync(reportDir, { recursive: true, force: true });
|
|
27551
27772
|
logger.info(`Report deleted: ${reportId}`);
|
|
27552
27773
|
return true;
|
|
27553
27774
|
} catch (error) {
|
|
@@ -27562,21 +27783,21 @@ var ReportManager = class {
|
|
|
27562
27783
|
*/
|
|
27563
27784
|
reportExists(reportId) {
|
|
27564
27785
|
const reportPath = this.getReportPath(reportId);
|
|
27565
|
-
return
|
|
27786
|
+
return fs15.existsSync(reportPath);
|
|
27566
27787
|
}
|
|
27567
27788
|
/**
|
|
27568
27789
|
* Get report count
|
|
27569
27790
|
* @returns Number of reports
|
|
27570
27791
|
*/
|
|
27571
27792
|
getReportCount() {
|
|
27572
|
-
if (!
|
|
27793
|
+
if (!fs15.existsSync(this.reportsBasePath)) {
|
|
27573
27794
|
return 0;
|
|
27574
27795
|
}
|
|
27575
27796
|
try {
|
|
27576
|
-
const reportDirs =
|
|
27797
|
+
const reportDirs = fs15.readdirSync(this.reportsBasePath);
|
|
27577
27798
|
return reportDirs.filter((dir) => {
|
|
27578
27799
|
const reportPath = this.getReportPath(dir);
|
|
27579
|
-
return
|
|
27800
|
+
return fs15.existsSync(reportPath);
|
|
27580
27801
|
}).length;
|
|
27581
27802
|
} catch (error) {
|
|
27582
27803
|
logger.error("Failed to get report count:", error);
|
|
@@ -28070,8 +28291,8 @@ var CleanupService = class _CleanupService {
|
|
|
28070
28291
|
};
|
|
28071
28292
|
|
|
28072
28293
|
// src/dashboardAgent/collection.ts
|
|
28073
|
-
import
|
|
28074
|
-
import
|
|
28294
|
+
import path18 from "path";
|
|
28295
|
+
import fs16 from "fs";
|
|
28075
28296
|
|
|
28076
28297
|
// src/dashboardAgent/tools.ts
|
|
28077
28298
|
import { Type } from "typebox";
|
|
@@ -28283,12 +28504,16 @@ function loadPi2() {
|
|
|
28283
28504
|
if (!piModulePromise2) piModulePromise2 = dynamicImport3("@earendil-works/pi-coding-agent");
|
|
28284
28505
|
return piModulePromise2;
|
|
28285
28506
|
}
|
|
28286
|
-
var
|
|
28287
|
-
function
|
|
28288
|
-
if (!
|
|
28289
|
-
|
|
28290
|
-
const
|
|
28291
|
-
const modelRegistry =
|
|
28507
|
+
var authAndRegistry = null;
|
|
28508
|
+
function getAuthAndRegistry() {
|
|
28509
|
+
if (!authAndRegistry) {
|
|
28510
|
+
authAndRegistry = loadPi2().then(async ({ ModelRuntime, getAgentDir }) => {
|
|
28511
|
+
const agentDir = getAgentDir();
|
|
28512
|
+
const modelRegistry = await ModelRuntime.create({
|
|
28513
|
+
authPath: `${agentDir}/auth.json`,
|
|
28514
|
+
modelsStorePath: `${agentDir}/models-store.json`,
|
|
28515
|
+
allowModelNetwork: true
|
|
28516
|
+
});
|
|
28292
28517
|
const proxyRoot = process.env.SUPERATOM_LLM_PROXY_URL;
|
|
28293
28518
|
if (proxyRoot) {
|
|
28294
28519
|
modelRegistry.registerProvider("openrouter", {
|
|
@@ -28300,10 +28525,10 @@ function getAuthAndRegistry2() {
|
|
|
28300
28525
|
'[DashboardAgent] OPENROUTER_API_KEY is a proxy key (sk-prox-\u2026) but SUPERATOM_LLM_PROXY_URL is not set \u2014 requests would be sent directly to openrouter.ai, which will 401 ("Missing Authentication header"). Set SUPERATOM_LLM_PROXY_URL (e.g. https://ai.superatom.ai) in backend/.env.'
|
|
28301
28526
|
);
|
|
28302
28527
|
}
|
|
28303
|
-
return {
|
|
28528
|
+
return { modelRuntime: modelRegistry };
|
|
28304
28529
|
});
|
|
28305
28530
|
}
|
|
28306
|
-
return
|
|
28531
|
+
return authAndRegistry;
|
|
28307
28532
|
}
|
|
28308
28533
|
async function preResolveSchema(sdk, tool, intent, projectId) {
|
|
28309
28534
|
const tier = tool.schemaTier;
|
|
@@ -28399,16 +28624,16 @@ function textDelta(event) {
|
|
|
28399
28624
|
}
|
|
28400
28625
|
var runningDashboards = /* @__PURE__ */ new Set();
|
|
28401
28626
|
function registerDashboardAgentCollection(sdk, config2 = {}) {
|
|
28402
|
-
const piCwd = config2.cwd || process.env.PI_AGENT_DIR ||
|
|
28403
|
-
const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR ||
|
|
28627
|
+
const piCwd = config2.cwd || process.env.PI_AGENT_DIR || path18.join(process.cwd(), ".prompts", "dashboard-agent");
|
|
28628
|
+
const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR || path18.join(process.cwd(), ".pi-dashboard-agent-runtime");
|
|
28404
28629
|
const freshSession = config2.freshSession ?? process.env.PI_AGENT_FRESH_SESSION !== "false";
|
|
28405
|
-
const SESSIONS_FILE =
|
|
28406
|
-
const DRAFTS_DIR =
|
|
28407
|
-
const DASHBOARD_LOG_FILE =
|
|
28630
|
+
const SESSIONS_FILE = path18.join(runtimeDir, ".pi-dashboard-sessions.json");
|
|
28631
|
+
const DRAFTS_DIR = path18.join(runtimeDir, "drafts");
|
|
28632
|
+
const DASHBOARD_LOG_FILE = path18.join(runtimeDir, "dashboard.log");
|
|
28408
28633
|
function appendLog(text) {
|
|
28409
28634
|
try {
|
|
28410
|
-
|
|
28411
|
-
|
|
28635
|
+
fs16.mkdirSync(runtimeDir, { recursive: true });
|
|
28636
|
+
fs16.appendFileSync(DASHBOARD_LOG_FILE, text);
|
|
28412
28637
|
} catch (err) {
|
|
28413
28638
|
console.warn(`[DashboardAgent] Failed to write dashboard.log: ${err?.message ?? err}`);
|
|
28414
28639
|
}
|
|
@@ -28420,15 +28645,15 @@ ${fullPrompt}
|
|
|
28420
28645
|
--- Response ---
|
|
28421
28646
|
`;
|
|
28422
28647
|
try {
|
|
28423
|
-
|
|
28424
|
-
|
|
28648
|
+
fs16.mkdirSync(runtimeDir, { recursive: true });
|
|
28649
|
+
fs16.writeFileSync(DASHBOARD_LOG_FILE, header);
|
|
28425
28650
|
} catch (err) {
|
|
28426
28651
|
console.warn(`[DashboardAgent] Failed to write dashboard.log: ${err?.message ?? err}`);
|
|
28427
28652
|
}
|
|
28428
28653
|
}
|
|
28429
28654
|
function readDraft(dashboardId) {
|
|
28430
28655
|
try {
|
|
28431
|
-
const raw =
|
|
28656
|
+
const raw = fs16.readFileSync(path18.join(DRAFTS_DIR, `${dashboardId}.json`), "utf-8");
|
|
28432
28657
|
return JSON.parse(raw);
|
|
28433
28658
|
} catch (err) {
|
|
28434
28659
|
console.warn(`[DashboardAgent] Failed to read draft for ${dashboardId}: ${err?.message ?? err}`);
|
|
@@ -28437,7 +28662,7 @@ ${fullPrompt}
|
|
|
28437
28662
|
}
|
|
28438
28663
|
function loadSessionMap() {
|
|
28439
28664
|
try {
|
|
28440
|
-
return JSON.parse(
|
|
28665
|
+
return JSON.parse(fs16.readFileSync(SESSIONS_FILE, "utf-8"));
|
|
28441
28666
|
} catch {
|
|
28442
28667
|
return {};
|
|
28443
28668
|
}
|
|
@@ -28446,8 +28671,8 @@ ${fullPrompt}
|
|
|
28446
28671
|
const map = loadSessionMap();
|
|
28447
28672
|
map[dashboardId] = sessionFile;
|
|
28448
28673
|
try {
|
|
28449
|
-
|
|
28450
|
-
|
|
28674
|
+
fs16.mkdirSync(piCwd, { recursive: true });
|
|
28675
|
+
fs16.writeFileSync(SESSIONS_FILE, JSON.stringify(map, null, 2));
|
|
28451
28676
|
} catch (err) {
|
|
28452
28677
|
console.warn(`[DashboardAgent] Failed to persist session file for ${dashboardId}: ${err?.message ?? err}`);
|
|
28453
28678
|
}
|
|
@@ -28517,14 +28742,14 @@ ${fullPrompt}
|
|
|
28517
28742
|
}
|
|
28518
28743
|
try {
|
|
28519
28744
|
const { createAgentSession, SessionManager } = await loadPi2();
|
|
28520
|
-
const {
|
|
28745
|
+
const { modelRuntime: modelRuntime2 } = await getAuthAndRegistry();
|
|
28521
28746
|
let model;
|
|
28522
28747
|
const explicitProvider = config2.provider || process.env.PI_AGENT_PROVIDER;
|
|
28523
28748
|
const explicitModel = config2.model || process.env.PI_AGENT_MODEL;
|
|
28524
28749
|
if (explicitProvider || explicitModel) {
|
|
28525
28750
|
const providerName = explicitProvider || "openrouter";
|
|
28526
28751
|
const modelId = explicitModel || "anthropic/claude-sonnet-4.5";
|
|
28527
|
-
model =
|
|
28752
|
+
model = modelRuntime2.getModel(providerName, modelId);
|
|
28528
28753
|
if (!model) {
|
|
28529
28754
|
return { success: false, error: `Model not found: ${providerName}/${modelId}. Check the configured provider/model and that its API key is configured.` };
|
|
28530
28755
|
}
|
|
@@ -28537,8 +28762,8 @@ ${fullPrompt}
|
|
|
28537
28762
|
{ provider: "groq", modelId: "llama-3.3-70b-versatile" }
|
|
28538
28763
|
];
|
|
28539
28764
|
for (const candidate of FALLBACK_MODELS) {
|
|
28540
|
-
const found =
|
|
28541
|
-
if (found &&
|
|
28765
|
+
const found = modelRuntime2.getModel(candidate.provider, candidate.modelId);
|
|
28766
|
+
if (found && modelRuntime2.hasConfiguredAuth(found.provider)) {
|
|
28542
28767
|
model = found;
|
|
28543
28768
|
break;
|
|
28544
28769
|
}
|
|
@@ -28623,7 +28848,7 @@ ${turn.responseText ?? ""}`).join("\n\n");
|
|
|
28623
28848
|
console.warn(`[DashboardAgent] Failed to fetch previous responses for ${dashboardId}: ${err?.message ?? err}`);
|
|
28624
28849
|
}
|
|
28625
28850
|
}
|
|
28626
|
-
const draftPath =
|
|
28851
|
+
const draftPath = path18.join(DRAFTS_DIR, `${dashboardId}.json`);
|
|
28627
28852
|
const fullPrompt = [
|
|
28628
28853
|
`Dashboard ID: ${dashboardId}.`,
|
|
28629
28854
|
projectId ? `Project ID: ${projectId}.` : null,
|
|
@@ -28655,7 +28880,7 @@ ${previousResponseText}` : null,
|
|
|
28655
28880
|
let sessionManager;
|
|
28656
28881
|
if (!freshSession) {
|
|
28657
28882
|
const existingSessionFile = loadSessionMap()[dashboardId];
|
|
28658
|
-
if (existingSessionFile &&
|
|
28883
|
+
if (existingSessionFile && fs16.existsSync(existingSessionFile)) {
|
|
28659
28884
|
sessionManager = SessionManager.open(existingSessionFile, runtimeDir);
|
|
28660
28885
|
console.log(`[DashboardAgent] Resuming session file ${existingSessionFile} for dashboard ${dashboardId}`);
|
|
28661
28886
|
} else {
|
|
@@ -28672,8 +28897,7 @@ ${previousResponseText}` : null,
|
|
|
28672
28897
|
const created = await createAgentSession({
|
|
28673
28898
|
cwd: piCwd,
|
|
28674
28899
|
sessionManager,
|
|
28675
|
-
|
|
28676
|
-
modelRegistry,
|
|
28900
|
+
modelRuntime: modelRuntime2,
|
|
28677
28901
|
model,
|
|
28678
28902
|
customTools
|
|
28679
28903
|
});
|
|
@@ -28683,8 +28907,7 @@ ${previousResponseText}` : null,
|
|
|
28683
28907
|
const created = await createAgentSession({
|
|
28684
28908
|
cwd: piCwd,
|
|
28685
28909
|
sessionManager: SessionManager.create(piCwd, runtimeDir),
|
|
28686
|
-
|
|
28687
|
-
modelRegistry,
|
|
28910
|
+
modelRuntime: modelRuntime2,
|
|
28688
28911
|
model,
|
|
28689
28912
|
customTools
|
|
28690
28913
|
});
|
|
@@ -28736,7 +28959,7 @@ ${description}
|
|
|
28736
28959
|
};
|
|
28737
28960
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
28738
28961
|
try {
|
|
28739
|
-
|
|
28962
|
+
fs16.unlinkSync(draftPath);
|
|
28740
28963
|
} catch {
|
|
28741
28964
|
}
|
|
28742
28965
|
let result;
|
|
@@ -28756,7 +28979,7 @@ ${description}
|
|
|
28756
28979
|
signal?.removeEventListener("abort", onAbort);
|
|
28757
28980
|
if (freshSession && session.sessionFile) {
|
|
28758
28981
|
try {
|
|
28759
|
-
|
|
28982
|
+
fs16.unlinkSync(session.sessionFile);
|
|
28760
28983
|
} catch (err) {
|
|
28761
28984
|
console.warn(`[DashboardAgent] Failed to delete fresh-session file ${session.sessionFile}: ${err?.message ?? err}`);
|
|
28762
28985
|
}
|
|
@@ -28956,6 +29179,9 @@ var SuperatomSDK = class {
|
|
|
28956
29179
|
if (config2.queryCacheTTL !== void 0) {
|
|
28957
29180
|
queryCache.setTTL(config2.queryCacheTTL);
|
|
28958
29181
|
}
|
|
29182
|
+
if (config2.queryResultCache === false) {
|
|
29183
|
+
queryCache.disableResultCache();
|
|
29184
|
+
}
|
|
28959
29185
|
if (config2.dashboardHistoryTTL !== void 0) {
|
|
28960
29186
|
dashboardConversationHistory.setTTL(config2.dashboardHistoryTTL);
|
|
28961
29187
|
}
|
|
@@ -29040,7 +29266,7 @@ var SuperatomSDK = class {
|
|
|
29040
29266
|
logger.info("Already connected to WebSocket");
|
|
29041
29267
|
return Promise.resolve();
|
|
29042
29268
|
}
|
|
29043
|
-
return new Promise((
|
|
29269
|
+
return new Promise((resolve4, reject) => {
|
|
29044
29270
|
try {
|
|
29045
29271
|
const url = new URL(this.url);
|
|
29046
29272
|
if (this.apiKey) {
|
|
@@ -29056,7 +29282,7 @@ var SuperatomSDK = class {
|
|
|
29056
29282
|
logger.info("WebSocket connected successfully");
|
|
29057
29283
|
this.startHeartbeat();
|
|
29058
29284
|
this.flushPendingOutbox();
|
|
29059
|
-
|
|
29285
|
+
resolve4();
|
|
29060
29286
|
});
|
|
29061
29287
|
this.ws.addEventListener("message", (event) => {
|
|
29062
29288
|
this.handleMessage(event.data);
|