@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.js
CHANGED
|
@@ -395,7 +395,13 @@ var UserPromptRequestPayloadSchema = import_zod3.z.object({
|
|
|
395
395
|
threadId: import_zod3.z.string(),
|
|
396
396
|
uiBlockId: import_zod3.z.string()
|
|
397
397
|
}).optional(),
|
|
398
|
-
responseMode: import_zod3.z.enum(["component", "text"]).optional()
|
|
398
|
+
responseMode: import_zod3.z.enum(["component", "text"]).optional(),
|
|
399
|
+
/**
|
|
400
|
+
* Where the question was asked from — e.g. the dashboard being viewed. See
|
|
401
|
+
* `PromptContext`. Accepted as anything so a client sending an unexpected shape
|
|
402
|
+
* is never rejected; the handler keeps only the fields it recognises.
|
|
403
|
+
*/
|
|
404
|
+
context: import_zod3.z.unknown().optional()
|
|
399
405
|
});
|
|
400
406
|
var MessageAuthContextSchema = import_zod3.z.object({
|
|
401
407
|
userId: import_zod3.z.string(),
|
|
@@ -1102,9 +1108,25 @@ var SchemaResponseMessageSchema = import_zod3.z.object({
|
|
|
1102
1108
|
|
|
1103
1109
|
// src/utils/logger.ts
|
|
1104
1110
|
var import_fs = __toESM(require("fs"));
|
|
1111
|
+
var import_path = __toESM(require("path"));
|
|
1105
1112
|
var PREFIX = "[SuperatomSDK]";
|
|
1106
|
-
var
|
|
1107
|
-
var
|
|
1113
|
+
var LEGACY_LOG_FILE_PATH = "superatom-sdk.log";
|
|
1114
|
+
var LOG_DIR = "logs";
|
|
1115
|
+
function slugifyLabel(label) {
|
|
1116
|
+
return label.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 60);
|
|
1117
|
+
}
|
|
1118
|
+
function buildLogFilePath(label) {
|
|
1119
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1120
|
+
const slug2 = label ? slugifyLabel(label) : "";
|
|
1121
|
+
const filename = slug2 ? `sdk-${stamp}-${slug2}.log` : `sdk-${stamp}.log`;
|
|
1122
|
+
return import_path.default.join(LOG_DIR, filename);
|
|
1123
|
+
}
|
|
1124
|
+
try {
|
|
1125
|
+
import_fs.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
var currentLogFilePath = LEGACY_LOG_FILE_PATH;
|
|
1129
|
+
var LOGSTREAM = import_fs.default.createWriteStream(LEGACY_LOG_FILE_PATH, { flags: "a" });
|
|
1108
1130
|
var LOG_LEVEL_PRIORITY = {
|
|
1109
1131
|
errors: 0,
|
|
1110
1132
|
warnings: 1,
|
|
@@ -1189,17 +1211,31 @@ var Logger = class {
|
|
|
1189
1211
|
}
|
|
1190
1212
|
}
|
|
1191
1213
|
/**
|
|
1192
|
-
* Write to log file
|
|
1214
|
+
* Write to log file — each line timestamped so a request that made many
|
|
1215
|
+
* calls (LLM turns, tool dispatches, direct-tool runs) can be read back in
|
|
1216
|
+
* order, matching "know what happened when" rather than just "what
|
|
1217
|
+
* happened".
|
|
1193
1218
|
*/
|
|
1194
1219
|
file(...args) {
|
|
1195
|
-
LOGSTREAM.write(args.join(" ")
|
|
1220
|
+
LOGSTREAM.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${args.join(" ")}
|
|
1221
|
+
`);
|
|
1222
|
+
}
|
|
1223
|
+
/** The log file the current request is writing to — print/return this so it's easy to find afterward. */
|
|
1224
|
+
getCurrentLogFilePath() {
|
|
1225
|
+
return currentLogFilePath;
|
|
1196
1226
|
}
|
|
1197
1227
|
/**
|
|
1198
|
-
*
|
|
1228
|
+
* Start a fresh, uniquely-named log file for a new top-level request
|
|
1229
|
+
* (chat turn, dashComp request, reportComp request, ...) instead of
|
|
1230
|
+
* truncating one shared file — pass a short label (a prompt snippet, a
|
|
1231
|
+
* request type) so the resulting filename is identifiable at a glance.
|
|
1232
|
+
* Falls back to the untimestamped legacy path when `label` is omitted, for
|
|
1233
|
+
* any caller not yet updated to pass one.
|
|
1199
1234
|
*/
|
|
1200
|
-
clearFile() {
|
|
1235
|
+
clearFile(label) {
|
|
1201
1236
|
LOGSTREAM.end();
|
|
1202
|
-
|
|
1237
|
+
currentLogFilePath = label ? buildLogFilePath(label) : LEGACY_LOG_FILE_PATH;
|
|
1238
|
+
LOGSTREAM = import_fs.default.createWriteStream(currentLogFilePath, { flags: "w" });
|
|
1203
1239
|
LOGSTREAM.write(`
|
|
1204
1240
|
${"=".repeat(80)}
|
|
1205
1241
|
`);
|
|
@@ -1208,6 +1244,7 @@ ${"=".repeat(80)}
|
|
|
1208
1244
|
LOGSTREAM.write(`${"=".repeat(80)}
|
|
1209
1245
|
|
|
1210
1246
|
`);
|
|
1247
|
+
this.info(`Logging this request to ${currentLogFilePath}`);
|
|
1211
1248
|
}
|
|
1212
1249
|
/**
|
|
1213
1250
|
* Log LLM method prompts with clear labeling
|
|
@@ -1215,7 +1252,7 @@ ${"=".repeat(80)}
|
|
|
1215
1252
|
logLLMPrompt(methodName, promptType, content) {
|
|
1216
1253
|
const header = `
|
|
1217
1254
|
${"#".repeat(80)}
|
|
1218
|
-
[LLM METHOD: ${methodName}] - ${promptType.toUpperCase()} PROMPT
|
|
1255
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] [LLM METHOD: ${methodName}] - ${promptType.toUpperCase()} PROMPT
|
|
1219
1256
|
${"#".repeat(80)}
|
|
1220
1257
|
`;
|
|
1221
1258
|
LOGSTREAM.write(header);
|
|
@@ -1940,6 +1977,7 @@ var QueryCache = class {
|
|
|
1940
1977
|
this.maxCacheSize = 5e3;
|
|
1941
1978
|
// Max data cache entries
|
|
1942
1979
|
this.cleanupInterval = null;
|
|
1980
|
+
this.resultCacheEnabled = true;
|
|
1943
1981
|
// Encryption for queryId tokens
|
|
1944
1982
|
this.algorithm = "aes-256-gcm";
|
|
1945
1983
|
const keySource = process.env.QUERY_TOKEN_SECRET || "superatom-query-cache-default-key-v1";
|
|
@@ -1963,6 +2001,15 @@ var QueryCache = class {
|
|
|
1963
2001
|
getTTL() {
|
|
1964
2002
|
return this.ttlMs / 60 / 1e3;
|
|
1965
2003
|
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Stop keeping result data in memory, for a host that caches results itself.
|
|
2006
|
+
* `set` stores nothing and `get` finds nothing; queryId tokens are unaffected.
|
|
2007
|
+
*/
|
|
2008
|
+
disableResultCache() {
|
|
2009
|
+
this.resultCacheEnabled = false;
|
|
2010
|
+
this.cache.clear();
|
|
2011
|
+
logger.info("[QueryCache] Result cache disabled \u2014 the host caches query results");
|
|
2012
|
+
}
|
|
1966
2013
|
/**
|
|
1967
2014
|
* Which access bucket this call reads and writes.
|
|
1968
2015
|
*
|
|
@@ -1995,6 +2042,7 @@ var QueryCache = class {
|
|
|
1995
2042
|
* a genuinely new key past the size limit.
|
|
1996
2043
|
*/
|
|
1997
2044
|
set(query, data) {
|
|
2045
|
+
if (!this.resultCacheEnabled) return;
|
|
1998
2046
|
const key = this.scopedKey(query);
|
|
1999
2047
|
if (key === null) return;
|
|
2000
2048
|
this.cache.delete(key);
|
|
@@ -2015,6 +2063,7 @@ var QueryCache = class {
|
|
|
2015
2063
|
* iteration order — turning FIFO eviction into true LRU.
|
|
2016
2064
|
*/
|
|
2017
2065
|
get(query) {
|
|
2066
|
+
if (!this.resultCacheEnabled) return null;
|
|
2018
2067
|
const key = this.scopedKey(query);
|
|
2019
2068
|
if (key === null) return null;
|
|
2020
2069
|
const entry = this.cache.get(key);
|
|
@@ -2793,7 +2842,7 @@ function sendDataResponse(id, collection, op, data, meta, sendMessage) {
|
|
|
2793
2842
|
|
|
2794
2843
|
// src/bundle.ts
|
|
2795
2844
|
var fs2 = __toESM(require("fs"));
|
|
2796
|
-
var
|
|
2845
|
+
var path2 = __toESM(require("path"));
|
|
2797
2846
|
function getBundleDir(configDir) {
|
|
2798
2847
|
const bundleDir = configDir || process.env.SA_BUNDLE_DIR;
|
|
2799
2848
|
if (!bundleDir) {
|
|
@@ -2826,7 +2875,7 @@ function getJS(bundleDir) {
|
|
|
2826
2875
|
`Could not find index-*.js file in ${bundleDir}. Expected a file matching pattern: index-*.js`
|
|
2827
2876
|
);
|
|
2828
2877
|
}
|
|
2829
|
-
const filePath =
|
|
2878
|
+
const filePath = path2.join(bundleDir, indexFile);
|
|
2830
2879
|
logger.info(`Loading bundle from ${filePath}`);
|
|
2831
2880
|
try {
|
|
2832
2881
|
return fs2.readFileSync(filePath, "utf8");
|
|
@@ -3265,12 +3314,12 @@ function sendDataResponse3(id, res, sendMessage, clientId) {
|
|
|
3265
3314
|
|
|
3266
3315
|
// src/utils/user-prompt-error-logger.ts
|
|
3267
3316
|
var import_fs2 = __toESM(require("fs"));
|
|
3268
|
-
var
|
|
3317
|
+
var import_path2 = __toESM(require("path"));
|
|
3269
3318
|
var UserPromptErrorLogger = class {
|
|
3270
3319
|
constructor() {
|
|
3271
3320
|
this.logStream = null;
|
|
3272
3321
|
this.hasErrors = false;
|
|
3273
|
-
this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH ||
|
|
3322
|
+
this.logPath = process.env.USER_PROMPT_ERROR_LOG_PATH || import_path2.default.join(process.cwd(), "user-prompt-req-errors");
|
|
3274
3323
|
this.enabled = process.env.USER_PROMPT_ERROR_LOGGING !== "false";
|
|
3275
3324
|
}
|
|
3276
3325
|
/**
|
|
@@ -3283,7 +3332,7 @@ var UserPromptErrorLogger = class {
|
|
|
3283
3332
|
this.logStream.end();
|
|
3284
3333
|
this.logStream = null;
|
|
3285
3334
|
}
|
|
3286
|
-
const dir =
|
|
3335
|
+
const dir = import_path2.default.dirname(this.logPath);
|
|
3287
3336
|
if (dir !== "." && !import_fs2.default.existsSync(dir)) {
|
|
3288
3337
|
import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
3289
3338
|
}
|
|
@@ -3763,7 +3812,8 @@ var findExactMatch = async ({
|
|
|
3763
3812
|
userPrompt,
|
|
3764
3813
|
collections,
|
|
3765
3814
|
userId,
|
|
3766
|
-
maxAgeHours
|
|
3815
|
+
maxAgeHours,
|
|
3816
|
+
statuses
|
|
3767
3817
|
}) => {
|
|
3768
3818
|
try {
|
|
3769
3819
|
if (!collections || !collections["conversation-history"] || !collections["conversation-history"]["exactMatch"]) {
|
|
@@ -3773,7 +3823,8 @@ var findExactMatch = async ({
|
|
|
3773
3823
|
const result = await collections["conversation-history"]["exactMatch"]({
|
|
3774
3824
|
userPrompt,
|
|
3775
3825
|
userId,
|
|
3776
|
-
maxAgeHours
|
|
3826
|
+
maxAgeHours,
|
|
3827
|
+
statuses
|
|
3777
3828
|
});
|
|
3778
3829
|
if (!result || !result.uiBlock) {
|
|
3779
3830
|
logger.info("[ConversationSearch] No exact match found");
|
|
@@ -3936,7 +3987,7 @@ var TaggedStreamBuffer = class extends StreamBuffer {
|
|
|
3936
3987
|
}
|
|
3937
3988
|
};
|
|
3938
3989
|
function streamDelay(ms = STREAM_DELAY_MS) {
|
|
3939
|
-
return new Promise((
|
|
3990
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3940
3991
|
}
|
|
3941
3992
|
async function withProgressHeartbeat(operation, progressMessage, streamBuffer, intervalMs = PROGRESS_HEARTBEAT_INTERVAL_MS) {
|
|
3942
3993
|
if (!streamBuffer.hasCallback()) {
|
|
@@ -4129,7 +4180,7 @@ var import_jsonrepair = require("jsonrepair");
|
|
|
4129
4180
|
|
|
4130
4181
|
// src/utils/llm-usage-logger.ts
|
|
4131
4182
|
var import_fs3 = __toESM(require("fs"));
|
|
4132
|
-
var
|
|
4183
|
+
var import_path3 = __toESM(require("path"));
|
|
4133
4184
|
var PRICING = {
|
|
4134
4185
|
// Anthropic (December 2025)
|
|
4135
4186
|
"claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
@@ -4193,7 +4244,7 @@ var LLMUsageLogger = class {
|
|
|
4193
4244
|
totalCostUSD: 0,
|
|
4194
4245
|
totalDurationMs: 0
|
|
4195
4246
|
};
|
|
4196
|
-
this.logPath = process.env.LLM_USAGE_LOG_PATH ||
|
|
4247
|
+
this.logPath = process.env.LLM_USAGE_LOG_PATH || import_path3.default.join(process.cwd(), "llm-usage-logs.log");
|
|
4197
4248
|
this.enabled = process.env.LLM_USAGE_LOGGING !== "false";
|
|
4198
4249
|
if (this.enabled) {
|
|
4199
4250
|
this.initLogStream();
|
|
@@ -4201,7 +4252,7 @@ var LLMUsageLogger = class {
|
|
|
4201
4252
|
}
|
|
4202
4253
|
initLogStream() {
|
|
4203
4254
|
try {
|
|
4204
|
-
const dir =
|
|
4255
|
+
const dir = import_path3.default.dirname(this.logPath);
|
|
4205
4256
|
if (!import_fs3.default.existsSync(dir)) {
|
|
4206
4257
|
import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
4207
4258
|
}
|
|
@@ -4867,7 +4918,7 @@ var LLM = class {
|
|
|
4867
4918
|
iterations++;
|
|
4868
4919
|
const iterationStartTime = Date.now();
|
|
4869
4920
|
const requestId = llmUsageLogger.generateRequestId();
|
|
4870
|
-
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? options.firstIterationToolChoice : void 0;
|
|
4921
|
+
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? options.firstIterationToolChoice : iterations === 2 && options.secondIterationToolChoice ? options.secondIterationToolChoice : void 0;
|
|
4871
4922
|
let stream;
|
|
4872
4923
|
try {
|
|
4873
4924
|
stream = await client.messages.create({
|
|
@@ -5825,7 +5876,7 @@ var LLM = class {
|
|
|
5825
5876
|
const iterStart = Date.now();
|
|
5826
5877
|
const requestId = llmUsageLogger.generateRequestId();
|
|
5827
5878
|
let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0;
|
|
5828
|
-
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? this._openaiToolChoice(options.firstIterationToolChoice) : void 0;
|
|
5879
|
+
const toolChoice = iterations === 1 && options.firstIterationToolChoice ? this._openaiToolChoice(options.firstIterationToolChoice) : iterations === 2 && options.secondIterationToolChoice ? this._openaiToolChoice(options.secondIterationToolChoice) : void 0;
|
|
5829
5880
|
const createParams = {
|
|
5830
5881
|
model: modelName,
|
|
5831
5882
|
messages: conversationMessages,
|
|
@@ -6016,7 +6067,7 @@ var LLM = class {
|
|
|
6016
6067
|
|
|
6017
6068
|
// src/userResponse/prompt-loader.ts
|
|
6018
6069
|
var import_fs4 = __toESM(require("fs"));
|
|
6019
|
-
var
|
|
6070
|
+
var import_path4 = __toESM(require("path"));
|
|
6020
6071
|
|
|
6021
6072
|
// src/userResponse/prompts.ts
|
|
6022
6073
|
var PROMPTS = {
|
|
@@ -9430,7 +9481,7 @@ var PromptLoader = class {
|
|
|
9430
9481
|
this.databaseRulesCache = /* @__PURE__ */ new Map();
|
|
9431
9482
|
this.isInitialized = false;
|
|
9432
9483
|
this.databaseType = "postgresql";
|
|
9433
|
-
this.promptsDir = config2?.promptsDir ||
|
|
9484
|
+
this.promptsDir = config2?.promptsDir || import_path4.default.join(process.cwd(), ".prompts");
|
|
9434
9485
|
}
|
|
9435
9486
|
/**
|
|
9436
9487
|
* Load a prompt template from file system OR fallback to hardcoded prompts
|
|
@@ -9439,8 +9490,8 @@ var PromptLoader = class {
|
|
|
9439
9490
|
*/
|
|
9440
9491
|
loadPromptTemplate(promptName) {
|
|
9441
9492
|
try {
|
|
9442
|
-
const systemPath =
|
|
9443
|
-
const userPath =
|
|
9493
|
+
const systemPath = import_path4.default.join(this.promptsDir, promptName, "system.md");
|
|
9494
|
+
const userPath = import_path4.default.join(this.promptsDir, promptName, "user.md");
|
|
9444
9495
|
if (import_fs4.default.existsSync(systemPath) && import_fs4.default.existsSync(userPath)) {
|
|
9445
9496
|
const system = import_fs4.default.readFileSync(systemPath, "utf-8");
|
|
9446
9497
|
const user = import_fs4.default.readFileSync(userPath, "utf-8");
|
|
@@ -9590,7 +9641,7 @@ var PromptLoader = class {
|
|
|
9590
9641
|
if (this.databaseRulesCache.has(this.databaseType)) {
|
|
9591
9642
|
return this.databaseRulesCache.get(this.databaseType);
|
|
9592
9643
|
}
|
|
9593
|
-
const rulesPath =
|
|
9644
|
+
const rulesPath = import_path4.default.join(this.promptsDir, "database-rules", `${this.databaseType}.md`);
|
|
9594
9645
|
try {
|
|
9595
9646
|
if (import_fs4.default.existsSync(rulesPath)) {
|
|
9596
9647
|
const rules2 = import_fs4.default.readFileSync(rulesPath, "utf-8");
|
|
@@ -9616,6 +9667,8 @@ var PromptLoader = class {
|
|
|
9616
9667
|
"postgres": "postgresql",
|
|
9617
9668
|
"postgresql": "postgresql",
|
|
9618
9669
|
"mssql": "mssql",
|
|
9670
|
+
"fabric": "mssql",
|
|
9671
|
+
// Microsoft Fabric SQL endpoints speak T-SQL
|
|
9619
9672
|
"mysql": "postgresql",
|
|
9620
9673
|
// MySQL uses similar rules to PostgreSQL
|
|
9621
9674
|
"excel": "duckdb",
|
|
@@ -9633,7 +9686,7 @@ var PromptLoader = class {
|
|
|
9633
9686
|
if (this.databaseRulesCache.has(dbType)) {
|
|
9634
9687
|
return this.databaseRulesCache.get(dbType);
|
|
9635
9688
|
}
|
|
9636
|
-
const rulesPath =
|
|
9689
|
+
const rulesPath = import_path4.default.join(this.promptsDir, "database-rules", `${dbType}.md`);
|
|
9637
9690
|
try {
|
|
9638
9691
|
if (import_fs4.default.existsSync(rulesPath)) {
|
|
9639
9692
|
const rules2 = import_fs4.default.readFileSync(rulesPath, "utf-8");
|
|
@@ -9663,7 +9716,7 @@ var PromptLoader = class {
|
|
|
9663
9716
|
const cacheKey = "__script_body_contract__";
|
|
9664
9717
|
let raw = this.databaseRulesCache.get(cacheKey);
|
|
9665
9718
|
if (raw === void 0) {
|
|
9666
|
-
const contractPath =
|
|
9719
|
+
const contractPath = import_path4.default.join(this.promptsDir, "script-rules", "body-contract.md");
|
|
9667
9720
|
try {
|
|
9668
9721
|
if (import_fs4.default.existsSync(contractPath)) {
|
|
9669
9722
|
raw = import_fs4.default.readFileSync(contractPath, "utf-8");
|
|
@@ -9695,7 +9748,7 @@ var PromptLoader = class {
|
|
|
9695
9748
|
return DATABASE_RULES[dbType] ?? DATABASE_RULES["postgresql"];
|
|
9696
9749
|
}
|
|
9697
9750
|
};
|
|
9698
|
-
var defaultPromptsPath = process.env.PROMPTS_DIR ||
|
|
9751
|
+
var defaultPromptsPath = process.env.PROMPTS_DIR || import_path4.default.join(process.cwd(), ".prompts");
|
|
9699
9752
|
var promptLoader = new PromptLoader({
|
|
9700
9753
|
promptsDir: defaultPromptsPath
|
|
9701
9754
|
});
|
|
@@ -10124,7 +10177,7 @@ function getDatabaseType() {
|
|
|
10124
10177
|
function getDbTypeFromToolId(toolId) {
|
|
10125
10178
|
if (!toolId) return getDatabaseType();
|
|
10126
10179
|
const prefix = toolId.split("-")[0]?.toLowerCase();
|
|
10127
|
-
if (prefix.startsWith("mssql")) return "mssql";
|
|
10180
|
+
if (prefix.startsWith("mssql") || prefix === "fabric") return "mssql";
|
|
10128
10181
|
if (prefix.startsWith("postgres")) return "postgresql";
|
|
10129
10182
|
if (prefix.startsWith("mysql")) return "mysql";
|
|
10130
10183
|
if (prefix.startsWith("snowflake")) return "snowflake";
|
|
@@ -10445,6 +10498,7 @@ var SourceAgent = class {
|
|
|
10445
10498
|
if (toolName.endsWith("_search_schema") && schemaSearchFn) {
|
|
10446
10499
|
const keywords = toolInput.keywords || [];
|
|
10447
10500
|
logger.info(`[SourceAgent:${this.tool.name}] Schema search: ${keywords.join(", ")}`);
|
|
10501
|
+
logger.file(`[SourceAgent:${this.tool.name}] SCHEMA SEARCH:`, keywords.join(", "));
|
|
10448
10502
|
if (this.streamBuffer.hasCallback()) {
|
|
10449
10503
|
this.streamBuffer.write(`\u{1F50D} **Looking up tables:** ${keywords.join(", ")}
|
|
10450
10504
|
|
|
@@ -10457,6 +10511,7 @@ var SourceAgent = class {
|
|
|
10457
10511
|
} catch (traceErr) {
|
|
10458
10512
|
logger.warn(`[SourceAgent:${this.tool.name}] Trace capture failed for search_schema lookup (non-blocking): ${traceErr}`);
|
|
10459
10513
|
}
|
|
10514
|
+
logger.file(`[SourceAgent:${this.tool.name}] SCHEMA SEARCH RESULT:`, result);
|
|
10460
10515
|
return `Schema search results:
|
|
10461
10516
|
|
|
10462
10517
|
${result}`;
|
|
@@ -10500,6 +10555,7 @@ ${result}`;
|
|
|
10500
10555
|
cappedInput.query = ensureQueryLimit(cappedInput.query, this.config.maxRowsFetched, this.config.maxRowsFetched, _st);
|
|
10501
10556
|
}
|
|
10502
10557
|
queryExecuted = cappedInput.sql || cappedInput.query || JSON.stringify(cappedInput);
|
|
10558
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY (attempt ${this.attempts}):`, queryExecuted);
|
|
10503
10559
|
if (this.streamBuffer.hasCallback() && queryExecuted) {
|
|
10504
10560
|
const queryDisplay = cappedInput.sql || cappedInput.query;
|
|
10505
10561
|
if (queryDisplay) {
|
|
@@ -10528,6 +10584,7 @@ ${queryDisplay}
|
|
|
10528
10584
|
if (result && result.error) {
|
|
10529
10585
|
const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
|
|
10530
10586
|
logger.warn(`[SourceAgent:${this.tool.name}] Tool returned error (attempt ${this.attempts}/${this.config.maxRetries}): ${errorMsg}`);
|
|
10587
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY ERROR (attempt ${this.attempts}):`, errorMsg);
|
|
10531
10588
|
if (this.streamBuffer.hasCallback()) {
|
|
10532
10589
|
this.streamBuffer.write(`\u274C **Query failed:** ${errorMsg}
|
|
10533
10590
|
|
|
@@ -10541,6 +10598,7 @@ Analyze the error and try again with a corrected query.`;
|
|
|
10541
10598
|
totalRowsMatched = result.metadata?.totalCount || result.count || resultData.length;
|
|
10542
10599
|
querySucceeded = true;
|
|
10543
10600
|
successfulQueries++;
|
|
10601
|
+
logger.file(`[SourceAgent:${this.tool.name}] QUERY RESULT (attempt ${this.attempts}):`, `${resultData.length} rows (${totalRowsMatched} total)`, "metadata:", JSON.stringify(result.metadata ?? {}));
|
|
10544
10602
|
if (this.streamBuffer.hasCallback()) {
|
|
10545
10603
|
const totalInfo = totalRowsMatched > resultData.length ? ` of ${totalRowsMatched} total` : "";
|
|
10546
10604
|
this.streamBuffer.write(`\u2705 **${resultData.length} rows${totalInfo} from ${this.tool.name}**
|
|
@@ -10948,7 +11006,7 @@ Even if a table appears in the detailed schema above, search_schema returns samp
|
|
|
10948
11006
|
const match = this.tool.id.match(/^(\w+)-[a-f0-9]+_/);
|
|
10949
11007
|
if (!match) return "unknown";
|
|
10950
11008
|
const raw = match[1].toLowerCase();
|
|
10951
|
-
if (raw.startsWith("mssql")) return "mssql";
|
|
11009
|
+
if (raw.startsWith("mssql") || raw === "fabric") return "mssql";
|
|
10952
11010
|
if (raw.startsWith("postgres")) return "postgres";
|
|
10953
11011
|
if (raw.startsWith("mysql")) return "mysql";
|
|
10954
11012
|
if (raw.startsWith("snowflake")) return "snowflake";
|
|
@@ -10974,7 +11032,7 @@ Even if a table appears in the detailed schema above, search_schema returns samp
|
|
|
10974
11032
|
|
|
10975
11033
|
// src/userResponse/scripts/script-runner.ts
|
|
10976
11034
|
var import_child_process = require("child_process");
|
|
10977
|
-
var
|
|
11035
|
+
var path6 = __toESM(require("path"));
|
|
10978
11036
|
var os = __toESM(require("os"));
|
|
10979
11037
|
|
|
10980
11038
|
// src/userResponse/scripts/script-ipc.ts
|
|
@@ -11117,7 +11175,7 @@ var Semaphore = class {
|
|
|
11117
11175
|
`Script execution at capacity (${this.max} running, ${this.waiters.length} queued). Try again shortly.`
|
|
11118
11176
|
);
|
|
11119
11177
|
}
|
|
11120
|
-
return new Promise((
|
|
11178
|
+
return new Promise((resolve4) => this.waiters.push(resolve4));
|
|
11121
11179
|
}
|
|
11122
11180
|
release() {
|
|
11123
11181
|
const next = this.waiters.shift();
|
|
@@ -11135,8 +11193,8 @@ function resolveTsxBinary() {
|
|
|
11135
11193
|
if (tsxBinaryPath) return tsxBinaryPath;
|
|
11136
11194
|
try {
|
|
11137
11195
|
const tsxPkg = require.resolve("tsx/package.json");
|
|
11138
|
-
const tsxDir =
|
|
11139
|
-
tsxBinaryPath =
|
|
11196
|
+
const tsxDir = path6.dirname(tsxPkg);
|
|
11197
|
+
tsxBinaryPath = path6.join(tsxDir, "dist", "cli.mjs");
|
|
11140
11198
|
return tsxBinaryPath;
|
|
11141
11199
|
} catch (err) {
|
|
11142
11200
|
throw new Error(
|
|
@@ -11145,7 +11203,7 @@ function resolveTsxBinary() {
|
|
|
11145
11203
|
}
|
|
11146
11204
|
}
|
|
11147
11205
|
function resolveBootstrapPath() {
|
|
11148
|
-
return
|
|
11206
|
+
return path6.resolve(__dirname, "userResponse/scripts/script-bootstrap.js");
|
|
11149
11207
|
}
|
|
11150
11208
|
async function runScript(recipe, scriptPath, params, options) {
|
|
11151
11209
|
const startedAt = Date.now();
|
|
@@ -11247,7 +11305,7 @@ FIX: pass every required parameter to execute_script, or give it a default in wr
|
|
|
11247
11305
|
}
|
|
11248
11306
|
}
|
|
11249
11307
|
function executeInSubprocess(scriptPath, params, paramSchema, externalTools, streamBuffer, timeoutMs, signal, mode, analysisData, analysisDatasets, entities, appliedPolicies) {
|
|
11250
|
-
return new Promise((
|
|
11308
|
+
return new Promise((resolve4) => {
|
|
11251
11309
|
const tsxBin = resolveTsxBinary();
|
|
11252
11310
|
const bootstrap = resolveBootstrapPath();
|
|
11253
11311
|
const contextToken = mintContextToken();
|
|
@@ -11303,7 +11361,7 @@ function executeInSubprocess(scriptPath, params, paramSchema, externalTools, str
|
|
|
11303
11361
|
clearTimeout(timer);
|
|
11304
11362
|
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
11305
11363
|
cleanup();
|
|
11306
|
-
|
|
11364
|
+
resolve4(r);
|
|
11307
11365
|
};
|
|
11308
11366
|
const timer = setTimeout(() => {
|
|
11309
11367
|
finish({
|
|
@@ -11653,6 +11711,7 @@ function codeOnly(source) {
|
|
|
11653
11711
|
var BARE_CONST_RE = /\bconst\s+(\w+)\s*=\s*(\d{2,}(?:\.\d+)?)\s*[;,)]/g;
|
|
11654
11712
|
var FMT_CALL_RE = /\bfmt\.\w+\(([^)]*)\)/g;
|
|
11655
11713
|
var DIRECT_BARE_ARG_RE = /(?<![\w.\[])(\d{2,}(?:\.\d+)?)(?![\w.\]])/;
|
|
11714
|
+
var OBJECT_PROP_RE = /(?<![\w.])([A-Za-z_]\w*)\s*:\s*(\d{2,}(?:\.\d+)?)\s*[,}]/g;
|
|
11656
11715
|
function interpolationContents(source) {
|
|
11657
11716
|
const out = [];
|
|
11658
11717
|
for (const tpl of source.matchAll(/`(?:\\.|[^`\\])*`/g)) {
|
|
@@ -11684,6 +11743,24 @@ function findHardcodedAggregates(source) {
|
|
|
11684
11743
|
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).`
|
|
11685
11744
|
});
|
|
11686
11745
|
}
|
|
11746
|
+
const propCandidates = [...code.matchAll(OBJECT_PROP_RE)].map((m) => ({ name: m[1], text: m[0].trim().replace(/[,}]$/, "") }));
|
|
11747
|
+
for (const c of propCandidates) {
|
|
11748
|
+
const accessPattern = `\\??\\.${c.name}\\b`;
|
|
11749
|
+
const accessRe = new RegExp(accessPattern);
|
|
11750
|
+
let flows = fmtArgLists.some((args) => accessRe.test(args));
|
|
11751
|
+
if (!flows) {
|
|
11752
|
+
const hop = code.match(new RegExp(`\\bconst\\s+(\\w+)\\s*=\\s*\\w+${accessPattern}`));
|
|
11753
|
+
if (hop) {
|
|
11754
|
+
const varRe = new RegExp(`\\b${hop[1]}\\b`);
|
|
11755
|
+
flows = fmtArgLists.some((args) => varRe.test(args));
|
|
11756
|
+
}
|
|
11757
|
+
}
|
|
11758
|
+
if (!flows) continue;
|
|
11759
|
+
violations.push({
|
|
11760
|
+
text: c.text,
|
|
11761
|
+
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.`
|
|
11762
|
+
});
|
|
11763
|
+
}
|
|
11687
11764
|
return violations;
|
|
11688
11765
|
}
|
|
11689
11766
|
function literalChunks(source) {
|
|
@@ -11801,7 +11878,7 @@ function resolveScriptRecipeStore(collections) {
|
|
|
11801
11878
|
|
|
11802
11879
|
// src/userResponse/scripts/script-store.ts
|
|
11803
11880
|
var fs6 = __toESM(require("fs"));
|
|
11804
|
-
var
|
|
11881
|
+
var path7 = __toESM(require("path"));
|
|
11805
11882
|
var import_crypto6 = require("crypto");
|
|
11806
11883
|
var DEFAULT_STORE_DIR = "scripts-store";
|
|
11807
11884
|
function stripAnalysisExport(body) {
|
|
@@ -11895,7 +11972,7 @@ export { getData };
|
|
|
11895
11972
|
var ScriptStore = class {
|
|
11896
11973
|
constructor(opts) {
|
|
11897
11974
|
this.store = opts?.store ?? resolveScriptRecipeStore(opts?.collections) ?? null;
|
|
11898
|
-
this.storeDir = opts?.baseDir ||
|
|
11975
|
+
this.storeDir = opts?.baseDir || path7.join(process.cwd(), DEFAULT_STORE_DIR);
|
|
11899
11976
|
this.projectId = opts?.projectId;
|
|
11900
11977
|
if (!this.store) {
|
|
11901
11978
|
logger.warn("[ScriptStore] No script-recipes metadata store injected \u2014 script reuse disabled this run.");
|
|
@@ -11965,7 +12042,7 @@ var ScriptStore = class {
|
|
|
11965
12042
|
if (version === row.version) {
|
|
11966
12043
|
return this.rowToRecipe(row, this.readBody(row.fileBase));
|
|
11967
12044
|
}
|
|
11968
|
-
const archivePath =
|
|
12045
|
+
const archivePath = path7.join(this.archiveDir, `${row.fileBase}.v${version}.ts`);
|
|
11969
12046
|
if (!fs6.existsSync(archivePath)) {
|
|
11970
12047
|
logger.warn(`[ScriptStore] getVersion(${id}, v${version}) \u2014 archived body not found, recipe is now at v${row.version}`);
|
|
11971
12048
|
return null;
|
|
@@ -11989,10 +12066,10 @@ var ScriptStore = class {
|
|
|
11989
12066
|
}
|
|
11990
12067
|
if (recipe.scriptBody) {
|
|
11991
12068
|
const normalized = normalizeScriptBody(recipe.scriptBody);
|
|
11992
|
-
const
|
|
11993
|
-
if (
|
|
12069
|
+
const hash2 = this.hash(normalized);
|
|
12070
|
+
if (hash2 !== recipe.bodyHash) {
|
|
11994
12071
|
this.writeBody(recipe.fileBase, normalized);
|
|
11995
|
-
recipe.bodyHash =
|
|
12072
|
+
recipe.bodyHash = hash2;
|
|
11996
12073
|
}
|
|
11997
12074
|
}
|
|
11998
12075
|
recipe.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -12280,7 +12357,7 @@ ${carried.trim()}
|
|
|
12280
12357
|
/** Absolute path to the .ts body for a recipe (used by the runner/MainAgent). */
|
|
12281
12358
|
getScriptPath(recipe) {
|
|
12282
12359
|
const base = recipe.fileBase || this.toSlug(recipe.name);
|
|
12283
|
-
return
|
|
12360
|
+
return path7.join(this.storeDir, `${base}.ts`);
|
|
12284
12361
|
}
|
|
12285
12362
|
// ============================================
|
|
12286
12363
|
// Internals
|
|
@@ -12466,7 +12543,7 @@ ${analysisBody.trim()}
|
|
|
12466
12543
|
return true;
|
|
12467
12544
|
}
|
|
12468
12545
|
bodyPath(fileBase) {
|
|
12469
|
-
return
|
|
12546
|
+
return path7.join(this.storeDir, `${fileBase}.ts`);
|
|
12470
12547
|
}
|
|
12471
12548
|
readBody(fileBase) {
|
|
12472
12549
|
try {
|
|
@@ -12478,7 +12555,7 @@ ${analysisBody.trim()}
|
|
|
12478
12555
|
}
|
|
12479
12556
|
/** Directory holding superseded bodies. Dot-prefixed so IDEs/`ls` hide it. */
|
|
12480
12557
|
get archiveDir() {
|
|
12481
|
-
return
|
|
12558
|
+
return path7.join(this.storeDir, ".versions");
|
|
12482
12559
|
}
|
|
12483
12560
|
/**
|
|
12484
12561
|
* Archive a superseded body as `.versions/<fileBase>.v<n>.ts`.
|
|
@@ -12488,7 +12565,7 @@ ${analysisBody.trim()}
|
|
|
12488
12565
|
*/
|
|
12489
12566
|
writeArchive(fileBase, version, body) {
|
|
12490
12567
|
if (!fs6.existsSync(this.archiveDir)) fs6.mkdirSync(this.archiveDir, { recursive: true });
|
|
12491
|
-
const finalPath =
|
|
12568
|
+
const finalPath = path7.join(this.archiveDir, `${fileBase}.v${version}.ts`);
|
|
12492
12569
|
const tmpPath = `${finalPath}.tmp-${(0, import_crypto6.randomBytes)(4).toString("hex")}`;
|
|
12493
12570
|
fs6.writeFileSync(tmpPath, body);
|
|
12494
12571
|
fs6.renameSync(tmpPath, finalPath);
|
|
@@ -12506,7 +12583,7 @@ ${analysisBody.trim()}
|
|
|
12506
12583
|
for (const f of fs6.readdirSync(this.archiveDir)) {
|
|
12507
12584
|
if (!f.startsWith(prefix) || !f.endsWith(".ts")) continue;
|
|
12508
12585
|
const suffix = f.slice(oldBase.length);
|
|
12509
|
-
fs6.renameSync(
|
|
12586
|
+
fs6.renameSync(path7.join(this.archiveDir, f), path7.join(this.archiveDir, `${newBase}${suffix}`));
|
|
12510
12587
|
}
|
|
12511
12588
|
} catch (err) {
|
|
12512
12589
|
logger.warn(`[ScriptStore] failed to move archives ${oldBase} \u2192 ${newBase}: ${err}`);
|
|
@@ -12532,7 +12609,7 @@ ${analysisBody.trim()}
|
|
|
12532
12609
|
const prefix = `${fileBase}.v`;
|
|
12533
12610
|
for (const f of fs6.readdirSync(this.archiveDir)) {
|
|
12534
12611
|
if (f.startsWith(prefix) && f.endsWith(".ts")) {
|
|
12535
|
-
fs6.unlinkSync(
|
|
12612
|
+
fs6.unlinkSync(path7.join(this.archiveDir, f));
|
|
12536
12613
|
}
|
|
12537
12614
|
}
|
|
12538
12615
|
} catch (err) {
|
|
@@ -12817,7 +12894,7 @@ var WRITE_SCRIPT_TOOL_DEF = {
|
|
|
12817
12894
|
};
|
|
12818
12895
|
var RESOLVE_ENTITIES_TOOL_DEF = {
|
|
12819
12896
|
name: "resolve_entities",
|
|
12820
|
-
description: 'ALWAYS call this FIRST, before any source query, on every question. It resolves
|
|
12897
|
+
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.',
|
|
12821
12898
|
input_schema: {
|
|
12822
12899
|
type: "object",
|
|
12823
12900
|
properties: {
|
|
@@ -12835,30 +12912,10 @@ var RESOLVE_ENTITIES_TOOL_DEF = {
|
|
|
12835
12912
|
},
|
|
12836
12913
|
required: ["text"]
|
|
12837
12914
|
}
|
|
12838
|
-
},
|
|
12839
|
-
concepts: {
|
|
12840
|
-
type: "array",
|
|
12841
|
-
items: { type: "string" },
|
|
12842
|
-
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."
|
|
12843
12915
|
}
|
|
12844
12916
|
}
|
|
12845
12917
|
}
|
|
12846
12918
|
};
|
|
12847
|
-
var RESOLVE_CONCEPTS_TOOL_DEF = {
|
|
12848
|
-
name: "resolve_concepts",
|
|
12849
|
-
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.',
|
|
12850
|
-
input_schema: {
|
|
12851
|
-
type: "object",
|
|
12852
|
-
properties: {
|
|
12853
|
-
names: {
|
|
12854
|
-
type: "array",
|
|
12855
|
-
items: { type: "string" },
|
|
12856
|
-
description: "Concept names from the CONCEPT CATALOG. Ask only for what this question needs \u2014 dependencies come back on their own."
|
|
12857
|
-
}
|
|
12858
|
-
},
|
|
12859
|
-
required: ["names"]
|
|
12860
|
-
}
|
|
12861
|
-
};
|
|
12862
12919
|
var EXECUTE_SCRIPT_TOOL_DEF = {
|
|
12863
12920
|
name: "execute_script",
|
|
12864
12921
|
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.",
|
|
@@ -12914,9 +12971,11 @@ var MainAgent = class {
|
|
|
12914
12971
|
*/
|
|
12915
12972
|
this.resolvedEntities = [];
|
|
12916
12973
|
/**
|
|
12917
|
-
* Concept bodies resolved this turn (
|
|
12918
|
-
*
|
|
12919
|
-
*
|
|
12974
|
+
* Concept bodies resolved this turn (currently: invariants only, via
|
|
12975
|
+
* loadConceptInvariants — concept resolution is no longer part of
|
|
12976
|
+
* resolve_entities or any other MainAgent-owned tool) — retained the same
|
|
12977
|
+
* way as resolvedEntities above, so they can be attached to the SourceAgent
|
|
12978
|
+
* dispatch.
|
|
12920
12979
|
*
|
|
12921
12980
|
* Without this, a concept resolved HERE (MainAgent's own tool loop) never
|
|
12922
12981
|
* reaches SourceAgent at all: SourceAgent only gets the natural-language
|
|
@@ -12992,7 +13051,6 @@ var MainAgent = class {
|
|
|
12992
13051
|
const entitySearchActive = this.entitySearchEnabled && entityCatalog.count > 0;
|
|
12993
13052
|
if (entityCatalog.count) logger.info(`[MainAgent] Entity catalog: ${entityCatalog.count} type(s) \u2014 resolve_entities pinned to iteration 1`);
|
|
12994
13053
|
const conceptCatalog = this.conceptsEnabled ? await this.loadConceptCatalog() : { text: "", count: 0 };
|
|
12995
|
-
const conceptsActive = this.conceptsEnabled && conceptCatalog.count > 0;
|
|
12996
13054
|
const conceptInvariants = this.conceptsEnabled ? await this.loadConceptInvariants() : { text: "", count: 0 };
|
|
12997
13055
|
if (conceptCatalog.count) {
|
|
12998
13056
|
logger.info(
|
|
@@ -13033,7 +13091,6 @@ var MainAgent = class {
|
|
|
13033
13091
|
...directToolDefs,
|
|
13034
13092
|
...workflowToolDefs,
|
|
13035
13093
|
...entitySearchActive ? [RESOLVE_ENTITIES_TOOL_DEF] : [],
|
|
13036
|
-
...conceptsActive ? [RESOLVE_CONCEPTS_TOOL_DEF] : [],
|
|
13037
13094
|
...this.scriptingEnabled ? [WRITE_SCRIPT_TOOL_DEF, EXECUTE_SCRIPT_TOOL_DEF, WRITE_ANALYSIS_TOOL_DEF] : [],
|
|
13038
13095
|
// Only offered when there is a catalog to choose from; without one the
|
|
13039
13096
|
// flow falls back to generateScriptComponents exactly as before.
|
|
@@ -13049,7 +13106,7 @@ var MainAgent = class {
|
|
|
13049
13106
|
let sourceCallCounter = 0;
|
|
13050
13107
|
const toolIdCallCounts = {};
|
|
13051
13108
|
let selectedWorkflow;
|
|
13052
|
-
const
|
|
13109
|
+
const toolHandlerInner = async (toolName, toolInput) => {
|
|
13053
13110
|
const workflow = this.workflows.find((w) => w.id === toolName);
|
|
13054
13111
|
if (workflow) {
|
|
13055
13112
|
return this.handleWorkflow(workflow, toolInput, (w) => {
|
|
@@ -13057,10 +13114,7 @@ var MainAgent = class {
|
|
|
13057
13114
|
});
|
|
13058
13115
|
}
|
|
13059
13116
|
if (toolName === "resolve_entities") {
|
|
13060
|
-
return this.handleResolveEntities(toolInput
|
|
13061
|
-
}
|
|
13062
|
-
if (toolName === "resolve_concepts") {
|
|
13063
|
-
return this.handleResolveConcepts(toolInput);
|
|
13117
|
+
return this.handleResolveEntities(toolInput);
|
|
13064
13118
|
}
|
|
13065
13119
|
if (toolName === "write_components") {
|
|
13066
13120
|
if (!this.scriptingEnabled) return "Component authoring is not enabled for this request.";
|
|
@@ -13088,7 +13142,8 @@ var MainAgent = class {
|
|
|
13088
13142
|
}
|
|
13089
13143
|
const sourceInput = {
|
|
13090
13144
|
intent: toolInput.intent || toolInput.query || JSON.stringify(toolInput),
|
|
13091
|
-
aggregation: toolInput.aggregation || "raw"
|
|
13145
|
+
aggregation: toolInput.aggregation || "raw",
|
|
13146
|
+
concepts: Array.isArray(toolInput.concepts) ? toolInput.concepts.filter((c) => typeof c === "string") : void 0
|
|
13092
13147
|
};
|
|
13093
13148
|
logger.info(`[MainAgent] Dispatching SourceAgent for "${externalTool.name}" | intent: "${sourceInput.intent}"`);
|
|
13094
13149
|
const preResolved = await this.preResolveSchema(externalTool, sourceInput.intent);
|
|
@@ -13117,10 +13172,20 @@ var MainAgent = class {
|
|
|
13117
13172
|
if (entityBlock) {
|
|
13118
13173
|
logger.info(`[MainAgent] Attached ${this.resolvedEntities.length} resolved entity id(s) to the dispatch: ${this.resolvedEntities.map((e) => `${e.entityType}=${e.instanceId}`).join(", ")}`);
|
|
13119
13174
|
}
|
|
13120
|
-
const conceptBlock = this.buildResolvedConceptsBlock();
|
|
13175
|
+
const conceptBlock = this.buildResolvedConceptsBlock(sourceInput.concepts);
|
|
13176
|
+
const wantedConcepts = new Set(sourceInput.concepts ?? []);
|
|
13177
|
+
const attachedConcepts = this.resolvedConcepts.filter((c) => c.alwaysInclude || wantedConcepts.has(c.name));
|
|
13121
13178
|
if (conceptBlock) {
|
|
13122
|
-
logger.info(`[MainAgent] Attached ${
|
|
13123
|
-
}
|
|
13179
|
+
logger.info(`[MainAgent] Attached ${attachedConcepts.length} resolved concept(s) to the dispatch: ${attachedConcepts.map((c) => c.name).join(", ")}`);
|
|
13180
|
+
}
|
|
13181
|
+
logger.file(
|
|
13182
|
+
`[DISPATCH] ${externalTool.name} | sourceInput.concepts requested:`,
|
|
13183
|
+
JSON.stringify(sourceInput.concepts ?? []),
|
|
13184
|
+
"\n entityBlock:",
|
|
13185
|
+
entityBlock || "(none)",
|
|
13186
|
+
"\n conceptBlock:",
|
|
13187
|
+
conceptBlock || "(none)"
|
|
13188
|
+
);
|
|
13124
13189
|
const result = await sourceAgent.execute({
|
|
13125
13190
|
...sourceInput,
|
|
13126
13191
|
intent: sourceInput.intent + entityBlock + conceptBlock
|
|
@@ -13149,7 +13214,7 @@ var MainAgent = class {
|
|
|
13149
13214
|
const entry = sourceAgent.getTraceEntry();
|
|
13150
13215
|
if (entry) {
|
|
13151
13216
|
entry.result = formatted;
|
|
13152
|
-
if (
|
|
13217
|
+
if (attachedConcepts.length) entry.resolvedConcepts = [...attachedConcepts];
|
|
13153
13218
|
entry.aggregation = sourceInput.aggregation;
|
|
13154
13219
|
}
|
|
13155
13220
|
} catch (traceErr) {
|
|
@@ -13157,8 +13222,21 @@ var MainAgent = class {
|
|
|
13157
13222
|
}
|
|
13158
13223
|
return formatted;
|
|
13159
13224
|
};
|
|
13225
|
+
const toolHandler = async (toolName, toolInput) => {
|
|
13226
|
+
logger.file(`[TOOL CALL] ${toolName} | input:`, JSON.stringify(toolInput));
|
|
13227
|
+
try {
|
|
13228
|
+
const result = await toolHandlerInner(toolName, toolInput);
|
|
13229
|
+
logger.file(`[TOOL RESULT] ${toolName} | result:`, typeof result === "string" ? result.slice(0, 4e3) : JSON.stringify(result).slice(0, 4e3));
|
|
13230
|
+
return result;
|
|
13231
|
+
} catch (err) {
|
|
13232
|
+
logger.file(`[TOOL ERROR] ${toolName} | error:`, err instanceof Error ? err.message : String(err));
|
|
13233
|
+
throw err;
|
|
13234
|
+
}
|
|
13235
|
+
};
|
|
13160
13236
|
let text = "";
|
|
13161
13237
|
const firstIterationToolChoice = entitySearchActive ? { type: "tool", name: "resolve_entities" } : (this.forkMode || this.editMode) && this.scriptingEnabled ? { type: "any" } : void 0;
|
|
13238
|
+
const conceptToolRegistered = this.externalTools.some((t) => t.id === "concept");
|
|
13239
|
+
const secondIterationToolChoice = conceptToolRegistered ? { type: "tool", name: "concept" } : void 0;
|
|
13162
13240
|
try {
|
|
13163
13241
|
text = await LLM.streamWithTools(
|
|
13164
13242
|
{
|
|
@@ -13177,7 +13255,8 @@ var MainAgent = class {
|
|
|
13177
13255
|
apiKey: apiKey || this.config.apiKey,
|
|
13178
13256
|
partial: streamCallback,
|
|
13179
13257
|
signal,
|
|
13180
|
-
...firstIterationToolChoice ? { firstIterationToolChoice } : {}
|
|
13258
|
+
...firstIterationToolChoice ? { firstIterationToolChoice } : {},
|
|
13259
|
+
...secondIterationToolChoice ? { secondIterationToolChoice } : {}
|
|
13181
13260
|
},
|
|
13182
13261
|
this.config.maxIterations
|
|
13183
13262
|
);
|
|
@@ -13348,24 +13427,6 @@ ${catalog}`,
|
|
|
13348
13427
|
const hardcodedComponentText = findHardcodedComponentText(body);
|
|
13349
13428
|
const hardcodedEntityNames = findHardcodedEntityNames(body, this.resolvedEntities.map((e) => e.displayName));
|
|
13350
13429
|
const componentViolations = [...hardcodedAggregates, ...hardcodedComponentText, ...hardcodedEntityNames];
|
|
13351
|
-
if (componentViolations.length > 0) {
|
|
13352
|
-
const named = componentViolations.map((v) => `"${v.text}"`).join(", ");
|
|
13353
|
-
const repeatedTexts = componentViolations.map((v) => v.text).filter((t) => this.lastComponentsViolationTexts.includes(t));
|
|
13354
|
-
this.lastComponentsViolationTexts = componentViolations.map((v) => v.text);
|
|
13355
|
-
logger.warn(`[MainAgent] write_components rejected \u2014 ${componentViolations.length} hardcoded value(s): ${named}`);
|
|
13356
|
-
const lines2 = componentViolations.map((v) => ` \u2022 "${v.text}" \u2014 ${v.reason}`).join("\n");
|
|
13357
|
-
let message = `write_components rejected: getComponents hardcodes ${componentViolations.length} value(s) that belong to this run's data, not fixed text:
|
|
13358
|
-
${lines2}
|
|
13359
|
-
|
|
13360
|
-
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.`;
|
|
13361
|
-
if (repeatedTexts.length > 0) {
|
|
13362
|
-
const repeatedList = repeatedTexts.map((t) => `"${t}"`).join(", ");
|
|
13363
|
-
message += `
|
|
13364
|
-
|
|
13365
|
-
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.`;
|
|
13366
|
-
}
|
|
13367
|
-
return message;
|
|
13368
|
-
}
|
|
13369
13430
|
const recipeId = this.scriptState.recipeId;
|
|
13370
13431
|
const draft = recipeId ? await this.scriptStore.get(recipeId) : null;
|
|
13371
13432
|
if (!recipeId || !draft) return "Draft was lost from the store. Call write_script again.";
|
|
@@ -13826,12 +13887,19 @@ The verified script is intact. You can still call write_analysis and write_compo
|
|
|
13826
13887
|
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:
|
|
13827
13888
|
${lines.join("\n")}`;
|
|
13828
13889
|
}
|
|
13829
|
-
/**
|
|
13830
|
-
|
|
13890
|
+
/**
|
|
13891
|
+
* Dedupe-and-retain helper shared by loadConceptInvariants and the generic
|
|
13892
|
+
* direct-tool `resolvedConcepts` opt-in hook. `alwaysInclude` marks entries
|
|
13893
|
+
* that buildResolvedConceptsBlock must attach to EVERY dispatch regardless
|
|
13894
|
+
* of that dispatch's `concepts` param — true only for invariants, which are
|
|
13895
|
+
* meant to apply everywhere by definition. Concepts retained without it
|
|
13896
|
+
* (e.g. via the `concept` tool's `get`) are scoped per-dispatch instead.
|
|
13897
|
+
*/
|
|
13898
|
+
retainResolvedConcepts(resolved, opts) {
|
|
13831
13899
|
for (const c of resolved) {
|
|
13832
13900
|
if (!c?.name || !c?.body) continue;
|
|
13833
13901
|
if (this.resolvedConcepts.some((existing) => existing.name === c.name)) continue;
|
|
13834
|
-
this.resolvedConcepts.push({ name: String(c.name), kind: String(c.kind ?? ""), body: String(c.body) });
|
|
13902
|
+
this.resolvedConcepts.push({ name: String(c.name), kind: String(c.kind ?? ""), body: String(c.body), alwaysInclude: !!opts?.alwaysInclude });
|
|
13835
13903
|
}
|
|
13836
13904
|
}
|
|
13837
13905
|
/**
|
|
@@ -13839,10 +13907,29 @@ ${lines.join("\n")}`;
|
|
|
13839
13907
|
* entity ids — appended to the dispatch intent so SourceAgent's own,
|
|
13840
13908
|
* independent LLM call receives the resolved definitions verbatim rather
|
|
13841
13909
|
* than only MainAgent's paraphrased intent text.
|
|
13910
|
+
*
|
|
13911
|
+
* Unlike buildResolvedEntitiesBlock, non-invariant entries here are SCOPED
|
|
13912
|
+
* to `names` (this dispatch's SourceToolInput.concepts) rather than
|
|
13913
|
+
* dumping the whole-turn `this.resolvedConcepts` accumulator into every
|
|
13914
|
+
* dispatch. A concept body is phrased as an authoritative override
|
|
13915
|
+
* ("overrides the schema... your own reasoning") — fetching several via
|
|
13916
|
+
* `get` to compare/discard, or a turn needing different concepts for
|
|
13917
|
+
* different sub-questions, must not leak an irrelevant concept's filters
|
|
13918
|
+
* into an unrelated dispatch. An entry not named in `names` and not
|
|
13919
|
+
* `alwaysInclude` is left out — a concept the model forgot to name is a
|
|
13920
|
+
* visible, fixable miss; one that leaks in uninvited is not.
|
|
13921
|
+
*
|
|
13922
|
+
* `alwaysInclude` entries (invariants — see loadConceptInvariants) bypass
|
|
13923
|
+
* `names` entirely and attach to every dispatch regardless, same as before
|
|
13924
|
+
* this method took a `names` param — they are meant to apply everywhere,
|
|
13925
|
+
* not just when the model happens to ask for them.
|
|
13842
13926
|
*/
|
|
13843
|
-
buildResolvedConceptsBlock() {
|
|
13927
|
+
buildResolvedConceptsBlock(names) {
|
|
13844
13928
|
if (!this.resolvedConcepts.length) return "";
|
|
13845
|
-
const
|
|
13929
|
+
const wanted = new Set(names ?? []);
|
|
13930
|
+
const scoped = this.resolvedConcepts.filter((c) => c.alwaysInclude || wanted.has(c.name));
|
|
13931
|
+
if (!scoped.length) return "";
|
|
13932
|
+
const lines = scoped.map((c) => `- ${c.name}${c.kind ? ` (${c.kind})` : ""}: ${c.body}`);
|
|
13846
13933
|
return `
|
|
13847
13934
|
|
|
13848
13935
|
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:
|
|
@@ -13857,11 +13944,21 @@ ${lines.join("\n")}`;
|
|
|
13857
13944
|
return !!this.config.collections?.["concepts"]?.["resolve"];
|
|
13858
13945
|
}
|
|
13859
13946
|
/**
|
|
13860
|
-
*
|
|
13947
|
+
* The concept catalog for the system prompt: one line per concept — name,
|
|
13948
|
+
* aliases, and what it is for. No bodies — the body comes back from
|
|
13861
13949
|
* resolve_concepts once the LLM has named one, exactly as with the entity
|
|
13862
13950
|
* catalog. Putting bodies here would rebuild the always-on context problem
|
|
13863
13951
|
* concepts exist to solve.
|
|
13864
13952
|
*
|
|
13953
|
+
* The description is what makes the list usable. It used to be names only,
|
|
13954
|
+
* which asked the model to judge from a name alone whether `ref-data null
|
|
13955
|
+
* sentinel` bears on the question in front of it — and a wrong judgement is
|
|
13956
|
+
* silent, since it simply does not fetch. A line of prose per concept costs
|
|
13957
|
+
* a few tokens and is read rather than matched.
|
|
13958
|
+
*
|
|
13959
|
+
* Aliases stay, because they are how a user is likely to phrase it, and
|
|
13960
|
+
* showing them is what stops the LLM inventing a near-miss name.
|
|
13961
|
+
*
|
|
13865
13962
|
* Not cached, for the same reason loadEntityCatalog isn't: unchanged rows
|
|
13866
13963
|
* render byte-identical text so the prompt-cache prefix still hits, and a
|
|
13867
13964
|
* process cache would only buy a window where a new concept is invisible.
|
|
@@ -13874,7 +13971,8 @@ ${lines.join("\n")}`;
|
|
|
13874
13971
|
const rows = res?.data || [];
|
|
13875
13972
|
const text = rows.map((r) => {
|
|
13876
13973
|
const aka = r.aliases?.length ? ` (also: ${r.aliases.join(", ")})` : "";
|
|
13877
|
-
|
|
13974
|
+
const what = r.description?.trim() ? ` \u2014 ${r.description.trim()}` : "";
|
|
13975
|
+
return `- ${r.name}${aka}${what}`;
|
|
13878
13976
|
}).join("\n");
|
|
13879
13977
|
return { text, count: rows.length };
|
|
13880
13978
|
} catch (err) {
|
|
@@ -13892,10 +13990,14 @@ ${lines.join("\n")}`;
|
|
|
13892
13990
|
* Bodies ARE included here — unlike the catalog — because there is nothing to
|
|
13893
13991
|
* request: being unrequestable is what makes them invariants.
|
|
13894
13992
|
*
|
|
13895
|
-
* Also retained into resolvedConcepts
|
|
13896
|
-
*
|
|
13897
|
-
*
|
|
13898
|
-
*
|
|
13993
|
+
* Also retained into resolvedConcepts so these bodies reach SourceAgent's
|
|
13994
|
+
* dispatch too — an invariant is meant to apply everywhere, not just
|
|
13995
|
+
* wherever the model happens to ask for something whose `requires` chain
|
|
13996
|
+
* reaches it. This is now the ONLY way a concept body reaches
|
|
13997
|
+
* resolvedConcepts/SourceAgent's dispatch automatically — a non-invariant
|
|
13998
|
+
* concept fetched through a project's own concept-lookup tool (if any) is
|
|
13999
|
+
* NOT retained here, and will not reach a later SourceAgent dispatch unless
|
|
14000
|
+
* the model itself restates it in the intent it writes.
|
|
13899
14001
|
*/
|
|
13900
14002
|
async loadConceptInvariants() {
|
|
13901
14003
|
const fn = this.config.collections?.["concepts"]?.["invariants"];
|
|
@@ -13903,7 +14005,7 @@ ${lines.join("\n")}`;
|
|
|
13903
14005
|
try {
|
|
13904
14006
|
const res = await fn({});
|
|
13905
14007
|
const rows = res?.data || [];
|
|
13906
|
-
this.retainResolvedConcepts(rows);
|
|
14008
|
+
this.retainResolvedConcepts(rows, { alwaysInclude: true });
|
|
13907
14009
|
const text = rows.map((r) => `### ${r.name}
|
|
13908
14010
|
${r.body}`).join("\n\n");
|
|
13909
14011
|
return { text, count: rows.length };
|
|
@@ -13912,161 +14014,11 @@ ${r.body}`).join("\n\n");
|
|
|
13912
14014
|
return { text: "", count: 0 };
|
|
13913
14015
|
}
|
|
13914
14016
|
}
|
|
13915
|
-
/**
|
|
13916
|
-
* Resolve the concepts for a turn, with a SERVER-SIDE FLOOR.
|
|
13917
|
-
*
|
|
13918
|
-
* Pinning a tool guarantees it is called; it does not guarantee the model
|
|
13919
|
-
* fills in the arguments. Observed: a question containing the exact words
|
|
13920
|
-
* "revenue forecast" produced a call with no concepts at all, and the agent
|
|
13921
|
-
* then used an incomplete copy of the formula from the knowledge base — the
|
|
13922
|
-
* resulting query filtered a customer id against a project column and
|
|
13923
|
-
* returned zero rows on every attempt.
|
|
13924
|
-
*
|
|
13925
|
-
* So when the model names nothing, scan the question itself for catalog names
|
|
13926
|
-
* and aliases on word boundaries. Precise (a literal name is evidence, not a
|
|
13927
|
-
* guess) and asymmetric in the safe direction: an unneeded definition costs a
|
|
13928
|
-
* few hundred ignorable tokens, a missing one costs a wrong answer.
|
|
13929
|
-
*/
|
|
13930
|
-
/**
|
|
13931
|
-
* Returns the tool-facing fields to spread into the resolve_entities JSON
|
|
13932
|
-
* response, PLUS a `_trace` field with everything the trace needs that the
|
|
13933
|
-
* model does not (asked/viaFloor/unmatched). Callers must pull `_trace` out
|
|
13934
|
-
* before spreading the rest into what the model sees — see handleResolveEntities.
|
|
13935
|
-
*/
|
|
13936
|
-
async resolveConceptsForTurn(asked, userPrompt) {
|
|
13937
|
-
const resolveFn = this.config.collections?.["concepts"]?.["resolve"];
|
|
13938
|
-
if (!resolveFn) return {};
|
|
13939
|
-
let names = [...asked];
|
|
13940
|
-
let viaFloor = [];
|
|
13941
|
-
if (!names.length && userPrompt) {
|
|
13942
|
-
try {
|
|
13943
|
-
const catFn = this.config.collections?.["concepts"]?.["catalog"];
|
|
13944
|
-
const cat = catFn ? await catFn({}) : null;
|
|
13945
|
-
const haystack = ` ${userPrompt.toLowerCase().replace(/\s+/g, " ")} `;
|
|
13946
|
-
for (const row of cat?.data || []) {
|
|
13947
|
-
const labels = [row.name, ...row.aliases || []].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
13948
|
-
for (const label of labels) {
|
|
13949
|
-
const l = String(label).toLowerCase().trim();
|
|
13950
|
-
if (l.length < 4) continue;
|
|
13951
|
-
if (haystack.includes(` ${l} `) || haystack.includes(` ${l},`) || haystack.includes(` ${l}?`) || haystack.includes(` ${l}.`)) {
|
|
13952
|
-
names.push(row.name);
|
|
13953
|
-
viaFloor.push(row.name);
|
|
13954
|
-
break;
|
|
13955
|
-
}
|
|
13956
|
-
}
|
|
13957
|
-
}
|
|
13958
|
-
} catch (err) {
|
|
13959
|
-
logger.warn(`[MainAgent] concept floor scan failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
13960
|
-
}
|
|
13961
|
-
}
|
|
13962
|
-
if (!names.length) return {};
|
|
13963
|
-
try {
|
|
13964
|
-
const res = await resolveFn({ names });
|
|
13965
|
-
const resolved = res?.resolved || [];
|
|
13966
|
-
const impliedKeys = new Set((res?.implied || []).map((s) => s.toLowerCase()));
|
|
13967
|
-
this.retainResolvedConcepts(resolved);
|
|
13968
|
-
logger.info(
|
|
13969
|
-
`[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(", ")}` : "")
|
|
13970
|
-
);
|
|
13971
|
-
const _trace = {
|
|
13972
|
-
asked,
|
|
13973
|
-
viaFloor,
|
|
13974
|
-
returned: resolved.map((c) => ({
|
|
13975
|
-
name: c.name,
|
|
13976
|
-
kind: c.kind,
|
|
13977
|
-
definition: c.body,
|
|
13978
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
13979
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
13980
|
-
})),
|
|
13981
|
-
implied: res?.implied || [],
|
|
13982
|
-
unmatched: res?.unmatched || [],
|
|
13983
|
-
corrected: res?.corrected || [],
|
|
13984
|
-
suggestions: res?.suggestions || []
|
|
13985
|
-
};
|
|
13986
|
-
if (!resolved.length) return { _trace };
|
|
13987
|
-
return {
|
|
13988
|
-
concepts: resolved.map((c) => ({
|
|
13989
|
-
name: c.name,
|
|
13990
|
-
kind: c.kind,
|
|
13991
|
-
definition: c.body,
|
|
13992
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
13993
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
13994
|
-
})),
|
|
13995
|
-
...res?.corrected?.length ? { conceptsCorrected: res.corrected } : {},
|
|
13996
|
-
...res?.suggestions?.length ? { conceptsDidYouMean: res.suggestions } : {},
|
|
13997
|
-
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(", ")}.` : ""),
|
|
13998
|
-
_trace
|
|
13999
|
-
};
|
|
14000
|
-
} catch (err) {
|
|
14001
|
-
logger.warn(`[MainAgent] concept resolution inside resolve_entities failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
14002
|
-
return { _trace: { asked, viaFloor, error: err instanceof Error ? err.message : String(err) } };
|
|
14003
|
-
}
|
|
14004
|
-
}
|
|
14005
14017
|
/**
|
|
14006
14018
|
* Never throws: a concept-store outage must degrade to "no definition found"
|
|
14007
14019
|
* and leave the agent deriving the formula as it does today, not take the
|
|
14008
14020
|
* whole answer down.
|
|
14009
14021
|
*/
|
|
14010
|
-
async handleResolveConcepts(toolInput) {
|
|
14011
|
-
const fn = this.config.collections?.["concepts"]?.["resolve"];
|
|
14012
|
-
const names = Array.isArray(toolInput?.names) ? toolInput.names.filter(Boolean) : [];
|
|
14013
|
-
if (!fn) {
|
|
14014
|
-
return JSON.stringify({ concepts: [], unmatched: names, note: "Concept definitions are not available; derive from the schema and say so." });
|
|
14015
|
-
}
|
|
14016
|
-
if (!names.length) {
|
|
14017
|
-
return JSON.stringify({ concepts: [], unmatched: [], note: "No concept names given \u2014 continue." });
|
|
14018
|
-
}
|
|
14019
|
-
try {
|
|
14020
|
-
const res = await fn({ names });
|
|
14021
|
-
const resolved = res?.resolved || [];
|
|
14022
|
-
const impliedKeys = new Set((res?.implied || []).map((s) => s.toLowerCase()));
|
|
14023
|
-
this.retainResolvedConcepts(resolved);
|
|
14024
|
-
logger.info(
|
|
14025
|
-
`[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(", ")}` : "")
|
|
14026
|
-
);
|
|
14027
|
-
const suggestions = res?.suggestions || [];
|
|
14028
|
-
const corrected = res?.corrected || [];
|
|
14029
|
-
const notes = [];
|
|
14030
|
-
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.");
|
|
14031
|
-
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.`);
|
|
14032
|
-
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.`);
|
|
14033
|
-
if (!resolved.length && !suggestions.length) notes.push("No definition on file \u2014 derive it yourself and say so in your answer.");
|
|
14034
|
-
const seenByLLM = {
|
|
14035
|
-
concepts: resolved.map((c) => ({
|
|
14036
|
-
name: c.name,
|
|
14037
|
-
kind: c.kind,
|
|
14038
|
-
definition: c.body,
|
|
14039
|
-
...c.returns && Object.keys(c.returns).length ? { returns: c.returns } : {},
|
|
14040
|
-
...impliedKeys.has(String(c.name).toLowerCase()) ? { viaRequires: true } : {}
|
|
14041
|
-
})),
|
|
14042
|
-
...corrected.length ? { corrected } : {},
|
|
14043
|
-
...suggestions.length ? { didYouMean: suggestions } : {},
|
|
14044
|
-
unmatched: res?.unmatched || [],
|
|
14045
|
-
note: notes.join(" ")
|
|
14046
|
-
};
|
|
14047
|
-
try {
|
|
14048
|
-
if (this.config.trace) {
|
|
14049
|
-
this.config.trace.concepts.calls.push({
|
|
14050
|
-
via: "resolve_concepts",
|
|
14051
|
-
asked: names,
|
|
14052
|
-
implied: res?.implied || [],
|
|
14053
|
-
returned: seenByLLM.concepts,
|
|
14054
|
-
...corrected.length ? { corrected } : {},
|
|
14055
|
-
...suggestions.length ? { suggestions } : {},
|
|
14056
|
-
unmatched: res?.unmatched || [],
|
|
14057
|
-
note: notes.join(" ")
|
|
14058
|
-
});
|
|
14059
|
-
}
|
|
14060
|
-
} catch (traceErr) {
|
|
14061
|
-
logger.warn(`[MainAgent] Trace capture failed for resolve_concepts (non-blocking): ${traceErr}`);
|
|
14062
|
-
}
|
|
14063
|
-
return JSON.stringify(seenByLLM);
|
|
14064
|
-
} catch (err) {
|
|
14065
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14066
|
-
logger.warn(`[MainAgent] resolve_concepts failed (non-blocking): ${msg}`);
|
|
14067
|
-
return JSON.stringify({ concepts: [], unmatched: names, note: "Concept lookup failed; derive from the schema and say so." });
|
|
14068
|
-
}
|
|
14069
|
-
}
|
|
14070
14022
|
/**
|
|
14071
14023
|
* Compact catalog (type + gloss) for the system prompt. This is what lets the
|
|
14072
14024
|
* LLM NAME a concept instead of guessing at our vocabulary — it can infer
|
|
@@ -14097,20 +14049,18 @@ ${r.body}`).join("\n\n");
|
|
|
14097
14049
|
* Never throws: a resolver outage must degrade to "nothing resolved" and
|
|
14098
14050
|
* leave the agent exactly as it was, not take the whole answer down.
|
|
14099
14051
|
*/
|
|
14100
|
-
async handleResolveEntities(toolInput
|
|
14052
|
+
async handleResolveEntities(toolInput) {
|
|
14101
14053
|
const fn = this.config.collections?.["entity-search"]?.["resolveEntities"];
|
|
14102
14054
|
const mentions = Array.isArray(toolInput?.mentions) ? toolInput.mentions.filter((m) => m?.text) : [];
|
|
14103
14055
|
const entityTypes = Array.isArray(toolInput?.entityTypes) ? toolInput.entityTypes.filter(Boolean) : [];
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
logger.warn(`[MainAgent] Trace capture failed for concepts-in-resolve_entities (non-blocking): ${traceErr}`);
|
|
14113
|
-
}
|
|
14056
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14057
|
+
const named = mentions.map((m) => `"${m.text}"`).join(", ");
|
|
14058
|
+
this.streamBuffer.write(`
|
|
14059
|
+
|
|
14060
|
+
\u{1F50D} **Resolving entities${named ? `: ${named}` : ""}...**
|
|
14061
|
+
|
|
14062
|
+
`);
|
|
14063
|
+
await streamDelay();
|
|
14114
14064
|
}
|
|
14115
14065
|
const traceEarlyReturn = (note) => {
|
|
14116
14066
|
try {
|
|
@@ -14123,23 +14073,48 @@ ${r.body}`).join("\n\n");
|
|
|
14123
14073
|
};
|
|
14124
14074
|
if (!fn) {
|
|
14125
14075
|
traceEarlyReturn("Entity search unavailable");
|
|
14126
|
-
|
|
14076
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14077
|
+
this.streamBuffer.write(`\u26A0\uFE0F **Entity search unavailable** \u2014 proceeding on schema alone.
|
|
14078
|
+
|
|
14079
|
+
`);
|
|
14080
|
+
}
|
|
14081
|
+
return JSON.stringify({ resolved: [], unresolved: [], entityMap: {}, note: "Entity search is not available; proceed using the schema." });
|
|
14127
14082
|
}
|
|
14128
14083
|
if (!mentions.length && !entityTypes.length) {
|
|
14129
14084
|
traceEarlyReturn("Nothing named in the question to resolve");
|
|
14085
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14086
|
+
this.streamBuffer.write(`\u2705 **Entity search:** nothing to resolve for this question.
|
|
14087
|
+
|
|
14088
|
+
`);
|
|
14089
|
+
}
|
|
14130
14090
|
return JSON.stringify({
|
|
14131
14091
|
resolved: [],
|
|
14132
14092
|
unresolved: [],
|
|
14133
14093
|
entityMap: {},
|
|
14134
|
-
|
|
14135
|
-
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."
|
|
14094
|
+
note: "Nothing to resolve for this question \u2014 continue."
|
|
14136
14095
|
});
|
|
14137
14096
|
}
|
|
14138
14097
|
try {
|
|
14139
14098
|
const res = await fn({ mentions, entityTypes });
|
|
14140
14099
|
logger.info(
|
|
14141
|
-
`[MainAgent] resolve_entities: ${res?.resolved?.length ?? 0} resolved, ${res?.unresolved?.length ?? 0} unresolved, ${Object.keys(res?.entityMap || {}).length}
|
|
14100
|
+
`[MainAgent] resolve_entities: ${res?.resolved?.length ?? 0} resolved, ${res?.unresolved?.length ?? 0} unresolved, ${Object.keys(res?.entityMap || {}).length} type(s)`
|
|
14142
14101
|
);
|
|
14102
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14103
|
+
const resolvedCount = res?.resolved?.length ?? 0;
|
|
14104
|
+
const unresolvedCount = res?.unresolved?.length ?? 0;
|
|
14105
|
+
const names = (Array.isArray(res?.resolved) ? res.resolved : []).map((r) => {
|
|
14106
|
+
const label = r?.displayName || r?.mention;
|
|
14107
|
+
if (!label) return null;
|
|
14108
|
+
return r?.entityType ? `${label} (${r.entityType})` : label;
|
|
14109
|
+
}).filter(Boolean).join(", ");
|
|
14110
|
+
const unresolvedNames = (Array.isArray(res?.unresolved) ? res.unresolved : []).map((u) => typeof u === "string" ? u : u?.text).filter(Boolean).join(", ");
|
|
14111
|
+
let line = `\u2705 **Entity search:** ${resolvedCount} resolved`;
|
|
14112
|
+
if (names) line += ` (${names})`;
|
|
14113
|
+
if (unresolvedCount) line += `, ${unresolvedCount} unresolved${unresolvedNames ? ` (${unresolvedNames})` : ""}`;
|
|
14114
|
+
this.streamBuffer.write(`${line}
|
|
14115
|
+
|
|
14116
|
+
`);
|
|
14117
|
+
}
|
|
14143
14118
|
const TIE_EPSILON = 0.05;
|
|
14144
14119
|
const key = (e) => `${e.entityType}::${e.instanceId}`;
|
|
14145
14120
|
const add = (mention, c) => {
|
|
@@ -14175,7 +14150,7 @@ ${r.body}`).join("\n\n");
|
|
|
14175
14150
|
attrs: r.attrs && typeof r.attrs === "object" ? r.attrs : void 0
|
|
14176
14151
|
});
|
|
14177
14152
|
}
|
|
14178
|
-
const seenByLLM = { ...res
|
|
14153
|
+
const seenByLLM = { ...res };
|
|
14179
14154
|
try {
|
|
14180
14155
|
if (this.config.trace) {
|
|
14181
14156
|
this.config.trace.entityResolution.calls.push({ mentions, entityTypes, ...seenByLLM });
|
|
@@ -14187,6 +14162,13 @@ ${r.body}`).join("\n\n");
|
|
|
14187
14162
|
} catch (err) {
|
|
14188
14163
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14189
14164
|
logger.warn(`[MainAgent] resolve_entities failed (non-blocking): ${msg}`);
|
|
14165
|
+
if (this.streamBuffer.hasCallback()) {
|
|
14166
|
+
this.streamBuffer.write(`
|
|
14167
|
+
|
|
14168
|
+
\u274C **Entity search failed:** ${msg}
|
|
14169
|
+
|
|
14170
|
+
`);
|
|
14171
|
+
}
|
|
14190
14172
|
try {
|
|
14191
14173
|
if (this.config.trace) {
|
|
14192
14174
|
this.config.trace.entityResolution.calls.push({
|
|
@@ -14205,8 +14187,7 @@ ${r.body}`).join("\n\n");
|
|
|
14205
14187
|
resolved: [],
|
|
14206
14188
|
entityMap: {},
|
|
14207
14189
|
error: msg,
|
|
14208
|
-
unresolved: mentions.map((m) => m.text)
|
|
14209
|
-
...conceptBlock
|
|
14190
|
+
unresolved: mentions.map((m) => m.text)
|
|
14210
14191
|
});
|
|
14211
14192
|
}
|
|
14212
14193
|
}
|
|
@@ -14299,7 +14280,7 @@ ${lines.join("\n")}`;
|
|
|
14299
14280
|
if (this.streamBuffer.hasCallback()) {
|
|
14300
14281
|
this.streamBuffer.write(`
|
|
14301
14282
|
|
|
14302
|
-
\u{1F527} **Running ${tool
|
|
14283
|
+
\u{1F527} **Running ${describeDirectToolCall(tool, toolInput)}...**
|
|
14303
14284
|
|
|
14304
14285
|
`);
|
|
14305
14286
|
await streamDelay();
|
|
@@ -14310,19 +14291,24 @@ ${lines.join("\n")}`;
|
|
|
14310
14291
|
if (result && result.error) {
|
|
14311
14292
|
const errorMsg = typeof result.error === "string" ? result.error : JSON.stringify(result.error);
|
|
14312
14293
|
logger.warn(`[MainAgent] Direct tool "${tool.name}" returned error: ${errorMsg}`);
|
|
14294
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, error: errorMsg, durationMs: executionTimeMs });
|
|
14313
14295
|
return `\u274C Tool "${tool.name}" error: ${errorMsg}`;
|
|
14314
14296
|
}
|
|
14297
|
+
if (Array.isArray(result?.resolvedConcepts)) {
|
|
14298
|
+
this.retainResolvedConcepts(result.resolvedConcepts, { alwaysInclude: true });
|
|
14299
|
+
}
|
|
14315
14300
|
const resultData = result.data || result;
|
|
14316
14301
|
const rowCount = Array.isArray(resultData) ? resultData.length : 1;
|
|
14317
14302
|
if (this.streamBuffer.hasCallback()) {
|
|
14318
|
-
this.streamBuffer.write(`\u2705 **${tool
|
|
14303
|
+
this.streamBuffer.write(`\u2705 **${describeDirectToolCall(tool, toolInput)}** completed (${rowCount} results, ${executionTimeMs}ms)
|
|
14319
14304
|
|
|
14320
14305
|
`);
|
|
14321
14306
|
}
|
|
14307
|
+
const isConceptTool = tool.id === "concept";
|
|
14322
14308
|
const formattedResult = formatToolResultForLLM(result, {
|
|
14323
14309
|
toolName: tool.name,
|
|
14324
|
-
maxRows: 5,
|
|
14325
|
-
maxCharsPerField: 200
|
|
14310
|
+
maxRows: isConceptTool ? 25 : 5,
|
|
14311
|
+
maxCharsPerField: isConceptTool ? 4e3 : 200
|
|
14326
14312
|
});
|
|
14327
14313
|
const sampleData = Array.isArray(resultData) ? resultData.slice(0, TOOL_TRACKING_SAMPLE_ROWS) : Array.isArray(formattedResult.data) ? formattedResult.data.slice(0, TOOL_TRACKING_SAMPLE_ROWS) : [];
|
|
14328
14314
|
executedTools.push({
|
|
@@ -14338,6 +14324,7 @@ ${lines.join("\n")}`;
|
|
|
14338
14324
|
outputSchema: tool.outputSchema
|
|
14339
14325
|
});
|
|
14340
14326
|
const formatted = typeof formattedResult === "string" ? formattedResult : JSON.stringify(formattedResult);
|
|
14327
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, output: result, durationMs: executionTimeMs });
|
|
14341
14328
|
return `\u2705 Tool "${tool.name}" executed successfully. ${rowCount} results returned.
|
|
14342
14329
|
|
|
14343
14330
|
${formatted}`;
|
|
@@ -14348,10 +14335,11 @@ ${formatted}`;
|
|
|
14348
14335
|
if (this.streamBuffer.hasCallback()) {
|
|
14349
14336
|
this.streamBuffer.write(`
|
|
14350
14337
|
|
|
14351
|
-
\u274C **${tool
|
|
14338
|
+
\u274C **${describeDirectToolCall(tool, toolInput)} failed:** ${errorMsg}
|
|
14352
14339
|
|
|
14353
14340
|
`);
|
|
14354
14341
|
}
|
|
14342
|
+
this.config.trace?.directTools.calls.push({ toolId: tool.id, input: toolInput, error: errorMsg, durationMs: executionTimeMs });
|
|
14355
14343
|
return `\u274C Tool "${tool.name}" failed: ${errorMsg}`;
|
|
14356
14344
|
}
|
|
14357
14345
|
}
|
|
@@ -14504,6 +14492,11 @@ ${propLines}` : ""
|
|
|
14504
14492
|
type: "string",
|
|
14505
14493
|
enum: ["raw", "pre-aggregate", "summary"],
|
|
14506
14494
|
description: 'How to return data. "pre-aggregate": use GROUP BY/SUM/COUNT for totals. "summary": high-level metrics. "raw": individual records.'
|
|
14495
|
+
},
|
|
14496
|
+
concepts: {
|
|
14497
|
+
type: "array",
|
|
14498
|
+
items: { type: "string" },
|
|
14499
|
+
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.`
|
|
14507
14500
|
}
|
|
14508
14501
|
},
|
|
14509
14502
|
required: ["intent"]
|
|
@@ -14727,6 +14720,22 @@ ${scopeWarning} The script re-runs them, so you can include more than one \`ctx.
|
|
|
14727
14720
|
return buildSourceSummaries(this.externalTools);
|
|
14728
14721
|
}
|
|
14729
14722
|
};
|
|
14723
|
+
function describeDirectToolCall(tool, toolInput) {
|
|
14724
|
+
const method = toolInput?.method;
|
|
14725
|
+
if (typeof method !== "string") return tool.name;
|
|
14726
|
+
switch (method) {
|
|
14727
|
+
case "find":
|
|
14728
|
+
return typeof toolInput.query === "string" ? `${tool.name} (find: "${toolInput.query}")` : `${tool.name} (find)`;
|
|
14729
|
+
case "get": {
|
|
14730
|
+
const names = Array.isArray(toolInput.names) ? toolInput.names.join(", ") : void 0;
|
|
14731
|
+
return names ? `${tool.name} (get: ${names})` : `${tool.name} (get)`;
|
|
14732
|
+
}
|
|
14733
|
+
case "run":
|
|
14734
|
+
return typeof toolInput.name === "string" ? `${tool.name} (run: ${toolInput.name})` : `${tool.name} (run)`;
|
|
14735
|
+
default:
|
|
14736
|
+
return `${tool.name} (${method})`;
|
|
14737
|
+
}
|
|
14738
|
+
}
|
|
14730
14739
|
function extractTablesFromSQL(sqls) {
|
|
14731
14740
|
const tables = /* @__PURE__ */ new Set();
|
|
14732
14741
|
const pattern = /(?:FROM|JOIN)\s+"?([a-zA-Z_][\w.]*)"?/gi;
|
|
@@ -14750,6 +14759,7 @@ function createRunTrace() {
|
|
|
14750
14759
|
scriptMatcher: null,
|
|
14751
14760
|
entityResolution: { calls: [] },
|
|
14752
14761
|
concepts: { calls: [] },
|
|
14762
|
+
directTools: { calls: [] },
|
|
14753
14763
|
componentGen: []
|
|
14754
14764
|
};
|
|
14755
14765
|
}
|
|
@@ -16044,12 +16054,12 @@ function answerCatalogForBinding(components) {
|
|
|
16044
16054
|
var import_dotenv = __toESM(require("dotenv"));
|
|
16045
16055
|
|
|
16046
16056
|
// src/userResponse/schema.ts
|
|
16047
|
-
var
|
|
16057
|
+
var import_path5 = __toESM(require("path"));
|
|
16048
16058
|
var import_fs5 = __toESM(require("fs"));
|
|
16049
16059
|
var Schema = class {
|
|
16050
16060
|
constructor(schemaFilePath) {
|
|
16051
16061
|
this.cachedSchema = null;
|
|
16052
|
-
this.schemaFilePath = schemaFilePath ||
|
|
16062
|
+
this.schemaFilePath = schemaFilePath || import_path5.default.join(process.cwd(), "../analysis/data/schema.json");
|
|
16053
16063
|
}
|
|
16054
16064
|
/**
|
|
16055
16065
|
* Gets the database schema from the schema file
|
|
@@ -16057,7 +16067,7 @@ var Schema = class {
|
|
|
16057
16067
|
*/
|
|
16058
16068
|
getDatabaseSchema() {
|
|
16059
16069
|
try {
|
|
16060
|
-
const dir =
|
|
16070
|
+
const dir = import_path5.default.dirname(this.schemaFilePath);
|
|
16061
16071
|
if (!import_fs5.default.existsSync(dir)) {
|
|
16062
16072
|
logger.info(`Creating directory structure: ${dir}`);
|
|
16063
16073
|
import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
@@ -17661,7 +17671,7 @@ ${executedToolsText}`);
|
|
|
17661
17671
|
*/
|
|
17662
17672
|
async handleUserRequest(userPrompt, components, apiKey, conversationHistory, responseMode = "text", streamCallback, collections, externalTools, userId) {
|
|
17663
17673
|
const startTime = Date.now();
|
|
17664
|
-
logger.clearFile();
|
|
17674
|
+
logger.clearFile(`legacy-${userPrompt}`);
|
|
17665
17675
|
logger.logLLMPrompt("handleUserRequest", "user", `User Prompt: ${userPrompt}`);
|
|
17666
17676
|
try {
|
|
17667
17677
|
const conversationMatch = await conversation_search_default.searchConversationsWithReranking({
|
|
@@ -18194,13 +18204,28 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
18194
18204
|
let runTrace;
|
|
18195
18205
|
const answerComps = answerComponents(components);
|
|
18196
18206
|
const bindingComps = projectForDatasetBinding(answerComps);
|
|
18197
|
-
logger.clearFile();
|
|
18207
|
+
logger.clearFile(`chat-${prompt}`);
|
|
18198
18208
|
logger.logLLMPrompt("agentUserResponse", "user", `User Prompt: ${prompt}`);
|
|
18199
18209
|
logger.info(`[AgentFlow] Starting | provider: ${provider} | prompt: "${prompt.substring(0, 50)}..."`);
|
|
18200
18210
|
try {
|
|
18201
18211
|
const cached = await replayExactMatch({ prompt, collections, userId, streamCallback, method: `${provider}-agent-exact-match`, startTime });
|
|
18202
18212
|
if (cached?.response) return cached.response;
|
|
18203
|
-
|
|
18213
|
+
let directReplay = cached?.directReplay;
|
|
18214
|
+
if (!cached) {
|
|
18215
|
+
const supersededMatch = await conversation_search_default.findExactMatch({
|
|
18216
|
+
userPrompt: prompt,
|
|
18217
|
+
collections,
|
|
18218
|
+
userId,
|
|
18219
|
+
statuses: ["superseded"]
|
|
18220
|
+
});
|
|
18221
|
+
const boundBinding = supersededMatch?.uiBlock?.scriptBinding;
|
|
18222
|
+
if (boundBinding?.recipeId) {
|
|
18223
|
+
directReplay = { recipeId: boundBinding.recipeId, params: boundBinding.params || {} };
|
|
18224
|
+
logger.info(
|
|
18225
|
+
`[AgentFlow] Exact match found a SUPERSEDED row (recipe edited since) \u2014 will re-run current recipe ${directReplay.recipeId} directly (skip matcher LLM)`
|
|
18226
|
+
);
|
|
18227
|
+
}
|
|
18228
|
+
}
|
|
18204
18229
|
logger.info(`[AgentFlow] No exact match \u2014 proceeding with agent`);
|
|
18205
18230
|
runTrace = createRunTrace();
|
|
18206
18231
|
const apiKey = (() => {
|
|
@@ -18285,7 +18310,7 @@ var get_agent_user_response = async (prompt, components, anthropicApiKey, groqAp
|
|
|
18285
18310
|
const boundRecipe = await scriptStore.get(directReplay.recipeId);
|
|
18286
18311
|
if (boundRecipe) {
|
|
18287
18312
|
scriptMatch = { tier: "high", recipe: boundRecipe, extractedParams: directReplay.params };
|
|
18288
|
-
logger.info(`[AgentFlow]
|
|
18313
|
+
logger.info(`[AgentFlow] Direct replay \u2014 running current recipe "${boundRecipe.name}" directly (skipped ScriptMatcher LLM)`);
|
|
18289
18314
|
} else {
|
|
18290
18315
|
logger.info(`[AgentFlow] Bound recipe ${directReplay.recipeId} no longer in store \u2014 falling back to ScriptMatcher LLM`);
|
|
18291
18316
|
}
|
|
@@ -18637,6 +18662,19 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${scriptMatch
|
|
|
18637
18662
|
}
|
|
18638
18663
|
};
|
|
18639
18664
|
}
|
|
18665
|
+
if (isDisplayEdit) {
|
|
18666
|
+
try {
|
|
18667
|
+
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
18668
|
+
if (invalidate) {
|
|
18669
|
+
const n = await invalidate({ recipeId: scriptMatch.recipe.id, userId });
|
|
18670
|
+
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to display-edited recipe ${scriptMatch.recipe.id}`);
|
|
18671
|
+
} else {
|
|
18672
|
+
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${scriptMatch.recipe.id} will replay the PRE-EDIT component set`);
|
|
18673
|
+
}
|
|
18674
|
+
} catch (invErr) {
|
|
18675
|
+
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
18676
|
+
}
|
|
18677
|
+
}
|
|
18640
18678
|
const elapsedTime2 = Date.now() - startTime;
|
|
18641
18679
|
logger.info(`[AgentFlow] Script flow complete | ${scriptResult.executedQueries.length} queries | ${matchedComponents2.length} components | ${elapsedTime2}ms`);
|
|
18642
18680
|
return {
|
|
@@ -18792,6 +18830,67 @@ BEGIN NOW. Your VERY FIRST action MUST be a \`write_script\` tool call containin
|
|
|
18792
18830
|
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, "");
|
|
18793
18831
|
streamBuffer.flush();
|
|
18794
18832
|
if (signal?.aborted) throw new RequestAbortedError();
|
|
18833
|
+
if (agentResponse.savedScript && editContext) {
|
|
18834
|
+
try {
|
|
18835
|
+
const authored = agentResponse.savedScript;
|
|
18836
|
+
if (runTrace) {
|
|
18837
|
+
try {
|
|
18838
|
+
runTrace.script = {
|
|
18839
|
+
reused: false,
|
|
18840
|
+
recipeId: authored.recipeId,
|
|
18841
|
+
body: authored.scriptBody,
|
|
18842
|
+
reasoning: editContext.instruction ?? null,
|
|
18843
|
+
executionResult: authored.executionResult ?? null,
|
|
18844
|
+
failedAttempts: agentResponse.failedScriptAttempts
|
|
18845
|
+
};
|
|
18846
|
+
} catch (traceErr) {
|
|
18847
|
+
logger.warn(`[AgentFlow] Trace capture failed for edited script (non-blocking): ${traceErr}`);
|
|
18848
|
+
}
|
|
18849
|
+
}
|
|
18850
|
+
const summaryLine = (agentResponse.text || "").split("\n").map((l) => l.trim()).find((l) => l.startsWith("\u270F\uFE0F"));
|
|
18851
|
+
const changeSummary = (summaryLine || editContext.instruction).replace(/^✏️\s*/, "").replace(/^\**\s*Updated the script:\s*/i, "").replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
18852
|
+
const committed = await scriptStore.commitEdit(
|
|
18853
|
+
editContext.recipeId,
|
|
18854
|
+
{
|
|
18855
|
+
recipeId: authored.recipeId,
|
|
18856
|
+
name: authored.name,
|
|
18857
|
+
intentDescription: authored.intentDescription,
|
|
18858
|
+
tags: authored.tags,
|
|
18859
|
+
parameters: authored.parameters,
|
|
18860
|
+
scriptBody: authored.scriptBody,
|
|
18861
|
+
sourceIds: authored.sourceIds,
|
|
18862
|
+
tables: authored.tables
|
|
18863
|
+
},
|
|
18864
|
+
{ instruction: editContext.instruction, changeSummary, editedBy: userId }
|
|
18865
|
+
);
|
|
18866
|
+
if (committed) {
|
|
18867
|
+
agentResponse.savedScript.recipeId = committed.id;
|
|
18868
|
+
scriptBinding = {
|
|
18869
|
+
recipeId: committed.id,
|
|
18870
|
+
version: committed.version,
|
|
18871
|
+
params: {},
|
|
18872
|
+
name: committed.name,
|
|
18873
|
+
columns: authored.executedQueries?.[0]?.data?.[0] ? Object.keys(authored.executedQueries[0].data[0]) : void 0
|
|
18874
|
+
};
|
|
18875
|
+
try {
|
|
18876
|
+
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
18877
|
+
if (invalidate) {
|
|
18878
|
+
const n = await invalidate({ recipeId: committed.id, userId });
|
|
18879
|
+
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to edited recipe ${committed.id}`);
|
|
18880
|
+
} else {
|
|
18881
|
+
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${committed.id} will replay the PRE-EDIT result`);
|
|
18882
|
+
}
|
|
18883
|
+
} catch (invErr) {
|
|
18884
|
+
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
18885
|
+
}
|
|
18886
|
+
} else {
|
|
18887
|
+
logger.warn(`[AgentFlow] commitEdit failed for "${editedRecipeName}" \u2014 original recipe left unchanged`);
|
|
18888
|
+
}
|
|
18889
|
+
} catch (err) {
|
|
18890
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
18891
|
+
logger.warn(`[AgentFlow] Failed to commit script edit (non-blocking): ${msg}`);
|
|
18892
|
+
}
|
|
18893
|
+
}
|
|
18795
18894
|
if (agentResponse.executedTools.length === 0 && agentResponse.savedScript?.executedQueries && agentResponse.savedScript.executedQueries.length > 0) {
|
|
18796
18895
|
for (const q of agentResponse.savedScript.executedQueries) {
|
|
18797
18896
|
agentResponse.executedTools.push({
|
|
@@ -19065,66 +19164,7 @@ RENDERING REQUIREMENT (overrides the default choice of component): ${carriedRequ
|
|
|
19065
19164
|
}
|
|
19066
19165
|
};
|
|
19067
19166
|
}
|
|
19068
|
-
if (agentResponse.savedScript && editContext) {
|
|
19069
|
-
try {
|
|
19070
|
-
const authored = agentResponse.savedScript;
|
|
19071
|
-
if (runTrace) {
|
|
19072
|
-
try {
|
|
19073
|
-
runTrace.script = {
|
|
19074
|
-
reused: false,
|
|
19075
|
-
recipeId: authored.recipeId,
|
|
19076
|
-
body: authored.scriptBody,
|
|
19077
|
-
reasoning: editContext.instruction ?? null,
|
|
19078
|
-
executionResult: authored.executionResult ?? null,
|
|
19079
|
-
failedAttempts: agentResponse.failedScriptAttempts
|
|
19080
|
-
};
|
|
19081
|
-
} catch (traceErr) {
|
|
19082
|
-
logger.warn(`[AgentFlow] Trace capture failed for edited script (non-blocking): ${traceErr}`);
|
|
19083
|
-
}
|
|
19084
|
-
}
|
|
19085
|
-
const summaryLine = (agentResponse.text || "").split("\n").map((l) => l.trim()).find((l) => l.startsWith("\u270F\uFE0F"));
|
|
19086
|
-
const changeSummary = (summaryLine || editContext.instruction).replace(/^✏️\s*/, "").replace(/^\**\s*Updated the script:\s*/i, "").replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
19087
|
-
const committed = await scriptStore.commitEdit(
|
|
19088
|
-
editContext.recipeId,
|
|
19089
|
-
{
|
|
19090
|
-
recipeId: authored.recipeId,
|
|
19091
|
-
name: authored.name,
|
|
19092
|
-
intentDescription: authored.intentDescription,
|
|
19093
|
-
tags: authored.tags,
|
|
19094
|
-
parameters: authored.parameters,
|
|
19095
|
-
scriptBody: authored.scriptBody,
|
|
19096
|
-
sourceIds: authored.sourceIds,
|
|
19097
|
-
tables: authored.tables
|
|
19098
|
-
},
|
|
19099
|
-
{ instruction: editContext.instruction, changeSummary, editedBy: userId }
|
|
19100
|
-
);
|
|
19101
|
-
if (committed) {
|
|
19102
|
-
scriptBinding = {
|
|
19103
|
-
recipeId: committed.id,
|
|
19104
|
-
version: committed.version,
|
|
19105
|
-
params: {},
|
|
19106
|
-
name: committed.name,
|
|
19107
|
-
columns: authored.executedQueries?.[0]?.data?.[0] ? Object.keys(authored.executedQueries[0].data[0]) : void 0
|
|
19108
|
-
};
|
|
19109
|
-
try {
|
|
19110
|
-
const invalidate = collections?.["conversation-history"]?.["invalidateByScriptRecipe"];
|
|
19111
|
-
if (invalidate) {
|
|
19112
|
-
const n = await invalidate({ recipeId: committed.id, userId });
|
|
19113
|
-
logger.info(`[AgentFlow] Superseded ${n?.count ?? n ?? 0} cached answer(s) bound to edited recipe ${committed.id}`);
|
|
19114
|
-
} else {
|
|
19115
|
-
logger.warn(`[AgentFlow] conversation-history.invalidateByScriptRecipe not wired \u2014 cached answers for recipe ${committed.id} will replay the PRE-EDIT result`);
|
|
19116
|
-
}
|
|
19117
|
-
} catch (invErr) {
|
|
19118
|
-
logger.warn(`[AgentFlow] Conversation invalidation failed (non-blocking): ${invErr}`);
|
|
19119
|
-
}
|
|
19120
|
-
} else {
|
|
19121
|
-
logger.warn(`[AgentFlow] commitEdit failed for "${editedRecipeName}" \u2014 original recipe left unchanged`);
|
|
19122
|
-
}
|
|
19123
|
-
} catch (err) {
|
|
19124
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19125
|
-
logger.warn(`[AgentFlow] Failed to commit script edit (non-blocking): ${msg}`);
|
|
19126
|
-
}
|
|
19127
|
-
} else if (agentResponse.savedScript) {
|
|
19167
|
+
if (agentResponse.savedScript && !editContext) {
|
|
19128
19168
|
try {
|
|
19129
19169
|
const authored = agentResponse.savedScript;
|
|
19130
19170
|
if (runTrace) {
|
|
@@ -19353,7 +19393,8 @@ function transformUIBlockForDB(uiblock, userPrompt, uiBlockId) {
|
|
|
19353
19393
|
};
|
|
19354
19394
|
}
|
|
19355
19395
|
function hasTraceContent(trace) {
|
|
19356
|
-
|
|
19396
|
+
if (!trace || typeof trace !== "object") return false;
|
|
19397
|
+
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;
|
|
19357
19398
|
}
|
|
19358
19399
|
async function saveConversation(params) {
|
|
19359
19400
|
const { userId, userPrompt, uiblock, uiBlockId, threadId, collections, status = "success", error: turnError, trace } = params;
|
|
@@ -19499,6 +19540,18 @@ var CONTEXT_CONFIG = {
|
|
|
19499
19540
|
function stripStreamMarkers(text) {
|
|
19500
19541
|
return text.replace(/__TEXT_COMPLETE__[\s\S]*/g, "").replace(/__ANSWER_COMPONENT_START__[\s\S]*?__ANSWER_COMPONENT_END__/g, "").replace(/__COMPONENT_GENERATION_START__/g, "").trim();
|
|
19501
19542
|
}
|
|
19543
|
+
function promptContextFrom(raw) {
|
|
19544
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
19545
|
+
const source = raw;
|
|
19546
|
+
const context = {};
|
|
19547
|
+
if (typeof source.dashboardId === "string" && source.dashboardId.trim()) {
|
|
19548
|
+
context.dashboardId = source.dashboardId.trim();
|
|
19549
|
+
}
|
|
19550
|
+
if (source.filters && typeof source.filters === "object" && !Array.isArray(source.filters)) {
|
|
19551
|
+
context.filters = source.filters;
|
|
19552
|
+
}
|
|
19553
|
+
return Object.keys(context).length ? context : void 0;
|
|
19554
|
+
}
|
|
19502
19555
|
var inFlightAborts = /* @__PURE__ */ new Map();
|
|
19503
19556
|
var recentlyAborted = /* @__PURE__ */ new Map();
|
|
19504
19557
|
var ABORT_TOMBSTONE_TTL_MS = 3e4;
|
|
@@ -19562,6 +19615,10 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19562
19615
|
const userConfig = payload.config;
|
|
19563
19616
|
const SA_RUNTIME = payload.SA_RUNTIME;
|
|
19564
19617
|
const wsId = userPromptRequest.from.id || "unknown";
|
|
19618
|
+
const reported = (response) => ({
|
|
19619
|
+
model: response?.data?.model,
|
|
19620
|
+
usage: response?.data?.usage
|
|
19621
|
+
});
|
|
19565
19622
|
const promptContext = `User Prompt: ${prompt?.substring(0, 50)}${(prompt?.length || 0) > 50 ? "..." : ""}`;
|
|
19566
19623
|
llmUsageLogger.resetLogFile(promptContext);
|
|
19567
19624
|
userPromptErrorLogger.resetLogFile(promptContext);
|
|
@@ -19685,7 +19742,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19685
19742
|
conversationHistory,
|
|
19686
19743
|
onStream: streamCallback,
|
|
19687
19744
|
onNarration: narrationCallback,
|
|
19688
|
-
signal: abortController.signal
|
|
19745
|
+
signal: abortController.signal,
|
|
19746
|
+
context: promptContextFrom(payload.context)
|
|
19689
19747
|
}) : await get_agent_user_response(
|
|
19690
19748
|
prompt,
|
|
19691
19749
|
components,
|
|
@@ -19742,7 +19800,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19742
19800
|
}
|
|
19743
19801
|
if (userId) {
|
|
19744
19802
|
const latencyMs = Date.now() - requestStartTime;
|
|
19745
|
-
const
|
|
19803
|
+
const turn = reported(userResponse);
|
|
19804
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19746
19805
|
const event = analyticsClient.buildEvent({
|
|
19747
19806
|
type: "chat_agent",
|
|
19748
19807
|
userId,
|
|
@@ -19755,7 +19814,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19755
19814
|
model,
|
|
19756
19815
|
errorMessage: "aborted",
|
|
19757
19816
|
conversationId: savedConversationId2,
|
|
19758
|
-
response: savedResponse2
|
|
19817
|
+
response: savedResponse2,
|
|
19818
|
+
usage: turn.usage
|
|
19759
19819
|
});
|
|
19760
19820
|
analyticsClient.track(event).catch(() => {
|
|
19761
19821
|
});
|
|
@@ -19809,7 +19869,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19809
19869
|
}
|
|
19810
19870
|
if (userId) {
|
|
19811
19871
|
const latencyMs = Date.now() - requestStartTime;
|
|
19812
|
-
const
|
|
19872
|
+
const turn = reported(userResponse);
|
|
19873
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19813
19874
|
const event = analyticsClient.buildEvent({
|
|
19814
19875
|
type: "chat_agent",
|
|
19815
19876
|
userId,
|
|
@@ -19822,7 +19883,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19822
19883
|
model,
|
|
19823
19884
|
errorMessage,
|
|
19824
19885
|
conversationId: savedConversationId2,
|
|
19825
|
-
response: savedResponse2
|
|
19886
|
+
response: savedResponse2,
|
|
19887
|
+
usage: turn.usage
|
|
19826
19888
|
});
|
|
19827
19889
|
analyticsClient.track(event).catch(() => {
|
|
19828
19890
|
});
|
|
@@ -19906,7 +19968,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19906
19968
|
llmUsageLogger.logSessionSummary(prompt?.substring(0, 50));
|
|
19907
19969
|
if (userId) {
|
|
19908
19970
|
const latencyMs = Date.now() - requestStartTime;
|
|
19909
|
-
const
|
|
19971
|
+
const turn = reported(userResponse);
|
|
19972
|
+
const model = turn.model || mainAgentModel || "unknown";
|
|
19910
19973
|
const componentConfig = userResponse.data?.component?.props?.config;
|
|
19911
19974
|
const queryMap = componentConfig?._queryMap || {};
|
|
19912
19975
|
const sqlStatements = Object.values(queryMap).filter((v) => typeof v === "string");
|
|
@@ -19938,7 +20001,8 @@ var get_user_request = async (data, components, sendMessage, anthropicApiKey, gr
|
|
|
19938
20001
|
sourcesUsed: sourcesUsed.length > 0 ? sourcesUsed : void 0,
|
|
19939
20002
|
sqlGenerated: sqlStatements.length > 0 ? sqlStatements.join(";\n") : void 0,
|
|
19940
20003
|
conversationId: savedConversationId,
|
|
19941
|
-
response: savedResponse
|
|
20004
|
+
response: savedResponse,
|
|
20005
|
+
usage: turn.usage
|
|
19942
20006
|
});
|
|
19943
20007
|
analyticsClient.track(event).catch(() => {
|
|
19944
20008
|
});
|
|
@@ -19961,7 +20025,7 @@ async function handleUserPromptRequest(data, components, sendMessage, anthropicA
|
|
|
19961
20025
|
id: `comp_${Math.random().toString(36).substring(2, 8)}`
|
|
19962
20026
|
}));
|
|
19963
20027
|
}
|
|
19964
|
-
const { trace: _trace, ...dataWithoutTrace } = response.data ?? {};
|
|
20028
|
+
const { trace: _trace, usage: _usage, model: _model, ...dataWithoutTrace } = response.data ?? {};
|
|
19965
20029
|
sendDataResponse4(
|
|
19966
20030
|
response.id || data.id,
|
|
19967
20031
|
{
|
|
@@ -24051,7 +24115,7 @@ var processDashCompRequest = async (data, components, _sendMessage, anthropicApi
|
|
|
24051
24115
|
const conversationHistory = dashboardId ? dashboardConversationHistory.getHistory(dashboardId, userId) : void 0;
|
|
24052
24116
|
const promptContext = `DASH_COMP[${reqType}]: ${prompt?.substring(0, 50)}${(prompt?.length || 0) > 50 ? "..." : ""}`;
|
|
24053
24117
|
llmUsageLogger.resetLogFile(promptContext);
|
|
24054
|
-
logger.clearFile();
|
|
24118
|
+
logger.clearFile(promptContext);
|
|
24055
24119
|
if (!prompt) {
|
|
24056
24120
|
errors.push("Prompt is required");
|
|
24057
24121
|
}
|
|
@@ -24582,6 +24646,9 @@ ${marker}
|
|
|
24582
24646
|
}
|
|
24583
24647
|
|
|
24584
24648
|
// src/codingAgent/drivers/pi.ts
|
|
24649
|
+
var crypto3 = __toESM(require("crypto"));
|
|
24650
|
+
var fs8 = __toESM(require("fs"));
|
|
24651
|
+
var path9 = __toESM(require("path"));
|
|
24585
24652
|
var dynamicImport2 = new Function("specifier", "return import(specifier)");
|
|
24586
24653
|
var piModulePromise = null;
|
|
24587
24654
|
function loadPi() {
|
|
@@ -24593,46 +24660,111 @@ function loadPi() {
|
|
|
24593
24660
|
}
|
|
24594
24661
|
return piModulePromise;
|
|
24595
24662
|
}
|
|
24596
|
-
var
|
|
24597
|
-
function
|
|
24598
|
-
if (!
|
|
24599
|
-
|
|
24600
|
-
|
|
24601
|
-
|
|
24663
|
+
var runtimePromise = null;
|
|
24664
|
+
function modelRuntime(pi) {
|
|
24665
|
+
if (!runtimePromise) {
|
|
24666
|
+
const agentDir = pi.getAgentDir();
|
|
24667
|
+
runtimePromise = pi.ModelRuntime.create({
|
|
24668
|
+
authPath: `${agentDir}/auth.json`,
|
|
24669
|
+
modelsStorePath: `${agentDir}/models-store.json`,
|
|
24670
|
+
allowModelNetwork: true
|
|
24671
|
+
}).catch((err) => {
|
|
24672
|
+
runtimePromise = null;
|
|
24673
|
+
throw err;
|
|
24602
24674
|
});
|
|
24603
24675
|
}
|
|
24604
|
-
return
|
|
24676
|
+
return runtimePromise;
|
|
24605
24677
|
}
|
|
24606
|
-
var DEFAULT_PROVIDER = "openai-codex";
|
|
24607
|
-
var DEFAULT_MODEL_ID = process.env.PI_AGENT_MODEL || "gpt-5.
|
|
24678
|
+
var DEFAULT_PROVIDER = process.env.PI_PROVIDER || "openai-codex";
|
|
24679
|
+
var DEFAULT_MODEL_ID = process.env.PI_MODEL || process.env.PI_AGENT_MODEL || "gpt-5.6-luna";
|
|
24608
24680
|
var KNOWN_UNUSABLE_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.3-codex-spark"]);
|
|
24609
|
-
async function resolveModel(
|
|
24610
|
-
const
|
|
24611
|
-
const
|
|
24612
|
-
const wanted =
|
|
24613
|
-
const
|
|
24614
|
-
|
|
24615
|
-
|
|
24616
|
-
|
|
24617
|
-
if (
|
|
24618
|
-
|
|
24619
|
-
|
|
24620
|
-
|
|
24681
|
+
async function resolveModel(runtime, spec) {
|
|
24682
|
+
const slash = spec?.indexOf("/") ?? -1;
|
|
24683
|
+
const provider = slash > 0 ? spec.slice(0, slash) : DEFAULT_PROVIDER;
|
|
24684
|
+
const wanted = slash > 0 ? spec.slice(slash + 1) : spec || DEFAULT_MODEL_ID;
|
|
24685
|
+
const available = await runtime.getAvailable(provider);
|
|
24686
|
+
const exact = available.find((m) => m.id === wanted);
|
|
24687
|
+
if (exact) return exact;
|
|
24688
|
+
const fallback = available.find((m) => !KNOWN_UNUSABLE_MODEL_IDS.has(m.id)) ?? available[0];
|
|
24689
|
+
if (fallback) {
|
|
24690
|
+
console.warn(`[pi] model ${provider}/${wanted} unavailable \u2014 using ${fallback.provider}/${fallback.id}`);
|
|
24691
|
+
return fallback;
|
|
24692
|
+
}
|
|
24693
|
+
throw new Error(`No model available from "${provider}". Authorise it in a terminal on this machine: run \`pi\`, then \`/login\`.`);
|
|
24694
|
+
}
|
|
24695
|
+
var SHELL_ENV_ALLOWLIST = [
|
|
24696
|
+
"PATH",
|
|
24697
|
+
"Path",
|
|
24698
|
+
"HOME",
|
|
24699
|
+
"USER",
|
|
24700
|
+
"USERNAME",
|
|
24701
|
+
"LOGNAME",
|
|
24702
|
+
"SHELL",
|
|
24703
|
+
"LANG",
|
|
24704
|
+
"LC_ALL",
|
|
24705
|
+
"TERM",
|
|
24706
|
+
"TMPDIR",
|
|
24707
|
+
"TEMP",
|
|
24708
|
+
"TMP",
|
|
24709
|
+
"NODE_EXTRA_CA_CERTS",
|
|
24710
|
+
// Windows: without these a shell cannot start programs.
|
|
24711
|
+
"SystemRoot",
|
|
24712
|
+
"SYSTEMROOT",
|
|
24713
|
+
"windir",
|
|
24714
|
+
"ComSpec",
|
|
24715
|
+
"COMSPEC",
|
|
24716
|
+
"PATHEXT",
|
|
24717
|
+
"USERPROFILE",
|
|
24718
|
+
"APPDATA",
|
|
24719
|
+
"LOCALAPPDATA",
|
|
24720
|
+
"HOMEDRIVE",
|
|
24721
|
+
"HOMEPATH",
|
|
24722
|
+
"ProgramFiles",
|
|
24723
|
+
"ProgramData",
|
|
24724
|
+
"MSYSTEM"
|
|
24725
|
+
];
|
|
24726
|
+
function shellEnv(run, inherit) {
|
|
24727
|
+
if (inherit) return { ...process.env, ...run };
|
|
24728
|
+
const env = {};
|
|
24729
|
+
for (const key of SHELL_ENV_ALLOWLIST) {
|
|
24730
|
+
if (process.env[key] !== void 0) env[key] = process.env[key];
|
|
24731
|
+
}
|
|
24732
|
+
return { ...env, ...run };
|
|
24733
|
+
}
|
|
24734
|
+
function bashTool(pi, cwd, env) {
|
|
24735
|
+
const local = pi.createLocalBashOperations();
|
|
24736
|
+
const definition = pi.createBashToolDefinition(cwd, {
|
|
24737
|
+
operations: {
|
|
24738
|
+
exec: (command, dir, options) => local.exec(command, dir, { ...options, timeout: void 0, env: env() })
|
|
24739
|
+
}
|
|
24740
|
+
});
|
|
24741
|
+
const parameters = definition.parameters;
|
|
24742
|
+
return {
|
|
24743
|
+
...definition,
|
|
24744
|
+
description: definition.description.replace(/\s*Optionally provide a timeout in seconds\./, ""),
|
|
24745
|
+
parameters: { ...parameters, properties: { command: parameters.properties.command }, required: ["command"] }
|
|
24746
|
+
};
|
|
24621
24747
|
}
|
|
24622
24748
|
function emptyUsage2() {
|
|
24623
24749
|
return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, toolCalls: 0 };
|
|
24624
24750
|
}
|
|
24625
24751
|
var BUFFER_CAP2 = 64e3;
|
|
24626
24752
|
var EVENT_CAP2 = 600;
|
|
24627
|
-
function
|
|
24628
|
-
|
|
24629
|
-
|
|
24630
|
-
|
|
24631
|
-
|
|
24632
|
-
|
|
24633
|
-
|
|
24634
|
-
|
|
24753
|
+
function textOf(content) {
|
|
24754
|
+
const blocks = content?.content ?? content;
|
|
24755
|
+
if (!Array.isArray(blocks)) return typeof content === "string" ? content : "";
|
|
24756
|
+
return blocks.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("").trim();
|
|
24757
|
+
}
|
|
24758
|
+
function assistantParts(message) {
|
|
24759
|
+
const record = message;
|
|
24760
|
+
if (record?.role !== "assistant" || !Array.isArray(record.content)) return { text: "", thinking: "" };
|
|
24761
|
+
let text = "";
|
|
24762
|
+
let thinking = "";
|
|
24763
|
+
for (const block of record.content) {
|
|
24764
|
+
if (block?.type === "text") text += String(block.text ?? "");
|
|
24765
|
+
else if (block?.type === "thinking" && !block.redacted) thinking += String(block.thinking ?? "");
|
|
24635
24766
|
}
|
|
24767
|
+
return { text, thinking };
|
|
24636
24768
|
}
|
|
24637
24769
|
function jsonPreview(v) {
|
|
24638
24770
|
if (v == null) return "";
|
|
@@ -24642,10 +24774,10 @@ function jsonPreview(v) {
|
|
|
24642
24774
|
return "";
|
|
24643
24775
|
}
|
|
24644
24776
|
}
|
|
24645
|
-
function
|
|
24646
|
-
|
|
24647
|
-
if (
|
|
24648
|
-
return
|
|
24777
|
+
function toolEvent(name, id, args) {
|
|
24778
|
+
if (name === "bash") return { kind: "command", id, command: String(args?.command ?? "") };
|
|
24779
|
+
if (name === "write" || name === "edit") return { kind: "file", id, text: String(args?.path ?? args?.file_path ?? "") };
|
|
24780
|
+
return { kind: "tool", id, tool: { name: name ?? "tool", args } };
|
|
24649
24781
|
}
|
|
24650
24782
|
var TRANSCRIPT_CAP = 400;
|
|
24651
24783
|
var sessionListeners = /* @__PURE__ */ new Set();
|
|
@@ -24663,9 +24795,6 @@ function publish(sessionKey, live, entry) {
|
|
|
24663
24795
|
for (const listener of sessionListeners) listener(sessionKey, entry);
|
|
24664
24796
|
}
|
|
24665
24797
|
var liveSessions = /* @__PURE__ */ new Map();
|
|
24666
|
-
function newSessionId() {
|
|
24667
|
-
return `pi_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
24668
|
-
}
|
|
24669
24798
|
var IDLE_TIMEOUT_MS = Number(process.env.PI_AGENT_IDLE_MS ?? 30 * 60 * 1e3);
|
|
24670
24799
|
var sweeper = null;
|
|
24671
24800
|
function startSweeper() {
|
|
@@ -24684,29 +24813,76 @@ function startSweeper() {
|
|
|
24684
24813
|
}, 6e4);
|
|
24685
24814
|
sweeper.unref?.();
|
|
24686
24815
|
}
|
|
24816
|
+
function readInstructions(opts) {
|
|
24817
|
+
if (!opts.instructionsFile) return void 0;
|
|
24818
|
+
const file = path9.resolve(opts.cwd, opts.instructionsFile);
|
|
24819
|
+
try {
|
|
24820
|
+
return { file, content: fs8.readFileSync(file, "utf-8") };
|
|
24821
|
+
} catch {
|
|
24822
|
+
return void 0;
|
|
24823
|
+
}
|
|
24824
|
+
}
|
|
24825
|
+
var hash = (s) => crypto3.createHash("sha256").update(s).digest("hex");
|
|
24687
24826
|
function createPiSession(opts) {
|
|
24688
24827
|
let running = false;
|
|
24689
|
-
let sessionKey = opts.resumeId
|
|
24690
|
-
async function
|
|
24691
|
-
const
|
|
24692
|
-
|
|
24693
|
-
const
|
|
24694
|
-
const
|
|
24695
|
-
const
|
|
24696
|
-
const
|
|
24828
|
+
let sessionKey = opts.resumeId;
|
|
24829
|
+
async function open(resumeFile) {
|
|
24830
|
+
const pi = await loadPi();
|
|
24831
|
+
const runtime = await modelRuntime(pi);
|
|
24832
|
+
const model = await resolveModel(runtime, opts.model);
|
|
24833
|
+
const agentDir = pi.getAgentDir();
|
|
24834
|
+
const instructions = readInstructions(opts);
|
|
24835
|
+
const resourceLoader = new pi.DefaultResourceLoader({
|
|
24836
|
+
cwd: opts.cwd,
|
|
24837
|
+
agentDir,
|
|
24838
|
+
// The workspace's own instructions, and only them — not whatever
|
|
24839
|
+
// AGENTS.md files the machine's user keeps in ~/.pi or a parent folder.
|
|
24840
|
+
agentsFilesOverride: (base) => instructions ? { agentsFiles: [{ path: instructions.file, content: instructions.content }] } : base
|
|
24841
|
+
});
|
|
24842
|
+
await resourceLoader.reload();
|
|
24843
|
+
const sessionManager = resumeFile && fs8.existsSync(resumeFile) ? pi.SessionManager.open(resumeFile) : pi.SessionManager.create(opts.cwd);
|
|
24844
|
+
const live = {
|
|
24845
|
+
running: false,
|
|
24846
|
+
transcript: [],
|
|
24847
|
+
lastActive: Date.now(),
|
|
24848
|
+
eventLog: [],
|
|
24849
|
+
buf: "",
|
|
24850
|
+
env: opts.env,
|
|
24851
|
+
inheritEnv: opts.inheritEnv,
|
|
24852
|
+
instructions: hash(instructions?.content ?? "")
|
|
24853
|
+
};
|
|
24854
|
+
const { session } = await pi.createAgentSession({
|
|
24697
24855
|
cwd: opts.cwd,
|
|
24698
|
-
|
|
24699
|
-
|
|
24700
|
-
modelRegistry,
|
|
24856
|
+
agentDir,
|
|
24857
|
+
modelRuntime: runtime,
|
|
24701
24858
|
model,
|
|
24702
|
-
tools: opts.tools
|
|
24859
|
+
...opts.tools ? { tools: opts.tools } : {},
|
|
24860
|
+
customTools: [bashTool(pi, opts.cwd, () => shellEnv(live.env, live.inheritEnv))],
|
|
24861
|
+
resourceLoader,
|
|
24862
|
+
sessionManager
|
|
24703
24863
|
});
|
|
24704
|
-
|
|
24705
|
-
const
|
|
24706
|
-
|
|
24864
|
+
live.session = session;
|
|
24865
|
+
const file = sessionManager.getSessionFile();
|
|
24866
|
+
if (!file) throw new Error("pi did not give this session a file, so it could not be resumed later.");
|
|
24867
|
+
sessionKey = file;
|
|
24868
|
+
liveSessions.set(file, live);
|
|
24707
24869
|
startSweeper();
|
|
24708
24870
|
return live;
|
|
24709
24871
|
}
|
|
24872
|
+
async function ensureSession() {
|
|
24873
|
+
const existing = sessionKey ? liveSessions.get(sessionKey) : void 0;
|
|
24874
|
+
if (existing) {
|
|
24875
|
+
if (existing.instructions !== hash(readInstructions(opts)?.content ?? "") && !existing.running) {
|
|
24876
|
+
existing.session.dispose();
|
|
24877
|
+
liveSessions.delete(sessionKey);
|
|
24878
|
+
return open(sessionKey);
|
|
24879
|
+
}
|
|
24880
|
+
existing.env = opts.env;
|
|
24881
|
+
existing.inheritEnv = opts.inheritEnv;
|
|
24882
|
+
return existing;
|
|
24883
|
+
}
|
|
24884
|
+
return open(sessionKey);
|
|
24885
|
+
}
|
|
24710
24886
|
async function runTurn(prompt, handlers, signal) {
|
|
24711
24887
|
const startedAt = Date.now();
|
|
24712
24888
|
const usage = emptyUsage2();
|
|
@@ -24738,29 +24914,70 @@ function createPiSession(opts) {
|
|
|
24738
24914
|
live.buf = (live.buf + chunk).slice(-BUFFER_CAP2);
|
|
24739
24915
|
handlers?.onOutput?.(chunk);
|
|
24740
24916
|
};
|
|
24917
|
+
const send = (event) => {
|
|
24918
|
+
live.eventLog.push(event);
|
|
24919
|
+
if (live.eventLog.length > EVENT_CAP2) live.eventLog.shift();
|
|
24920
|
+
handlers?.onEvent?.(event);
|
|
24921
|
+
publish(key, live, { at: Date.now(), kind: "activity", event });
|
|
24922
|
+
};
|
|
24923
|
+
let streamed = 0;
|
|
24924
|
+
let messageSeq = 0;
|
|
24925
|
+
const started = /* @__PURE__ */ new Map();
|
|
24741
24926
|
const unsubscribe = live.session.subscribe((ev) => {
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24927
|
+
switch (ev.type) {
|
|
24928
|
+
case "turn_start":
|
|
24929
|
+
send({ kind: "turn" });
|
|
24930
|
+
return;
|
|
24931
|
+
case "message_start":
|
|
24932
|
+
streamed = 0;
|
|
24933
|
+
return;
|
|
24934
|
+
case "message_update": {
|
|
24935
|
+
const { text } = assistantParts(ev.message);
|
|
24936
|
+
if (text.length > streamed) {
|
|
24937
|
+
const delta = text.slice(streamed);
|
|
24938
|
+
streamed = text.length;
|
|
24939
|
+
emit(delta);
|
|
24940
|
+
handlers?.onNarration?.(delta);
|
|
24941
|
+
}
|
|
24942
|
+
return;
|
|
24747
24943
|
}
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
24944
|
+
case "message_end": {
|
|
24945
|
+
const { text, thinking } = assistantParts(ev.message);
|
|
24946
|
+
const id = `msg_${++messageSeq}`;
|
|
24947
|
+
if (thinking.trim()) send({ kind: "reasoning", id: `${id}_r`, text: thinking.trim(), status: "completed", done: true });
|
|
24948
|
+
if (text.trim()) send({ kind: "message", id, text: text.trim(), status: "completed", done: true });
|
|
24949
|
+
streamed = 0;
|
|
24950
|
+
return;
|
|
24951
|
+
}
|
|
24952
|
+
case "tool_execution_start": {
|
|
24953
|
+
const event = { ...toolEvent(ev.toolName, ev.toolCallId, ev.args), status: "in_progress" };
|
|
24954
|
+
if (ev.toolCallId) started.set(ev.toolCallId, event);
|
|
24955
|
+
send(event);
|
|
24956
|
+
emit(`
|
|
24759
24957
|
\u2192 ${ev.toolName ?? "tool"} ${jsonPreview(ev.args)}
|
|
24760
24958
|
`);
|
|
24761
|
-
|
|
24959
|
+
return;
|
|
24960
|
+
}
|
|
24961
|
+
case "tool_execution_end": {
|
|
24962
|
+
usage.toolCalls++;
|
|
24963
|
+
const start = ev.toolCallId && started.get(ev.toolCallId) || toolEvent(ev.toolName, ev.toolCallId, void 0);
|
|
24964
|
+
send({ ...start, status: ev.isError ? "failed" : "completed", done: true, output: textOf(ev.result) });
|
|
24965
|
+
return;
|
|
24966
|
+
}
|
|
24967
|
+
default:
|
|
24968
|
+
return;
|
|
24762
24969
|
}
|
|
24763
24970
|
});
|
|
24971
|
+
let early = false;
|
|
24972
|
+
const poll = handlers?.doneWhen ? setInterval(async () => {
|
|
24973
|
+
try {
|
|
24974
|
+
if (!early && await handlers.doneWhen()) {
|
|
24975
|
+
early = true;
|
|
24976
|
+
void live.session.abort();
|
|
24977
|
+
}
|
|
24978
|
+
} catch {
|
|
24979
|
+
}
|
|
24980
|
+
}, 1e3) : null;
|
|
24764
24981
|
try {
|
|
24765
24982
|
if (signal?.aborted) {
|
|
24766
24983
|
status = "aborted";
|
|
@@ -24768,16 +24985,20 @@ function createPiSession(opts) {
|
|
|
24768
24985
|
}
|
|
24769
24986
|
await live.session.prompt(prompt);
|
|
24770
24987
|
const err = live.session.agent.state.errorMessage;
|
|
24771
|
-
if (
|
|
24772
|
-
|
|
24988
|
+
if (early) status = "early";
|
|
24989
|
+
else if (signal?.aborted) status = "aborted";
|
|
24990
|
+
else if (err) {
|
|
24991
|
+
status = "error";
|
|
24773
24992
|
error = err;
|
|
24774
|
-
} else if (signal?.aborted) {
|
|
24775
|
-
status = "aborted";
|
|
24776
24993
|
}
|
|
24777
24994
|
} catch (err) {
|
|
24778
|
-
status =
|
|
24779
|
-
|
|
24995
|
+
if (early) status = "early";
|
|
24996
|
+
else {
|
|
24997
|
+
status = signal?.aborted ? "aborted" : "error";
|
|
24998
|
+
error = err?.message ?? String(err);
|
|
24999
|
+
}
|
|
24780
25000
|
} finally {
|
|
25001
|
+
if (poll) clearInterval(poll);
|
|
24781
25002
|
unsubscribe();
|
|
24782
25003
|
signal?.removeEventListener("abort", onAbort);
|
|
24783
25004
|
live.running = false;
|
|
@@ -24818,8 +25039,8 @@ function createPiSession(opts) {
|
|
|
24818
25039
|
}
|
|
24819
25040
|
sessionKey = void 0;
|
|
24820
25041
|
},
|
|
24821
|
-
// Local-only, same as the codex driver: the live session stays in the
|
|
24822
|
-
//
|
|
25042
|
+
// Local-only, same as the codex driver: the live session stays in the map
|
|
25043
|
+
// so a later run with this sessionId resumes it.
|
|
24823
25044
|
dispose: () => {
|
|
24824
25045
|
}
|
|
24825
25046
|
};
|
|
@@ -24838,11 +25059,11 @@ function createCodingAgentSession(harness, opts) {
|
|
|
24838
25059
|
}
|
|
24839
25060
|
|
|
24840
25061
|
// src/codingAgent/registry.ts
|
|
24841
|
-
var
|
|
25062
|
+
var path10 = __toESM(require("path"));
|
|
24842
25063
|
var agents = /* @__PURE__ */ new Map();
|
|
24843
25064
|
var config = {
|
|
24844
|
-
promptRoot:
|
|
24845
|
-
runtimeDir:
|
|
25065
|
+
promptRoot: path10.join(process.cwd(), ".prompts", "coding-agents"),
|
|
25066
|
+
runtimeDir: path10.join(process.cwd(), ".coding-agent-runtime"),
|
|
24846
25067
|
harness: "codex",
|
|
24847
25068
|
keepRuns: 20,
|
|
24848
25069
|
keepThreads: 200
|
|
@@ -24881,13 +25102,13 @@ function getCodingAgentsConfig() {
|
|
|
24881
25102
|
return config;
|
|
24882
25103
|
}
|
|
24883
25104
|
function resolvePromptDir(def) {
|
|
24884
|
-
return def.promptDir ||
|
|
25105
|
+
return def.promptDir || path10.join(config.promptRoot, def.id);
|
|
24885
25106
|
}
|
|
24886
25107
|
|
|
24887
25108
|
// src/codingAgent/runner.ts
|
|
24888
|
-
var
|
|
25109
|
+
var fs9 = __toESM(require("fs"));
|
|
24889
25110
|
var fsp = __toESM(require("fs/promises"));
|
|
24890
|
-
var
|
|
25111
|
+
var path11 = __toESM(require("path"));
|
|
24891
25112
|
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
24892
25113
|
var inFlight = /* @__PURE__ */ new Set();
|
|
24893
25114
|
function slug(v) {
|
|
@@ -24897,11 +25118,11 @@ function resolveScope(def, opts) {
|
|
|
24897
25118
|
const scope = def.scope ?? (def.mode === "chat" ? "thread" : "project");
|
|
24898
25119
|
if (scope === "thread") {
|
|
24899
25120
|
const id = slug(opts.threadId || "default");
|
|
24900
|
-
return { key: `thread:${id}`, dirName:
|
|
25121
|
+
return { key: `thread:${id}`, dirName: path11.join("threads", id) };
|
|
24901
25122
|
}
|
|
24902
25123
|
if (scope === "user") {
|
|
24903
25124
|
const id = slug(opts.userId || "anonymous");
|
|
24904
|
-
return { key: `user:${id}`, dirName:
|
|
25125
|
+
return { key: `user:${id}`, dirName: path11.join("users", id) };
|
|
24905
25126
|
}
|
|
24906
25127
|
return { key: "project", dirName: "_project" };
|
|
24907
25128
|
}
|
|
@@ -24910,30 +25131,30 @@ function makeRunId() {
|
|
|
24910
25131
|
return `${t}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24911
25132
|
}
|
|
24912
25133
|
function statePath(scopeDir) {
|
|
24913
|
-
return
|
|
25134
|
+
return path11.join(scopeDir, "_state.json");
|
|
24914
25135
|
}
|
|
24915
25136
|
function loadState(scopeDir) {
|
|
24916
25137
|
try {
|
|
24917
|
-
return JSON.parse(
|
|
25138
|
+
return JSON.parse(fs9.readFileSync(statePath(scopeDir), "utf-8"));
|
|
24918
25139
|
} catch {
|
|
24919
25140
|
return { turns: 0, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
24920
25141
|
}
|
|
24921
25142
|
}
|
|
24922
25143
|
function saveState(scopeDir, state) {
|
|
24923
25144
|
try {
|
|
24924
|
-
|
|
24925
|
-
|
|
25145
|
+
fs9.mkdirSync(scopeDir, { recursive: true });
|
|
25146
|
+
fs9.writeFileSync(statePath(scopeDir), JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
24926
25147
|
} catch (err) {
|
|
24927
25148
|
logger.warn(`[CodingAgent] failed to persist session state: ${err?.message ?? err}`);
|
|
24928
25149
|
}
|
|
24929
25150
|
}
|
|
24930
25151
|
async function clearTransient(dir) {
|
|
24931
|
-
await fsp.rm(
|
|
25152
|
+
await fsp.rm(path11.join(dir, "input"), { recursive: true, force: true });
|
|
24932
25153
|
}
|
|
24933
25154
|
function createWorkspace(dir, runId) {
|
|
24934
|
-
const abs = (rel) =>
|
|
25155
|
+
const abs = (rel) => path11.join(dir, rel);
|
|
24935
25156
|
const ensureDir = async (rel) => {
|
|
24936
|
-
await fsp.mkdir(
|
|
25157
|
+
await fsp.mkdir(path11.dirname(abs(rel)), { recursive: true });
|
|
24937
25158
|
};
|
|
24938
25159
|
return {
|
|
24939
25160
|
dir,
|
|
@@ -24962,7 +25183,7 @@ function createWorkspace(dir, runId) {
|
|
|
24962
25183
|
}
|
|
24963
25184
|
var INSTRUCTIONS_FILE = "system.md";
|
|
24964
25185
|
async function installInstructions(def, ws) {
|
|
24965
|
-
const src =
|
|
25186
|
+
const src = path11.join(resolvePromptDir(def), "system.md");
|
|
24966
25187
|
try {
|
|
24967
25188
|
await ws.write(INSTRUCTIONS_FILE, await fsp.readFile(src, "utf-8"));
|
|
24968
25189
|
} catch {
|
|
@@ -24976,7 +25197,7 @@ async function pruneThreads(agentId, threadsDir, keep) {
|
|
|
24976
25197
|
const dirs = (await fsp.readdir(threadsDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
24977
25198
|
if (dirs.length <= keep) return;
|
|
24978
25199
|
const aged = await Promise.all(dirs.map(async (e) => {
|
|
24979
|
-
const dir =
|
|
25200
|
+
const dir = path11.join(threadsDir, e.name);
|
|
24980
25201
|
const stat2 = await fsp.stat(statePath(dir)).catch(() => fsp.stat(dir));
|
|
24981
25202
|
return { name: e.name, dir, used: stat2.mtimeMs };
|
|
24982
25203
|
}));
|
|
@@ -24996,7 +25217,7 @@ async function pruneRuns(agentDir, keep) {
|
|
|
24996
25217
|
try {
|
|
24997
25218
|
const entries = (await fsp.readdir(agentDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
24998
25219
|
for (const name of entries.slice(0, Math.max(0, entries.length - keep))) {
|
|
24999
|
-
await fsp.rm(
|
|
25220
|
+
await fsp.rm(path11.join(agentDir, name), { recursive: true, force: true });
|
|
25000
25221
|
}
|
|
25001
25222
|
} catch {
|
|
25002
25223
|
}
|
|
@@ -25011,17 +25232,17 @@ async function runCodingAgent(agentId, options = {}) {
|
|
|
25011
25232
|
}
|
|
25012
25233
|
const cfg = getCodingAgentsConfig();
|
|
25013
25234
|
const runId = makeRunId();
|
|
25014
|
-
const agentDir =
|
|
25235
|
+
const agentDir = path11.join(cfg.runtimeDir, def.id);
|
|
25015
25236
|
const policy = def.session ?? {};
|
|
25016
25237
|
const persistentWorkspace = policy.workspace === "persistent";
|
|
25017
25238
|
const persistentSession = policy.scope === "persistent";
|
|
25018
25239
|
const runAsJob = !(options.chat === true && def.mode === "task");
|
|
25019
25240
|
const { key: scopeKey, dirName } = resolveScope(def, { userId: options.userId, threadId: options.threadId });
|
|
25020
|
-
const scopeDir =
|
|
25021
|
-
const dir = options.workspaceDir || (persistentWorkspace ? scopeDir :
|
|
25022
|
-
|
|
25241
|
+
const scopeDir = path11.join(agentDir, dirName);
|
|
25242
|
+
const dir = options.workspaceDir || (persistentWorkspace ? scopeDir : path11.join(scopeDir, "runs", runId));
|
|
25243
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
25023
25244
|
if (persistentWorkspace && !options.workspaceDir) {
|
|
25024
|
-
|
|
25245
|
+
fs9.mkdirSync(path11.join(dir, "memory"), { recursive: true });
|
|
25025
25246
|
if (runAsJob) await clearTransient(dir);
|
|
25026
25247
|
}
|
|
25027
25248
|
const lockKey = persistentWorkspace || persistentSession ? `${def.id}:${scopeKey}` : `dir:${dir}`;
|
|
@@ -25122,7 +25343,7 @@ async function runCodingAgent(agentId, options = {}) {
|
|
|
25122
25343
|
onUsage: ctx.onUsage,
|
|
25123
25344
|
// Opt-in only: see stopOnDeliverable. Letting the turn end naturally
|
|
25124
25345
|
// is what makes usage reporting work at all.
|
|
25125
|
-
...outputPath && def.stopOnDeliverable ? { doneWhen: () =>
|
|
25346
|
+
...outputPath && def.stopOnDeliverable ? { doneWhen: () => fs9.existsSync(outputPath) } : {}
|
|
25126
25347
|
},
|
|
25127
25348
|
timeout.signal
|
|
25128
25349
|
);
|
|
@@ -25226,9 +25447,9 @@ Fix ONLY that, and rewrite the same file at the same path. Do not re-run any que
|
|
|
25226
25447
|
logger.info(
|
|
25227
25448
|
`[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}"` : "")
|
|
25228
25449
|
);
|
|
25229
|
-
if (!options.workspaceDir && !persistentWorkspace) await pruneRuns(
|
|
25230
|
-
if (!options.workspaceDir && dirName.startsWith(`threads${
|
|
25231
|
-
await pruneThreads(def.id,
|
|
25450
|
+
if (!options.workspaceDir && !persistentWorkspace) await pruneRuns(path11.join(scopeDir, "runs"), cfg.keepRuns);
|
|
25451
|
+
if (!options.workspaceDir && dirName.startsWith(`threads${path11.sep}`)) {
|
|
25452
|
+
await pruneThreads(def.id, path11.join(agentDir, "threads"), cfg.keepThreads);
|
|
25232
25453
|
}
|
|
25233
25454
|
}
|
|
25234
25455
|
}
|
|
@@ -25288,14 +25509,14 @@ ${assertReadOnlySql.toString().replace("export function", "function")}
|
|
|
25288
25509
|
`;
|
|
25289
25510
|
|
|
25290
25511
|
// src/codingAgent/tools/shims.ts
|
|
25291
|
-
var
|
|
25292
|
-
var
|
|
25293
|
-
var
|
|
25512
|
+
var fs10 = __toESM(require("fs"));
|
|
25513
|
+
var path12 = __toESM(require("path"));
|
|
25514
|
+
var resolve3 = (target, name) => typeof target === "string" ? path12.join(target, name) : target.pathTo(name);
|
|
25294
25515
|
function writeShims(defs, target) {
|
|
25295
25516
|
for (const def of defs) {
|
|
25296
|
-
const file =
|
|
25297
|
-
|
|
25298
|
-
|
|
25517
|
+
const file = resolve3(target, def.name);
|
|
25518
|
+
fs10.writeFileSync(file, def.body, "utf-8");
|
|
25519
|
+
fs10.chmodSync(file, 493);
|
|
25299
25520
|
}
|
|
25300
25521
|
return defs.map((def) => ({ name: `./${def.name}`, summary: def.summary }));
|
|
25301
25522
|
}
|
|
@@ -25554,17 +25775,17 @@ console.log(${JSON.stringify(
|
|
|
25554
25775
|
}
|
|
25555
25776
|
|
|
25556
25777
|
// src/codingAgent/triggers.ts
|
|
25557
|
-
var
|
|
25558
|
-
var
|
|
25778
|
+
var fs11 = __toESM(require("fs"));
|
|
25779
|
+
var path13 = __toESM(require("path"));
|
|
25559
25780
|
var EVENT_BURST_CAP = 200;
|
|
25560
25781
|
var capEvents = (events) => events.length > EVENT_BURST_CAP ? events.slice(-EVENT_BURST_CAP) : events;
|
|
25561
25782
|
function statePath2(agentId) {
|
|
25562
|
-
return
|
|
25783
|
+
return path13.join(getCodingAgentsConfig().runtimeDir, agentId, "_triggers.json");
|
|
25563
25784
|
}
|
|
25564
25785
|
function loadTriggerState(agentId) {
|
|
25565
25786
|
let raw;
|
|
25566
25787
|
try {
|
|
25567
|
-
raw = JSON.parse(
|
|
25788
|
+
raw = JSON.parse(fs11.readFileSync(statePath2(agentId), "utf-8"));
|
|
25568
25789
|
} catch {
|
|
25569
25790
|
return { counts: {}, pending: {} };
|
|
25570
25791
|
}
|
|
@@ -25584,8 +25805,8 @@ function loadTriggerState(agentId) {
|
|
|
25584
25805
|
function saveTriggerState(agentId, state) {
|
|
25585
25806
|
try {
|
|
25586
25807
|
const p = statePath2(agentId);
|
|
25587
|
-
|
|
25588
|
-
|
|
25808
|
+
fs11.mkdirSync(path13.dirname(p), { recursive: true });
|
|
25809
|
+
fs11.writeFileSync(p, JSON.stringify(state, null, 2));
|
|
25589
25810
|
} catch (err) {
|
|
25590
25811
|
logger.warn(`[CodingAgent:${agentId}] failed to persist trigger state: ${err?.message ?? err}`);
|
|
25591
25812
|
}
|
|
@@ -25721,13 +25942,13 @@ function describeTriggers() {
|
|
|
25721
25942
|
}
|
|
25722
25943
|
|
|
25723
25944
|
// src/codingAgent/collection.ts
|
|
25724
|
-
var
|
|
25725
|
-
var
|
|
25945
|
+
var fs12 = __toESM(require("fs"));
|
|
25946
|
+
var path14 = __toESM(require("path"));
|
|
25726
25947
|
function isChattable(agent) {
|
|
25727
25948
|
return agent.mode !== "task";
|
|
25728
25949
|
}
|
|
25729
25950
|
function registerCodingAgentCollection(sdk, config2 = {}) {
|
|
25730
|
-
const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR ||
|
|
25951
|
+
const runtimeDir = config2.runtimeDir || process.env.CODING_AGENT_RUNTIME_DIR || path14.join(process.cwd(), ".coding-agent-runtime");
|
|
25731
25952
|
const defaultHarness = config2.harness || process.env.CODING_AGENT_HARNESS || "codex";
|
|
25732
25953
|
const sessions = /* @__PURE__ */ new Map();
|
|
25733
25954
|
const running = /* @__PURE__ */ new Set();
|
|
@@ -25787,8 +26008,8 @@ function registerCodingAgentCollection(sdk, config2 = {}) {
|
|
|
25787
26008
|
...record.status === "skipped" ? { error: `${def.title} is in the middle of a run \u2014 try again in a moment.` } : record.error ? { error: record.error } : {}
|
|
25788
26009
|
};
|
|
25789
26010
|
}
|
|
25790
|
-
const cwd =
|
|
25791
|
-
|
|
26011
|
+
const cwd = path14.join(runtimeDir, "threads", threadId);
|
|
26012
|
+
fs12.mkdirSync(cwd, { recursive: true });
|
|
25792
26013
|
let session = sessions.get(threadId);
|
|
25793
26014
|
if (!session) {
|
|
25794
26015
|
session = createCodingAgentSession(harness || defaultHarness, {
|
|
@@ -27068,7 +27289,7 @@ function getLLMProviders() {
|
|
|
27068
27289
|
|
|
27069
27290
|
// src/auth/user-manager.ts
|
|
27070
27291
|
var import_fs6 = __toESM(require("fs"));
|
|
27071
|
-
var
|
|
27292
|
+
var import_path6 = __toESM(require("path"));
|
|
27072
27293
|
var import_os = __toESM(require("os"));
|
|
27073
27294
|
var UserManager = class {
|
|
27074
27295
|
/**
|
|
@@ -27081,7 +27302,7 @@ var UserManager = class {
|
|
|
27081
27302
|
this.hasChanged = false;
|
|
27082
27303
|
this.syncInterval = null;
|
|
27083
27304
|
this.isInitialized = false;
|
|
27084
|
-
this.filePath =
|
|
27305
|
+
this.filePath = import_path6.default.join(import_os.default.homedir(), ".superatom", "projects", projectId, "users.json");
|
|
27085
27306
|
this.syncIntervalMs = syncIntervalMs;
|
|
27086
27307
|
}
|
|
27087
27308
|
/**
|
|
@@ -27106,7 +27327,7 @@ var UserManager = class {
|
|
|
27106
27327
|
*/
|
|
27107
27328
|
async loadUsersFromFile() {
|
|
27108
27329
|
try {
|
|
27109
|
-
const dir =
|
|
27330
|
+
const dir = import_path6.default.dirname(this.filePath);
|
|
27110
27331
|
if (!import_fs6.default.existsSync(dir)) {
|
|
27111
27332
|
logger.info(`Creating directory structure: ${dir}`);
|
|
27112
27333
|
import_fs6.default.mkdirSync(dir, { recursive: true });
|
|
@@ -27138,7 +27359,7 @@ var UserManager = class {
|
|
|
27138
27359
|
return;
|
|
27139
27360
|
}
|
|
27140
27361
|
try {
|
|
27141
|
-
const dir =
|
|
27362
|
+
const dir = import_path6.default.dirname(this.filePath);
|
|
27142
27363
|
if (!import_fs6.default.existsSync(dir)) {
|
|
27143
27364
|
import_fs6.default.mkdirSync(dir, { recursive: true });
|
|
27144
27365
|
}
|
|
@@ -27366,7 +27587,7 @@ var UserManager = class {
|
|
|
27366
27587
|
|
|
27367
27588
|
// src/dashboards/dashboard-manager.ts
|
|
27368
27589
|
var import_fs7 = __toESM(require("fs"));
|
|
27369
|
-
var
|
|
27590
|
+
var import_path7 = __toESM(require("path"));
|
|
27370
27591
|
var import_os2 = __toESM(require("os"));
|
|
27371
27592
|
var DashboardManager = class {
|
|
27372
27593
|
/**
|
|
@@ -27375,7 +27596,7 @@ var DashboardManager = class {
|
|
|
27375
27596
|
*/
|
|
27376
27597
|
constructor(projectId = "snowflake-dataset") {
|
|
27377
27598
|
this.projectId = projectId;
|
|
27378
|
-
this.dashboardsBasePath =
|
|
27599
|
+
this.dashboardsBasePath = import_path7.default.join(
|
|
27379
27600
|
import_os2.default.homedir(),
|
|
27380
27601
|
".superatom",
|
|
27381
27602
|
"projects",
|
|
@@ -27389,7 +27610,7 @@ var DashboardManager = class {
|
|
|
27389
27610
|
* @returns Full path to dashboard data.json file
|
|
27390
27611
|
*/
|
|
27391
27612
|
getDashboardPath(dashboardId) {
|
|
27392
|
-
return
|
|
27613
|
+
return import_path7.default.join(this.dashboardsBasePath, dashboardId, "data.json");
|
|
27393
27614
|
}
|
|
27394
27615
|
/**
|
|
27395
27616
|
* Create a new dashboard
|
|
@@ -27399,7 +27620,7 @@ var DashboardManager = class {
|
|
|
27399
27620
|
*/
|
|
27400
27621
|
createDashboard(dashboardId, dashboard) {
|
|
27401
27622
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27402
|
-
const dashboardDir =
|
|
27623
|
+
const dashboardDir = import_path7.default.dirname(dashboardPath);
|
|
27403
27624
|
if (import_fs7.default.existsSync(dashboardPath)) {
|
|
27404
27625
|
throw new Error(`Dashboard '${dashboardId}' already exists`);
|
|
27405
27626
|
}
|
|
@@ -27487,7 +27708,7 @@ var DashboardManager = class {
|
|
|
27487
27708
|
*/
|
|
27488
27709
|
deleteDashboard(dashboardId) {
|
|
27489
27710
|
const dashboardPath = this.getDashboardPath(dashboardId);
|
|
27490
|
-
const dashboardDir =
|
|
27711
|
+
const dashboardDir = import_path7.default.dirname(dashboardPath);
|
|
27491
27712
|
if (!import_fs7.default.existsSync(dashboardPath)) {
|
|
27492
27713
|
logger.warn(`Dashboard not found for deletion: ${dashboardId}`);
|
|
27493
27714
|
return false;
|
|
@@ -27533,7 +27754,7 @@ var DashboardManager = class {
|
|
|
27533
27754
|
|
|
27534
27755
|
// src/reports/report-manager.ts
|
|
27535
27756
|
var import_fs8 = __toESM(require("fs"));
|
|
27536
|
-
var
|
|
27757
|
+
var import_path8 = __toESM(require("path"));
|
|
27537
27758
|
var import_os3 = __toESM(require("os"));
|
|
27538
27759
|
var ReportManager = class {
|
|
27539
27760
|
/**
|
|
@@ -27542,7 +27763,7 @@ var ReportManager = class {
|
|
|
27542
27763
|
*/
|
|
27543
27764
|
constructor(projectId = "snowflake-dataset") {
|
|
27544
27765
|
this.projectId = projectId;
|
|
27545
|
-
this.reportsBasePath =
|
|
27766
|
+
this.reportsBasePath = import_path8.default.join(
|
|
27546
27767
|
import_os3.default.homedir(),
|
|
27547
27768
|
".superatom",
|
|
27548
27769
|
"projects",
|
|
@@ -27556,7 +27777,7 @@ var ReportManager = class {
|
|
|
27556
27777
|
* @returns Full path to report data.json file
|
|
27557
27778
|
*/
|
|
27558
27779
|
getReportPath(reportId) {
|
|
27559
|
-
return
|
|
27780
|
+
return import_path8.default.join(this.reportsBasePath, reportId, "data.json");
|
|
27560
27781
|
}
|
|
27561
27782
|
/**
|
|
27562
27783
|
* Create a new report
|
|
@@ -27566,7 +27787,7 @@ var ReportManager = class {
|
|
|
27566
27787
|
*/
|
|
27567
27788
|
createReport(reportId, report) {
|
|
27568
27789
|
const reportPath = this.getReportPath(reportId);
|
|
27569
|
-
const reportDir =
|
|
27790
|
+
const reportDir = import_path8.default.dirname(reportPath);
|
|
27570
27791
|
if (import_fs8.default.existsSync(reportPath)) {
|
|
27571
27792
|
throw new Error(`Report '${reportId}' already exists`);
|
|
27572
27793
|
}
|
|
@@ -27654,7 +27875,7 @@ var ReportManager = class {
|
|
|
27654
27875
|
*/
|
|
27655
27876
|
deleteReport(reportId) {
|
|
27656
27877
|
const reportPath = this.getReportPath(reportId);
|
|
27657
|
-
const reportDir =
|
|
27878
|
+
const reportDir = import_path8.default.dirname(reportPath);
|
|
27658
27879
|
if (!import_fs8.default.existsSync(reportPath)) {
|
|
27659
27880
|
logger.warn(`Report not found for deletion: ${reportId}`);
|
|
27660
27881
|
return false;
|
|
@@ -28183,7 +28404,7 @@ var CleanupService = class _CleanupService {
|
|
|
28183
28404
|
};
|
|
28184
28405
|
|
|
28185
28406
|
// src/dashboardAgent/collection.ts
|
|
28186
|
-
var
|
|
28407
|
+
var import_path9 = __toESM(require("path"));
|
|
28187
28408
|
var import_fs9 = __toESM(require("fs"));
|
|
28188
28409
|
|
|
28189
28410
|
// src/dashboardAgent/tools.ts
|
|
@@ -28396,12 +28617,16 @@ function loadPi2() {
|
|
|
28396
28617
|
if (!piModulePromise2) piModulePromise2 = dynamicImport3("@earendil-works/pi-coding-agent");
|
|
28397
28618
|
return piModulePromise2;
|
|
28398
28619
|
}
|
|
28399
|
-
var
|
|
28400
|
-
function
|
|
28401
|
-
if (!
|
|
28402
|
-
|
|
28403
|
-
const
|
|
28404
|
-
const modelRegistry =
|
|
28620
|
+
var authAndRegistry = null;
|
|
28621
|
+
function getAuthAndRegistry() {
|
|
28622
|
+
if (!authAndRegistry) {
|
|
28623
|
+
authAndRegistry = loadPi2().then(async ({ ModelRuntime, getAgentDir }) => {
|
|
28624
|
+
const agentDir = getAgentDir();
|
|
28625
|
+
const modelRegistry = await ModelRuntime.create({
|
|
28626
|
+
authPath: `${agentDir}/auth.json`,
|
|
28627
|
+
modelsStorePath: `${agentDir}/models-store.json`,
|
|
28628
|
+
allowModelNetwork: true
|
|
28629
|
+
});
|
|
28405
28630
|
const proxyRoot = process.env.SUPERATOM_LLM_PROXY_URL;
|
|
28406
28631
|
if (proxyRoot) {
|
|
28407
28632
|
modelRegistry.registerProvider("openrouter", {
|
|
@@ -28413,10 +28638,10 @@ function getAuthAndRegistry2() {
|
|
|
28413
28638
|
'[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.'
|
|
28414
28639
|
);
|
|
28415
28640
|
}
|
|
28416
|
-
return {
|
|
28641
|
+
return { modelRuntime: modelRegistry };
|
|
28417
28642
|
});
|
|
28418
28643
|
}
|
|
28419
|
-
return
|
|
28644
|
+
return authAndRegistry;
|
|
28420
28645
|
}
|
|
28421
28646
|
async function preResolveSchema(sdk, tool, intent, projectId) {
|
|
28422
28647
|
const tier = tool.schemaTier;
|
|
@@ -28512,12 +28737,12 @@ function textDelta(event) {
|
|
|
28512
28737
|
}
|
|
28513
28738
|
var runningDashboards = /* @__PURE__ */ new Set();
|
|
28514
28739
|
function registerDashboardAgentCollection(sdk, config2 = {}) {
|
|
28515
|
-
const piCwd = config2.cwd || process.env.PI_AGENT_DIR ||
|
|
28516
|
-
const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR ||
|
|
28740
|
+
const piCwd = config2.cwd || process.env.PI_AGENT_DIR || import_path9.default.join(process.cwd(), ".prompts", "dashboard-agent");
|
|
28741
|
+
const runtimeDir = config2.runtimeDir || process.env.PI_AGENT_RUNTIME_DIR || import_path9.default.join(process.cwd(), ".pi-dashboard-agent-runtime");
|
|
28517
28742
|
const freshSession = config2.freshSession ?? process.env.PI_AGENT_FRESH_SESSION !== "false";
|
|
28518
|
-
const SESSIONS_FILE =
|
|
28519
|
-
const DRAFTS_DIR =
|
|
28520
|
-
const DASHBOARD_LOG_FILE =
|
|
28743
|
+
const SESSIONS_FILE = import_path9.default.join(runtimeDir, ".pi-dashboard-sessions.json");
|
|
28744
|
+
const DRAFTS_DIR = import_path9.default.join(runtimeDir, "drafts");
|
|
28745
|
+
const DASHBOARD_LOG_FILE = import_path9.default.join(runtimeDir, "dashboard.log");
|
|
28521
28746
|
function appendLog(text) {
|
|
28522
28747
|
try {
|
|
28523
28748
|
import_fs9.default.mkdirSync(runtimeDir, { recursive: true });
|
|
@@ -28541,7 +28766,7 @@ ${fullPrompt}
|
|
|
28541
28766
|
}
|
|
28542
28767
|
function readDraft(dashboardId) {
|
|
28543
28768
|
try {
|
|
28544
|
-
const raw = import_fs9.default.readFileSync(
|
|
28769
|
+
const raw = import_fs9.default.readFileSync(import_path9.default.join(DRAFTS_DIR, `${dashboardId}.json`), "utf-8");
|
|
28545
28770
|
return JSON.parse(raw);
|
|
28546
28771
|
} catch (err) {
|
|
28547
28772
|
console.warn(`[DashboardAgent] Failed to read draft for ${dashboardId}: ${err?.message ?? err}`);
|
|
@@ -28630,14 +28855,14 @@ ${fullPrompt}
|
|
|
28630
28855
|
}
|
|
28631
28856
|
try {
|
|
28632
28857
|
const { createAgentSession, SessionManager } = await loadPi2();
|
|
28633
|
-
const {
|
|
28858
|
+
const { modelRuntime: modelRuntime2 } = await getAuthAndRegistry();
|
|
28634
28859
|
let model;
|
|
28635
28860
|
const explicitProvider = config2.provider || process.env.PI_AGENT_PROVIDER;
|
|
28636
28861
|
const explicitModel = config2.model || process.env.PI_AGENT_MODEL;
|
|
28637
28862
|
if (explicitProvider || explicitModel) {
|
|
28638
28863
|
const providerName = explicitProvider || "openrouter";
|
|
28639
28864
|
const modelId = explicitModel || "anthropic/claude-sonnet-4.5";
|
|
28640
|
-
model =
|
|
28865
|
+
model = modelRuntime2.getModel(providerName, modelId);
|
|
28641
28866
|
if (!model) {
|
|
28642
28867
|
return { success: false, error: `Model not found: ${providerName}/${modelId}. Check the configured provider/model and that its API key is configured.` };
|
|
28643
28868
|
}
|
|
@@ -28650,8 +28875,8 @@ ${fullPrompt}
|
|
|
28650
28875
|
{ provider: "groq", modelId: "llama-3.3-70b-versatile" }
|
|
28651
28876
|
];
|
|
28652
28877
|
for (const candidate of FALLBACK_MODELS) {
|
|
28653
|
-
const found =
|
|
28654
|
-
if (found &&
|
|
28878
|
+
const found = modelRuntime2.getModel(candidate.provider, candidate.modelId);
|
|
28879
|
+
if (found && modelRuntime2.hasConfiguredAuth(found.provider)) {
|
|
28655
28880
|
model = found;
|
|
28656
28881
|
break;
|
|
28657
28882
|
}
|
|
@@ -28736,7 +28961,7 @@ ${turn.responseText ?? ""}`).join("\n\n");
|
|
|
28736
28961
|
console.warn(`[DashboardAgent] Failed to fetch previous responses for ${dashboardId}: ${err?.message ?? err}`);
|
|
28737
28962
|
}
|
|
28738
28963
|
}
|
|
28739
|
-
const draftPath =
|
|
28964
|
+
const draftPath = import_path9.default.join(DRAFTS_DIR, `${dashboardId}.json`);
|
|
28740
28965
|
const fullPrompt = [
|
|
28741
28966
|
`Dashboard ID: ${dashboardId}.`,
|
|
28742
28967
|
projectId ? `Project ID: ${projectId}.` : null,
|
|
@@ -28785,8 +29010,7 @@ ${previousResponseText}` : null,
|
|
|
28785
29010
|
const created = await createAgentSession({
|
|
28786
29011
|
cwd: piCwd,
|
|
28787
29012
|
sessionManager,
|
|
28788
|
-
|
|
28789
|
-
modelRegistry,
|
|
29013
|
+
modelRuntime: modelRuntime2,
|
|
28790
29014
|
model,
|
|
28791
29015
|
customTools
|
|
28792
29016
|
});
|
|
@@ -28796,8 +29020,7 @@ ${previousResponseText}` : null,
|
|
|
28796
29020
|
const created = await createAgentSession({
|
|
28797
29021
|
cwd: piCwd,
|
|
28798
29022
|
sessionManager: SessionManager.create(piCwd, runtimeDir),
|
|
28799
|
-
|
|
28800
|
-
modelRegistry,
|
|
29023
|
+
modelRuntime: modelRuntime2,
|
|
28801
29024
|
model,
|
|
28802
29025
|
customTools
|
|
28803
29026
|
});
|
|
@@ -29069,6 +29292,9 @@ var SuperatomSDK = class {
|
|
|
29069
29292
|
if (config2.queryCacheTTL !== void 0) {
|
|
29070
29293
|
queryCache.setTTL(config2.queryCacheTTL);
|
|
29071
29294
|
}
|
|
29295
|
+
if (config2.queryResultCache === false) {
|
|
29296
|
+
queryCache.disableResultCache();
|
|
29297
|
+
}
|
|
29072
29298
|
if (config2.dashboardHistoryTTL !== void 0) {
|
|
29073
29299
|
dashboardConversationHistory.setTTL(config2.dashboardHistoryTTL);
|
|
29074
29300
|
}
|
|
@@ -29153,7 +29379,7 @@ var SuperatomSDK = class {
|
|
|
29153
29379
|
logger.info("Already connected to WebSocket");
|
|
29154
29380
|
return Promise.resolve();
|
|
29155
29381
|
}
|
|
29156
|
-
return new Promise((
|
|
29382
|
+
return new Promise((resolve4, reject) => {
|
|
29157
29383
|
try {
|
|
29158
29384
|
const url = new URL(this.url);
|
|
29159
29385
|
if (this.apiKey) {
|
|
@@ -29169,7 +29395,7 @@ var SuperatomSDK = class {
|
|
|
29169
29395
|
logger.info("WebSocket connected successfully");
|
|
29170
29396
|
this.startHeartbeat();
|
|
29171
29397
|
this.flushPendingOutbox();
|
|
29172
|
-
|
|
29398
|
+
resolve4();
|
|
29173
29399
|
});
|
|
29174
29400
|
this.ws.addEventListener("message", (event) => {
|
|
29175
29401
|
this.handleMessage(event.data);
|