@probelabs/probe 0.6.0-rc301 → 0.6.0-rc303
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/bin/binaries/{probe-v0.6.0-rc301-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc303-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc301-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc303-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc301-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc303-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc301-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc303-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc301-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc303-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/ProbeAgent.js +56 -12
- package/build/agent/bashCommandUtils.js +4 -4
- package/build/agent/bashPermissions.js +34 -0
- package/build/tools/bash.js +2 -0
- package/build/tools/vercel.js +101 -3
- package/cjs/agent/ProbeAgent.cjs +271 -35
- package/cjs/index.cjs +271 -35
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +56 -12
- package/src/agent/bashCommandUtils.js +4 -4
- package/src/agent/bashPermissions.js +34 -0
- package/src/tools/bash.js +2 -0
- package/src/tools/vercel.js +101 -3
package/cjs/index.cjs
CHANGED
|
@@ -50610,6 +50610,10 @@ var init_parser4 = __esm({
|
|
|
50610
50610
|
});
|
|
50611
50611
|
|
|
50612
50612
|
// node_modules/@probelabs/maid/out/diagrams/sequence/semantics.js
|
|
50613
|
+
function isEscapedEntitySemicolon(image, semicolonIdx) {
|
|
50614
|
+
const uptoSemicolon = image.slice(0, semicolonIdx + 1);
|
|
50615
|
+
return /(?:#\d+|&#\d+|&[A-Za-z][A-Za-z0-9]+);$/.test(uptoSemicolon);
|
|
50616
|
+
}
|
|
50613
50617
|
function analyzeSequence(_cst, _tokens) {
|
|
50614
50618
|
const ctx = { tokens: _tokens };
|
|
50615
50619
|
const v = new SequenceSemanticsVisitor(ctx);
|
|
@@ -50710,6 +50714,35 @@ function analyzeSequence(_cst, _tokens) {
|
|
|
50710
50714
|
if (arrowIdx > 0) {
|
|
50711
50715
|
const from = grabActorRef(arr, 0);
|
|
50712
50716
|
const to = grabActorRef(arr, arrowIdx + 1);
|
|
50717
|
+
const colonIdx = arr.findIndex((tk, idx) => idx > arrowIdx && tk.tokenType === Colon3);
|
|
50718
|
+
if (colonIdx !== -1) {
|
|
50719
|
+
let semicolonColumn = null;
|
|
50720
|
+
for (let i = colonIdx + 1; i < arr.length && semicolonColumn == null; i++) {
|
|
50721
|
+
const tk = arr[i];
|
|
50722
|
+
const img = tk.image || "";
|
|
50723
|
+
if (!img.includes(";"))
|
|
50724
|
+
continue;
|
|
50725
|
+
for (let j = 0; j < img.length; j++) {
|
|
50726
|
+
if (img[j] !== ";")
|
|
50727
|
+
continue;
|
|
50728
|
+
if (isEscapedEntitySemicolon(img, j))
|
|
50729
|
+
continue;
|
|
50730
|
+
semicolonColumn = (tk.startColumn ?? 1) + j;
|
|
50731
|
+
break;
|
|
50732
|
+
}
|
|
50733
|
+
}
|
|
50734
|
+
if (semicolonColumn != null) {
|
|
50735
|
+
errs.push({
|
|
50736
|
+
line: ln,
|
|
50737
|
+
column: semicolonColumn,
|
|
50738
|
+
severity: "error",
|
|
50739
|
+
code: "SE-MSG-SEMICOLON-UNESCAPED",
|
|
50740
|
+
message: "Semicolons in sequence message text must be escaped as '#59;'.",
|
|
50741
|
+
hint: "Replace ';' with '#59;' in the message text.",
|
|
50742
|
+
length: 1
|
|
50743
|
+
});
|
|
50744
|
+
}
|
|
50745
|
+
}
|
|
50713
50746
|
if (from || to) {
|
|
50714
50747
|
const plusTok = arr.find((tk) => tk.tokenType === Plus);
|
|
50715
50748
|
const minusTok = arr.find((tk) => tk.tokenType === Minus);
|
|
@@ -52284,6 +52317,20 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
52284
52317
|
out = out.split(SENT_Q).join(""");
|
|
52285
52318
|
return out;
|
|
52286
52319
|
}
|
|
52320
|
+
function escapeUnescapedSemicolons(textPart) {
|
|
52321
|
+
let out = "";
|
|
52322
|
+
for (let i = 0; i < textPart.length; i++) {
|
|
52323
|
+
const ch = textPart[i];
|
|
52324
|
+
if (ch !== ";") {
|
|
52325
|
+
out += ch;
|
|
52326
|
+
continue;
|
|
52327
|
+
}
|
|
52328
|
+
const upto = textPart.slice(0, i + 1);
|
|
52329
|
+
const isEntity = /(?:#\d+|&#\d+|&[A-Za-z][A-Za-z0-9]+);$/.test(upto);
|
|
52330
|
+
out += isEntity ? ";" : "#59;";
|
|
52331
|
+
}
|
|
52332
|
+
return out;
|
|
52333
|
+
}
|
|
52287
52334
|
for (const e of errors) {
|
|
52288
52335
|
const key = `${e.code}@${e.line}:${e.column}:${e.length ?? 1}`;
|
|
52289
52336
|
if (seen.has(key))
|
|
@@ -53191,6 +53238,35 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
53191
53238
|
}
|
|
53192
53239
|
continue;
|
|
53193
53240
|
}
|
|
53241
|
+
if (is("SE-MSG-SEMICOLON-UNESCAPED", e)) {
|
|
53242
|
+
const lineText = lineTextAt(text, e.line);
|
|
53243
|
+
const arrows = ["<<-->>", "<<->>", "-->>", "->>", "-->", "->", "--x", "-x", "--)", "-)"];
|
|
53244
|
+
let ai = -1;
|
|
53245
|
+
let alen = 0;
|
|
53246
|
+
for (const a of arrows) {
|
|
53247
|
+
const idx = lineText.indexOf(a);
|
|
53248
|
+
if (idx !== -1 && (ai === -1 || idx < ai)) {
|
|
53249
|
+
ai = idx;
|
|
53250
|
+
alen = a.length;
|
|
53251
|
+
}
|
|
53252
|
+
}
|
|
53253
|
+
if (ai !== -1) {
|
|
53254
|
+
const colonIdx = lineText.indexOf(":", ai + alen);
|
|
53255
|
+
if (colonIdx !== -1) {
|
|
53256
|
+
const head2 = lineText.slice(0, colonIdx + 1);
|
|
53257
|
+
const tail = lineText.slice(colonIdx + 1);
|
|
53258
|
+
const fixedTail = escapeUnescapedSemicolons(tail);
|
|
53259
|
+
if (fixedTail !== tail) {
|
|
53260
|
+
edits.push({
|
|
53261
|
+
start: { line: e.line, column: 1 },
|
|
53262
|
+
end: { line: e.line, column: lineText.length + 1 },
|
|
53263
|
+
newText: head2 + fixedTail
|
|
53264
|
+
});
|
|
53265
|
+
}
|
|
53266
|
+
}
|
|
53267
|
+
}
|
|
53268
|
+
continue;
|
|
53269
|
+
}
|
|
53194
53270
|
if (is("SE-NOTE-MALFORMED", e)) {
|
|
53195
53271
|
const lineText = lineTextAt(text, e.line);
|
|
53196
53272
|
const mLR = /^(\s*)Note\s+(left|right)\s+of\s+(.+?)\s+(.+)$/.exec(lineText);
|
|
@@ -93984,10 +94060,9 @@ function parseSimpleCommand(command) {
|
|
|
93984
94060
|
// Command substitution $()
|
|
93985
94061
|
/`/,
|
|
93986
94062
|
// Command substitution ``
|
|
93987
|
-
|
|
93988
|
-
//
|
|
93989
|
-
|
|
93990
|
-
// Redirection <
|
|
94063
|
+
// Note: > and < (redirection) are intentionally NOT in this list.
|
|
94064
|
+
// They are not command separators — they redirect I/O on a single command.
|
|
94065
|
+
// The base command is still checked against allow/deny lists.
|
|
93991
94066
|
/\*\*/,
|
|
93992
94067
|
// Glob patterns (potentially dangerous)
|
|
93993
94068
|
/^\s*\{.*,.*\}|\{.*\.\.\.*\}/
|
|
@@ -94090,12 +94165,8 @@ function isComplexPattern(pattern) {
|
|
|
94090
94165
|
// Background execution
|
|
94091
94166
|
/\$\(/,
|
|
94092
94167
|
// Command substitution $()
|
|
94093
|
-
|
|
94168
|
+
/`/
|
|
94094
94169
|
// Command substitution ``
|
|
94095
|
-
/>/,
|
|
94096
|
-
// Redirection >
|
|
94097
|
-
/</
|
|
94098
|
-
// Redirection <
|
|
94099
94170
|
];
|
|
94100
94171
|
return operatorPatterns.some((p) => p.test(pattern));
|
|
94101
94172
|
}
|
|
@@ -94204,12 +94275,14 @@ var init_bashPermissions = __esm({
|
|
|
94204
94275
|
* @param {string[]} [config.deny] - Additional deny patterns (always win)
|
|
94205
94276
|
* @param {boolean} [config.disableDefaultAllow] - Disable default allow list
|
|
94206
94277
|
* @param {boolean} [config.disableDefaultDeny] - Disable default deny list
|
|
94278
|
+
* @param {boolean} [config.allowEdit] - Whether file editing is allowed (controls output redirection)
|
|
94207
94279
|
* @param {boolean} [config.debug] - Enable debug logging
|
|
94208
94280
|
* @param {Object} [config.tracer] - Optional tracer for telemetry
|
|
94209
94281
|
*/
|
|
94210
94282
|
constructor(config2 = {}) {
|
|
94211
94283
|
this.debug = config2.debug || false;
|
|
94212
94284
|
this.tracer = config2.tracer || null;
|
|
94285
|
+
this.allowEdit = config2.allowEdit || false;
|
|
94213
94286
|
this.defaultAllowPatterns = config2.disableDefaultAllow ? [] : [...DEFAULT_ALLOW_PATTERNS];
|
|
94214
94287
|
this.customAllowPatterns = config2.allow && Array.isArray(config2.allow) ? [...config2.allow] : [];
|
|
94215
94288
|
this.allowPatterns = [...this.defaultAllowPatterns, ...this.customAllowPatterns];
|
|
@@ -94295,6 +94368,24 @@ var init_bashPermissions = __esm({
|
|
|
94295
94368
|
console.log(`[BashPermissions] Checking simple command: "${command}"`);
|
|
94296
94369
|
console.log(`[BashPermissions] Parsed: ${parsed.command} with args: [${parsed.args.join(", ")}]`);
|
|
94297
94370
|
}
|
|
94371
|
+
if (!this.allowEdit && parsed.args.some((arg) => arg === ">" || arg === ">>")) {
|
|
94372
|
+
const result2 = {
|
|
94373
|
+
allowed: false,
|
|
94374
|
+
reason: "Output redirection (> or >>) requires edit permissions (allowEdit)",
|
|
94375
|
+
command,
|
|
94376
|
+
parsed
|
|
94377
|
+
};
|
|
94378
|
+
if (this.debug) {
|
|
94379
|
+
console.log(`[BashPermissions] DENIED - output redirection without allowEdit`);
|
|
94380
|
+
}
|
|
94381
|
+
this.recordBashEvent("permission.denied", {
|
|
94382
|
+
command,
|
|
94383
|
+
parsedCommand: parsed.command,
|
|
94384
|
+
reason: "output_redirection_without_allow_edit",
|
|
94385
|
+
isComplex: false
|
|
94386
|
+
});
|
|
94387
|
+
return result2;
|
|
94388
|
+
}
|
|
94298
94389
|
if (matchesAnyPattern(parsed, this.customDenyPatterns)) {
|
|
94299
94390
|
const matchedPatterns = this.customDenyPatterns.filter((pattern) => matchesPattern(parsed, pattern));
|
|
94300
94391
|
if (this.debug) {
|
|
@@ -94564,6 +94655,15 @@ var init_bashPermissions = __esm({
|
|
|
94564
94655
|
deniedReason = parsed.error || "Component contains nested complex constructs";
|
|
94565
94656
|
break;
|
|
94566
94657
|
}
|
|
94658
|
+
if (!this.allowEdit && parsed.args && parsed.args.some((arg) => arg === ">" || arg === ">>")) {
|
|
94659
|
+
if (this.debug) {
|
|
94660
|
+
console.log(`[BashPermissions] Component "${component}" has output redirection without allowEdit`);
|
|
94661
|
+
}
|
|
94662
|
+
allAllowed = false;
|
|
94663
|
+
deniedComponent = component;
|
|
94664
|
+
deniedReason = "Output redirection (> or >>) requires edit permissions (allowEdit)";
|
|
94665
|
+
break;
|
|
94666
|
+
}
|
|
94567
94667
|
if (matchesAnyPattern(parsed, this.customDenyPatterns)) {
|
|
94568
94668
|
if (this.debug) {
|
|
94569
94669
|
console.log(`[BashPermissions] Component "${component}" matches custom deny pattern`);
|
|
@@ -95093,6 +95193,7 @@ var init_bash = __esm({
|
|
|
95093
95193
|
debug = false,
|
|
95094
95194
|
cwd,
|
|
95095
95195
|
allowedFolders = [],
|
|
95196
|
+
allowEdit = false,
|
|
95096
95197
|
workspaceRoot: providedWorkspaceRoot,
|
|
95097
95198
|
tracer = null
|
|
95098
95199
|
} = options;
|
|
@@ -95102,6 +95203,7 @@ var init_bash = __esm({
|
|
|
95102
95203
|
deny: bashConfig.deny,
|
|
95103
95204
|
disableDefaultAllow: bashConfig.disableDefaultAllow,
|
|
95104
95205
|
disableDefaultDeny: bashConfig.disableDefaultDeny,
|
|
95206
|
+
allowEdit,
|
|
95105
95207
|
debug,
|
|
95106
95208
|
tracer
|
|
95107
95209
|
});
|
|
@@ -100231,19 +100333,23 @@ ${activeToolsDesc}
|
|
|
100231
100333
|
- Time budget remaining: ${Math.round(remainingBudgetMs / 6e4)} minutes
|
|
100232
100334
|
- Max per extension: ${maxPerReqMin} minutes
|
|
100233
100335
|
|
|
100234
|
-
|
|
100336
|
+
Your DEFAULT should be to EXTEND. The agent is working on a task and deserves time to finish. Only decline if you see clear evidence of a problem.
|
|
100337
|
+
|
|
100338
|
+
EXTEND (this should be your default) if:
|
|
100235
100339
|
- Tools are actively running (especially delegates or complex analysis) \u2014 they need time to finish
|
|
100236
|
-
- The agent is making
|
|
100340
|
+
- The agent is making progress, even if slowly \u2014 complex tasks take time
|
|
100237
100341
|
- New information is being gathered that will improve the final answer
|
|
100342
|
+
- The agent is iterating on an approach (trying different searches, refining queries) \u2014 this is normal problem-solving, not a loop
|
|
100343
|
+
- There is remaining budget and the task is not yet complete
|
|
100344
|
+
- When in doubt, extend \u2014 it's better to give the agent a chance than to cut it off prematurely
|
|
100238
100345
|
|
|
100239
|
-
DO NOT EXTEND if:
|
|
100240
|
-
- The agent
|
|
100241
|
-
- The
|
|
100242
|
-
-
|
|
100243
|
-
-
|
|
100244
|
-
- The agent is doing redundant work (searching for things it already found)
|
|
100346
|
+
DO NOT EXTEND only if you see CLEAR evidence of:
|
|
100347
|
+
- The agent is stuck in an obvious loop \u2014 repeating the EXACT same tool calls with the EXACT same arguments and getting the same errors back-to-back (3+ times)
|
|
100348
|
+
- The agent is retrying a fundamentally broken operation without changing its approach at all
|
|
100349
|
+
- Tool calls are consistently returning errors or empty results AND the agent is not adapting
|
|
100350
|
+
- The conversation clearly shows the agent has all the information it needs and is just making redundant calls
|
|
100245
100351
|
|
|
100246
|
-
|
|
100352
|
+
IMPORTANT: Iterating, refining, or trying variations is NOT the same as being stuck in a loop. A loop means identical repeated calls with no variation. Be generous with time \u2014 a slightly longer response time is much better than a prematurely cut-off incomplete answer.
|
|
100247
100353
|
|
|
100248
100354
|
Respond with ONLY valid JSON (no markdown, no explanation):
|
|
100249
100355
|
{"extend": true, "minutes": <1-${maxPerReqMin}>, "reason": "your reason here"}
|
|
@@ -100287,38 +100393,38 @@ or
|
|
|
100287
100393
|
const decision = JSON.parse(jsonStr);
|
|
100288
100394
|
if (decision.extend && decision.minutes > 0) {
|
|
100289
100395
|
const requestedMs = Math.min(decision.minutes, maxPerReqMin) * 6e4;
|
|
100290
|
-
const
|
|
100291
|
-
const
|
|
100396
|
+
const grantedMs2 = Math.min(requestedMs, remainingBudgetMs, negotiatedTimeoutState.maxPerRequestMs);
|
|
100397
|
+
const grantedMin2 = Math.round(grantedMs2 / 6e4 * 10) / 10;
|
|
100292
100398
|
negotiatedTimeoutState.extensionsUsed++;
|
|
100293
|
-
negotiatedTimeoutState.totalExtraTimeMs +=
|
|
100294
|
-
negotiatedTimeoutState.extensionMessage = `\u23F0 Time limit was reached. The timeout observer granted ${
|
|
100399
|
+
negotiatedTimeoutState.totalExtraTimeMs += grantedMs2;
|
|
100400
|
+
negotiatedTimeoutState.extensionMessage = `\u23F0 Time limit was reached. The timeout observer granted ${grantedMin2} more minute(s) (reason: ${decision.reason || "work in progress"}). Extensions remaining: ${negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed}. Continue your work efficiently.`;
|
|
100295
100401
|
negotiatedTimeoutState.softTimeoutId = setTimeout(() => {
|
|
100296
100402
|
runTimeoutObserver();
|
|
100297
|
-
},
|
|
100403
|
+
}, grantedMs2);
|
|
100298
100404
|
if (this.debug) {
|
|
100299
|
-
console.log(`[DEBUG] Timeout observer: granted ${
|
|
100405
|
+
console.log(`[DEBUG] Timeout observer: granted ${grantedMin2} min (reason: ${decision.reason}). Extensions: ${negotiatedTimeoutState.extensionsUsed}/${negotiatedTimeoutState.maxRequests}`);
|
|
100300
100406
|
}
|
|
100301
100407
|
if (this.tracer) {
|
|
100302
100408
|
this.tracer.addEvent("negotiated_timeout.observer_extended", {
|
|
100303
100409
|
decision_reason: decision.reason,
|
|
100304
100410
|
requested_minutes: decision.minutes,
|
|
100305
|
-
granted_ms:
|
|
100306
|
-
granted_min:
|
|
100411
|
+
granted_ms: grantedMs2,
|
|
100412
|
+
granted_min: grantedMin2,
|
|
100307
100413
|
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
100308
100414
|
max_requests: negotiatedTimeoutState.maxRequests,
|
|
100309
100415
|
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
100310
|
-
budget_remaining_ms: remainingBudgetMs -
|
|
100416
|
+
budget_remaining_ms: remainingBudgetMs - grantedMs2,
|
|
100311
100417
|
active_tools: activeToolsList.map((t) => t.name),
|
|
100312
100418
|
active_tools_count: activeToolsList.length
|
|
100313
100419
|
});
|
|
100314
100420
|
}
|
|
100315
100421
|
this.events.emit("timeout.extended", {
|
|
100316
|
-
grantedMs,
|
|
100422
|
+
grantedMs: grantedMs2,
|
|
100317
100423
|
reason: decision.reason || "work in progress",
|
|
100318
100424
|
extensionsUsed: negotiatedTimeoutState.extensionsUsed,
|
|
100319
100425
|
extensionsRemaining: negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed,
|
|
100320
100426
|
totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs,
|
|
100321
|
-
budgetRemainingMs: remainingBudgetMs -
|
|
100427
|
+
budgetRemainingMs: remainingBudgetMs - grantedMs2
|
|
100322
100428
|
});
|
|
100323
100429
|
} else {
|
|
100324
100430
|
if (this.debug) {
|
|
@@ -100340,6 +100446,18 @@ or
|
|
|
100340
100446
|
});
|
|
100341
100447
|
await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
|
|
100342
100448
|
}
|
|
100449
|
+
return {
|
|
100450
|
+
decision: decision.extend ? "extended" : "declined",
|
|
100451
|
+
reason: decision.reason || "",
|
|
100452
|
+
...decision.extend ? {
|
|
100453
|
+
granted_ms: grantedMs,
|
|
100454
|
+
granted_min: grantedMin,
|
|
100455
|
+
budget_remaining_ms: remainingBudgetMs - grantedMs
|
|
100456
|
+
} : {},
|
|
100457
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
100458
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
100459
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs
|
|
100460
|
+
};
|
|
100343
100461
|
};
|
|
100344
100462
|
try {
|
|
100345
100463
|
if (this.tracer) {
|
|
@@ -100348,6 +100466,23 @@ or
|
|
|
100348
100466
|
"timeout.extensions_used": negotiatedTimeoutState.extensionsUsed,
|
|
100349
100467
|
"timeout.active_tools_count": activeToolsList.length,
|
|
100350
100468
|
"timeout.remaining_budget_ms": remainingBudgetMs
|
|
100469
|
+
}, (span, result) => {
|
|
100470
|
+
if (result) {
|
|
100471
|
+
span.setAttributes({
|
|
100472
|
+
"observer.decision": result.decision,
|
|
100473
|
+
"observer.reason": result.reason,
|
|
100474
|
+
"observer.extensions_used": result.extensions_used,
|
|
100475
|
+
"observer.max_requests": result.max_requests,
|
|
100476
|
+
"observer.total_extra_time_ms": result.total_extra_time_ms
|
|
100477
|
+
});
|
|
100478
|
+
if (result.decision === "extended") {
|
|
100479
|
+
span.setAttributes({
|
|
100480
|
+
"observer.granted_ms": result.granted_ms,
|
|
100481
|
+
"observer.granted_min": result.granted_min,
|
|
100482
|
+
"observer.budget_remaining_ms": result.budget_remaining_ms
|
|
100483
|
+
});
|
|
100484
|
+
}
|
|
100485
|
+
}
|
|
100351
100486
|
});
|
|
100352
100487
|
} else {
|
|
100353
100488
|
await observerFn();
|
|
@@ -100455,7 +100590,13 @@ or
|
|
|
100455
100590
|
}
|
|
100456
100591
|
return {
|
|
100457
100592
|
toolChoice: "none",
|
|
100458
|
-
userMessage: `\u26A0\uFE0F TIME
|
|
100593
|
+
userMessage: `\u26A0\uFE0F TIME BUDGET EXHAUSTED. Your allocated time for this task has run out. You have ${remaining} step(s) remaining to provide your answer.
|
|
100594
|
+
|
|
100595
|
+
IMPORTANT: This is a time budget constraint, NOT a system shutdown or error. The system is working perfectly \u2014 you simply used all your allocated time.
|
|
100596
|
+
|
|
100597
|
+
Do NOT say things like "the system is shutting down" or "try again later" \u2014 the user submitted a request and is waiting for YOUR answer right now.
|
|
100598
|
+
|
|
100599
|
+
Provide your BEST answer NOW using the information you have already gathered. Do NOT call any more tools. Summarize your findings and respond completely. If something was not completed, honestly state what was not done and provide any partial results or recommendations you can offer.`
|
|
100459
100600
|
};
|
|
100460
100601
|
}
|
|
100461
100602
|
if (this.debug) {
|
|
@@ -100883,7 +101024,9 @@ Respond with ONLY valid JSON \u2014 no markdown, no explanation, no text outside
|
|
|
100883
101024
|
} catch {
|
|
100884
101025
|
}
|
|
100885
101026
|
}
|
|
100886
|
-
const summaryPrompt = `Your
|
|
101027
|
+
const summaryPrompt = `Your allocated time budget for this task has been exhausted. Some of your tool calls were cancelled mid-execution because the timeout observer determined the time limit was reached.
|
|
101028
|
+
|
|
101029
|
+
IMPORTANT: This is a time budget constraint, NOT a system shutdown or error. The system is working perfectly \u2014 you simply used all your allocated time. Do NOT say things like "the system is shutting down" or "try again later." The user is waiting for your answer RIGHT NOW.
|
|
100887
101030
|
|
|
100888
101031
|
Please provide a DETAILED summary of:
|
|
100889
101032
|
1. What you were asked to do (the original task)
|
|
@@ -100918,7 +101061,14 @@ Be thorough \u2014 this is the user's only response. Include all useful informat
|
|
|
100918
101061
|
let summaryText;
|
|
100919
101062
|
if (this.tracer) {
|
|
100920
101063
|
summaryText = await this.tracer.withSpan("negotiated_timeout.abort_summary", summaryFn, {
|
|
100921
|
-
"summary.conversation_messages": currentMessages.length
|
|
101064
|
+
"summary.conversation_messages": currentMessages.length,
|
|
101065
|
+
"observer.was_timeout": true
|
|
101066
|
+
}, (span, result) => {
|
|
101067
|
+
if (result) {
|
|
101068
|
+
span.setAttributes({
|
|
101069
|
+
"observer.summary_length": result.length
|
|
101070
|
+
});
|
|
101071
|
+
}
|
|
100922
101072
|
});
|
|
100923
101073
|
} else {
|
|
100924
101074
|
summaryText = await summaryFn();
|
|
@@ -102930,8 +103080,23 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
102930
103080
|
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
102931
103081
|
"- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
|
|
102932
103082
|
"- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
|
|
102933
|
-
"
|
|
102934
|
-
"
|
|
103083
|
+
"",
|
|
103084
|
+
"When a search returns no results:",
|
|
103085
|
+
'- If you searched a SUBFOLDER (e.g., path="gateway/"), the term might exist elsewhere.',
|
|
103086
|
+
" Try searching from the workspace root (omit the path parameter) or a different directory.",
|
|
103087
|
+
" But do NOT retry the same subfolder with different quoting \u2014 that will not help.",
|
|
103088
|
+
"- If you searched the WORKSPACE ROOT and got no results, the term does not exist in this codebase.",
|
|
103089
|
+
' Changing quotes, adding "func " prefix, or switching to method syntax will NOT help.',
|
|
103090
|
+
"- These are ALL the same failed search, NOT different searches:",
|
|
103091
|
+
' search("func ctxGetData") \u2192 no results',
|
|
103092
|
+
' search("ctxGetData") \u2192 no results \u2190 WASTED, same concept, different quoting',
|
|
103093
|
+
" search(ctxGetData) \u2192 no results \u2190 WASTED, same concept, no quotes",
|
|
103094
|
+
' search("ctx.GetData") \u2192 no results \u2190 WASTED, method syntax of same concept',
|
|
103095
|
+
' After the FIRST "no results" at a given scope, either widen the search path or try',
|
|
103096
|
+
" a fundamentally different approach: search for a broader concept, use listFiles",
|
|
103097
|
+
" to discover actual function names, or extract a known file to read real code.",
|
|
103098
|
+
"- If 2 searches return no results for a concept (across different scopes), the code likely",
|
|
103099
|
+
" uses different naming than you expect \u2014 discover the real names via extract or listFiles.",
|
|
102935
103100
|
"",
|
|
102936
103101
|
"When to use exact=true:",
|
|
102937
103102
|
"- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
|
|
@@ -102984,6 +103149,21 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
102984
103149
|
' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
|
|
102985
103150
|
' \u2192 search "authentication" \u2192 wait \u2192 search "session management" \u2192 wait (WRONG: these are independent, run them in parallel)',
|
|
102986
103151
|
"",
|
|
103152
|
+
" WORST pattern \u2014 retrying a non-existent function with quote/syntax variations (this wastes 30 minutes):",
|
|
103153
|
+
' \u2192 search "func ctxGetData" \u2192 no results',
|
|
103154
|
+
' \u2192 search "ctxGetData" \u2192 no results \u2190 WRONG: same term without "func" prefix',
|
|
103155
|
+
' \u2192 search "ctx.GetData" \u2192 no results \u2190 WRONG: method syntax of same concept',
|
|
103156
|
+
' \u2192 search "ctx.SetData" \u2192 no results \u2190 WRONG: Set variant of same concept',
|
|
103157
|
+
" \u2192 search ctxGetData \u2192 no results \u2190 WRONG: unquoted version of same term",
|
|
103158
|
+
" \u2192 extract api.go \u2192 extract api.go \u2192 extract api.go (8 times!) \u2190 WRONG: re-reading same file",
|
|
103159
|
+
' FIX: After "func ctxGetData" returns no results in gateway/:',
|
|
103160
|
+
" Option A: Widen scope \u2014 search from the workspace root (omit path) in case the",
|
|
103161
|
+
" function is defined in a different package (e.g., apidef/, user/, config/).",
|
|
103162
|
+
" Option B: Discover real names \u2014 extract a file you KNOW uses context (e.g., a",
|
|
103163
|
+
" middleware file) and READ what functions it actually calls.",
|
|
103164
|
+
" Option C: Browse \u2014 use listFiles to see what files exist and extract the relevant ones.",
|
|
103165
|
+
" NEVER: retry the same concept with different quoting in the same directory.",
|
|
103166
|
+
"",
|
|
102987
103167
|
"Keyword tips:",
|
|
102988
103168
|
"- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
|
|
102989
103169
|
'- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
@@ -103022,7 +103202,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
103022
103202
|
" - Type references and imports \u2192 include type definitions.",
|
|
103023
103203
|
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
103024
103204
|
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
103025
|
-
"7. If a search returns NO results
|
|
103205
|
+
"7. If a search returns NO results: widen the path scope if you searched a subfolder, or move on. Do NOT retry with quote/syntax variations \u2014 they search the same index.",
|
|
103026
103206
|
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
103027
103207
|
"",
|
|
103028
103208
|
`Query: ${searchQuery}`,
|
|
@@ -103082,7 +103262,14 @@ var init_vercel = __esm({
|
|
|
103082
103262
|
const previousSearches = /* @__PURE__ */ new Map();
|
|
103083
103263
|
const dupBlockCounts = /* @__PURE__ */ new Map();
|
|
103084
103264
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
103265
|
+
let consecutiveNoResults = 0;
|
|
103266
|
+
const MAX_CONSECUTIVE_NO_RESULTS = 4;
|
|
103267
|
+
const failedConcepts = /* @__PURE__ */ new Map();
|
|
103085
103268
|
const MAX_PAGES_PER_QUERY = 3;
|
|
103269
|
+
function normalizeQueryConcept(query2) {
|
|
103270
|
+
if (!query2) return "";
|
|
103271
|
+
return query2.replace(/^["']|["']$/g, "").replace(/\./g, "").replace(/[_\-\s]+/g, "").toLowerCase().trim();
|
|
103272
|
+
}
|
|
103086
103273
|
return (0, import_ai5.tool)({
|
|
103087
103274
|
name: "search",
|
|
103088
103275
|
description: searchDelegate ? searchDelegateDescription : searchDescription,
|
|
@@ -103151,6 +103338,41 @@ var init_vercel = __esm({
|
|
|
103151
103338
|
}
|
|
103152
103339
|
previousSearches.set(searchKey, { hadResults: false });
|
|
103153
103340
|
paginationCounts.set(searchKey, 0);
|
|
103341
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
103342
|
+
if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
|
|
103343
|
+
const conceptCount = failedConcepts.get(normalizedKey) + 1;
|
|
103344
|
+
failedConcepts.set(normalizedKey, conceptCount);
|
|
103345
|
+
if (debug) {
|
|
103346
|
+
console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
|
|
103347
|
+
}
|
|
103348
|
+
const isSubfolder = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103349
|
+
const scopeHint = isSubfolder ? `
|
|
103350
|
+
- Try searching from the workspace root (omit the path parameter) \u2014 the term may exist in a different directory` : `
|
|
103351
|
+
- The term does not exist in this codebase at any path`;
|
|
103352
|
+
return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.
|
|
103353
|
+
|
|
103354
|
+
Change your strategy:${scopeHint}
|
|
103355
|
+
- Use extract on a file you ALREADY found to read actual code and discover real function/type names
|
|
103356
|
+
- Use listFiles to browse directories and find what functions actually exist
|
|
103357
|
+
- Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")
|
|
103358
|
+
- If you have enough information from prior searches, provide your final answer NOW`;
|
|
103359
|
+
}
|
|
103360
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
|
|
103361
|
+
if (debug) {
|
|
103362
|
+
console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, blocking: "${searchQuery}"`);
|
|
103363
|
+
}
|
|
103364
|
+
const isSubfolderCB = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103365
|
+
const cbScopeHint = isSubfolderCB ? `
|
|
103366
|
+
- You have been searching in "${path9}" \u2014 try searching from the workspace root or a different directory` : "";
|
|
103367
|
+
return `CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results. You appear to be guessing function/type names that don't match what's actually in the code.
|
|
103368
|
+
|
|
103369
|
+
Change your approach:${cbScopeHint}
|
|
103370
|
+
1. Use extract on files you already found \u2014 read the actual code to discover real function names
|
|
103371
|
+
2. Use listFiles to browse directories and see what files/functions actually exist
|
|
103372
|
+
3. If you found some results earlier, those are likely sufficient \u2014 provide your final answer
|
|
103373
|
+
|
|
103374
|
+
Retrying search query variations will not help. Discover real names from real code instead.`;
|
|
103375
|
+
}
|
|
103154
103376
|
} else {
|
|
103155
103377
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
103156
103378
|
paginationCounts.set(searchKey, pageCount);
|
|
@@ -103164,10 +103386,24 @@ var init_vercel = __esm({
|
|
|
103164
103386
|
try {
|
|
103165
103387
|
const result = maybeAnnotate(await runRawSearch());
|
|
103166
103388
|
if (typeof result === "string" && result.includes("No results found")) {
|
|
103389
|
+
consecutiveNoResults++;
|
|
103390
|
+
const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
|
|
103391
|
+
failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
|
|
103392
|
+
if (debug) {
|
|
103393
|
+
console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
|
|
103394
|
+
}
|
|
103167
103395
|
if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
|
|
103168
103396
|
return result + "\n\n\u26A0\uFE0F Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).";
|
|
103169
103397
|
}
|
|
103398
|
+
if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1) {
|
|
103399
|
+
const isSubfolderWarn = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
|
|
103400
|
+
const warnScopeHint = isSubfolderWarn ? ` You are searching in "${path9}" \u2014 consider searching from the workspace root or a different directory.` : "";
|
|
103401
|
+
return result + `
|
|
103402
|
+
|
|
103403
|
+
\u26A0\uFE0F WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
|
|
103404
|
+
}
|
|
103170
103405
|
} else if (typeof result === "string") {
|
|
103406
|
+
consecutiveNoResults = 0;
|
|
103171
103407
|
const entry = previousSearches.get(searchKey);
|
|
103172
103408
|
if (entry) entry.hadResults = true;
|
|
103173
103409
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc303",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@anthropic-ai/claude-agent-sdk": "^0.1.46",
|
|
82
82
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
83
83
|
"@nyariv/sandboxjs": "github:probelabs/SandboxJS",
|
|
84
|
-
"@probelabs/maid": "^0.0.
|
|
84
|
+
"@probelabs/maid": "^0.0.29",
|
|
85
85
|
"acorn": "^8.15.0",
|
|
86
86
|
"acorn-walk": "^8.3.4",
|
|
87
87
|
"adm-zip": "^0.5.16",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -3746,19 +3746,23 @@ ${activeToolsDesc}
|
|
|
3746
3746
|
- Time budget remaining: ${Math.round(remainingBudgetMs / 60000)} minutes
|
|
3747
3747
|
- Max per extension: ${maxPerReqMin} minutes
|
|
3748
3748
|
|
|
3749
|
-
|
|
3749
|
+
Your DEFAULT should be to EXTEND. The agent is working on a task and deserves time to finish. Only decline if you see clear evidence of a problem.
|
|
3750
|
+
|
|
3751
|
+
EXTEND (this should be your default) if:
|
|
3750
3752
|
- Tools are actively running (especially delegates or complex analysis) — they need time to finish
|
|
3751
|
-
- The agent is making
|
|
3753
|
+
- The agent is making progress, even if slowly — complex tasks take time
|
|
3752
3754
|
- New information is being gathered that will improve the final answer
|
|
3755
|
+
- The agent is iterating on an approach (trying different searches, refining queries) — this is normal problem-solving, not a loop
|
|
3756
|
+
- There is remaining budget and the task is not yet complete
|
|
3757
|
+
- When in doubt, extend — it's better to give the agent a chance than to cut it off prematurely
|
|
3753
3758
|
|
|
3754
|
-
DO NOT EXTEND if:
|
|
3755
|
-
- The agent
|
|
3756
|
-
- The
|
|
3757
|
-
-
|
|
3758
|
-
-
|
|
3759
|
-
- The agent is doing redundant work (searching for things it already found)
|
|
3759
|
+
DO NOT EXTEND only if you see CLEAR evidence of:
|
|
3760
|
+
- The agent is stuck in an obvious loop — repeating the EXACT same tool calls with the EXACT same arguments and getting the same errors back-to-back (3+ times)
|
|
3761
|
+
- The agent is retrying a fundamentally broken operation without changing its approach at all
|
|
3762
|
+
- Tool calls are consistently returning errors or empty results AND the agent is not adapting
|
|
3763
|
+
- The conversation clearly shows the agent has all the information it needs and is just making redundant calls
|
|
3760
3764
|
|
|
3761
|
-
|
|
3765
|
+
IMPORTANT: Iterating, refining, or trying variations is NOT the same as being stuck in a loop. A loop means identical repeated calls with no variation. Be generous with time — a slightly longer response time is much better than a prematurely cut-off incomplete answer.
|
|
3762
3766
|
|
|
3763
3767
|
Respond with ONLY valid JSON (no markdown, no explanation):
|
|
3764
3768
|
{"extend": true, "minutes": <1-${maxPerReqMin}>, "reason": "your reason here"}
|
|
@@ -3885,6 +3889,20 @@ or
|
|
|
3885
3889
|
|
|
3886
3890
|
await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
|
|
3887
3891
|
}
|
|
3892
|
+
|
|
3893
|
+
// Return decision data for span enrichment
|
|
3894
|
+
return {
|
|
3895
|
+
decision: decision.extend ? 'extended' : 'declined',
|
|
3896
|
+
reason: decision.reason || '',
|
|
3897
|
+
...(decision.extend ? {
|
|
3898
|
+
granted_ms: grantedMs,
|
|
3899
|
+
granted_min: grantedMin,
|
|
3900
|
+
budget_remaining_ms: remainingBudgetMs - grantedMs,
|
|
3901
|
+
} : {}),
|
|
3902
|
+
extensions_used: negotiatedTimeoutState.extensionsUsed,
|
|
3903
|
+
max_requests: negotiatedTimeoutState.maxRequests,
|
|
3904
|
+
total_extra_time_ms: negotiatedTimeoutState.totalExtraTimeMs,
|
|
3905
|
+
};
|
|
3888
3906
|
};
|
|
3889
3907
|
|
|
3890
3908
|
try {
|
|
@@ -3894,6 +3912,23 @@ or
|
|
|
3894
3912
|
'timeout.extensions_used': negotiatedTimeoutState.extensionsUsed,
|
|
3895
3913
|
'timeout.active_tools_count': activeToolsList.length,
|
|
3896
3914
|
'timeout.remaining_budget_ms': remainingBudgetMs,
|
|
3915
|
+
}, (span, result) => {
|
|
3916
|
+
if (result) {
|
|
3917
|
+
span.setAttributes({
|
|
3918
|
+
'observer.decision': result.decision,
|
|
3919
|
+
'observer.reason': result.reason,
|
|
3920
|
+
'observer.extensions_used': result.extensions_used,
|
|
3921
|
+
'observer.max_requests': result.max_requests,
|
|
3922
|
+
'observer.total_extra_time_ms': result.total_extra_time_ms,
|
|
3923
|
+
});
|
|
3924
|
+
if (result.decision === 'extended') {
|
|
3925
|
+
span.setAttributes({
|
|
3926
|
+
'observer.granted_ms': result.granted_ms,
|
|
3927
|
+
'observer.granted_min': result.granted_min,
|
|
3928
|
+
'observer.budget_remaining_ms': result.budget_remaining_ms,
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3897
3932
|
});
|
|
3898
3933
|
} else {
|
|
3899
3934
|
await observerFn();
|
|
@@ -4033,7 +4068,7 @@ or
|
|
|
4033
4068
|
}
|
|
4034
4069
|
return {
|
|
4035
4070
|
toolChoice: 'none',
|
|
4036
|
-
userMessage: `⚠️ TIME
|
|
4071
|
+
userMessage: `⚠️ TIME BUDGET EXHAUSTED. Your allocated time for this task has run out. You have ${remaining} step(s) remaining to provide your answer.\n\nIMPORTANT: This is a time budget constraint, NOT a system shutdown or error. The system is working perfectly — you simply used all your allocated time.\n\nDo NOT say things like "the system is shutting down" or "try again later" — the user submitted a request and is waiting for YOUR answer right now.\n\nProvide your BEST answer NOW using the information you have already gathered. Do NOT call any more tools. Summarize your findings and respond completely. If something was not completed, honestly state what was not done and provide any partial results or recommendations you can offer.`
|
|
4037
4072
|
};
|
|
4038
4073
|
}
|
|
4039
4074
|
|
|
@@ -4571,8 +4606,10 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
4571
4606
|
} catch {}
|
|
4572
4607
|
}
|
|
4573
4608
|
|
|
4574
|
-
const summaryPrompt = `Your
|
|
4575
|
-
`Some of your tool calls were cancelled mid-execution.\n\n` +
|
|
4609
|
+
const summaryPrompt = `Your allocated time budget for this task has been exhausted. ` +
|
|
4610
|
+
`Some of your tool calls were cancelled mid-execution because the timeout observer determined the time limit was reached.\n\n` +
|
|
4611
|
+
`IMPORTANT: This is a time budget constraint, NOT a system shutdown or error. The system is working perfectly — you simply used all your allocated time. ` +
|
|
4612
|
+
`Do NOT say things like "the system is shutting down" or "try again later." The user is waiting for your answer RIGHT NOW.\n\n` +
|
|
4576
4613
|
`Please provide a DETAILED summary of:\n` +
|
|
4577
4614
|
`1. What you were asked to do (the original task)\n` +
|
|
4578
4615
|
`2. What you accomplished — include ALL findings, code snippets, data, and conclusions you gathered\n` +
|
|
@@ -4615,6 +4652,13 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
4615
4652
|
if (this.tracer) {
|
|
4616
4653
|
summaryText = await this.tracer.withSpan('negotiated_timeout.abort_summary', summaryFn, {
|
|
4617
4654
|
'summary.conversation_messages': currentMessages.length,
|
|
4655
|
+
'observer.was_timeout': true,
|
|
4656
|
+
}, (span, result) => {
|
|
4657
|
+
if (result) {
|
|
4658
|
+
span.setAttributes({
|
|
4659
|
+
'observer.summary_length': result.length,
|
|
4660
|
+
});
|
|
4661
|
+
}
|
|
4618
4662
|
});
|
|
4619
4663
|
} else {
|
|
4620
4664
|
summaryText = await summaryFn();
|