pi-advisor-flow 0.5.6 → 0.6.0
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/CHANGELOG.md +13 -0
- package/dist/index.js +1371 -1128
- package/extensions/index.ts +9 -9
- package/package.json +3 -1
- package/src/{untracked.ts → attachments.ts} +2 -5
- package/src/commands/activation-preparation.ts +6 -6
- package/src/commands/activation.ts +7 -7
- package/src/commands/lifecycle.ts +5 -5
- package/src/commands/manual-command.ts +13 -9
- package/src/commands/manual-consultation.ts +8 -8
- package/src/commands/manual-progress.ts +3 -3
- package/src/commands/model-commands.ts +6 -6
- package/src/commands/model-options.ts +4 -4
- package/src/commands/model-picker.ts +2 -2
- package/src/commands/registration.ts +8 -8
- package/src/commands/renderers.ts +5 -5
- package/src/commands/runtime.ts +8 -8
- package/src/commands/settings-commands.ts +8 -8
- package/src/commands/settings-persistence.ts +4 -4
- package/src/commands/types.ts +5 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +3 -3
- package/src/config/schema.ts +322 -0
- package/src/config/state.ts +2 -2
- package/src/config/storage.ts +20 -117
- package/src/config/types.ts +1 -1
- package/src/config/validation.ts +61 -181
- package/src/config.ts +7 -7
- package/src/content-utils.ts +30 -0
- package/src/conversation.ts +13 -168
- package/src/git.ts +4 -18
- package/src/herdr.ts +6 -8
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +2 -5
- package/src/pi-settings.ts +59 -0
- package/src/preferences.ts +2 -2
- package/src/redaction.ts +50 -0
- package/src/scout-context.ts +121 -485
- package/src/scout-curation.ts +84 -0
- package/src/scout-groups.ts +485 -0
- package/src/scout-protocol.ts +122 -0
- package/src/scout-reconstruct.ts +44 -0
- package/src/scout-types.ts +66 -0
- package/src/scout.ts +53 -63
- package/src/session-state.ts +119 -76
- package/src/tool-result-cap.ts +88 -0
- package/src/tools/consult-context.ts +155 -0
- package/src/tools/consultation.ts +71 -220
- package/src/tools/gate-policy.ts +5 -5
- package/src/tools/gate-protocol.ts +2 -2
- package/src/tools/loop-gate.ts +70 -61
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +9 -14
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +5 -5
- package/src/tools/registration.ts +10 -10
- package/src/tools/render-advisor-result.ts +11 -8
- package/src/tools/render-common.ts +25 -13
- package/src/tools/scout-status.ts +5 -5
- package/src/tools/session.ts +1 -1
- package/src/tools/types.ts +7 -7
- package/src/tools.ts +9 -9
- package/src/ui/manual-dialog-render.ts +2 -2
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/model-selector.ts +1 -1
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +4 -4
- package/src/ui/settings-mutations.ts +2 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +1 -1
- package/src/ui/types.ts +2 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +1 -6
package/dist/index.js
CHANGED
|
@@ -297,66 +297,226 @@ ${patch}` : "Patch: (no tracked-file content changes)");
|
|
|
297
297
|
}
|
|
298
298
|
};
|
|
299
299
|
|
|
300
|
+
// src/config/schema.ts
|
|
301
|
+
var configuredModelRef = (value) => value?.trim() || undefined;
|
|
302
|
+
var isValidAdvisorToolPolicies = (value) => {
|
|
303
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 && typeof policy === "string" && ADVISOR_TOOL_POLICIES.includes(policy));
|
|
307
|
+
};
|
|
308
|
+
var nonNegativeSafeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
309
|
+
var isValidContextMaxChars = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= MAX_CONTEXT_MAX_CHARS;
|
|
310
|
+
var isValidLoopThreshold = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 2;
|
|
311
|
+
var isValidMaxCallsPerSession = (value) => nonNegativeSafeInteger(value);
|
|
312
|
+
var isValidGateFailureMode = (value) => typeof value === "string" && GATE_FAILURE_MODES.includes(value);
|
|
313
|
+
var isValidToolResultMaxLines = (value) => nonNegativeSafeInteger(value);
|
|
314
|
+
var isValidToolResultMaxBytes = (value) => nonNegativeSafeInteger(value);
|
|
315
|
+
var CONFIG_SCHEMA = {
|
|
316
|
+
advisor: {
|
|
317
|
+
accepted: "a provider/model string",
|
|
318
|
+
current: () => configuredModelRef(advisorRef),
|
|
319
|
+
persisted: true,
|
|
320
|
+
type: "string"
|
|
321
|
+
},
|
|
322
|
+
advisorAutoLoopGate: {
|
|
323
|
+
accepted: "true or false",
|
|
324
|
+
current: () => advisorAutoLoopGateRef,
|
|
325
|
+
persisted: true,
|
|
326
|
+
type: "boolean"
|
|
327
|
+
},
|
|
328
|
+
advisorBlockOnBlocked: {
|
|
329
|
+
accepted: "true or false",
|
|
330
|
+
current: () => advisorBlockOnBlockedRef,
|
|
331
|
+
persisted: true,
|
|
332
|
+
type: "boolean"
|
|
333
|
+
},
|
|
334
|
+
advisorCollapseResponses: {
|
|
335
|
+
accepted: "true or false",
|
|
336
|
+
current: () => advisorCollapseResponsesRef,
|
|
337
|
+
persisted: true,
|
|
338
|
+
type: "boolean"
|
|
339
|
+
},
|
|
340
|
+
advisorCompletionGate: {
|
|
341
|
+
accepted: "true or false",
|
|
342
|
+
current: () => advisorCompletionGateRef,
|
|
343
|
+
persisted: true,
|
|
344
|
+
type: "boolean"
|
|
345
|
+
},
|
|
346
|
+
advisorCustomInvocation: {
|
|
347
|
+
accepted: "a string",
|
|
348
|
+
current: () => advisorCustomInvocationRef,
|
|
349
|
+
persisted: true,
|
|
350
|
+
type: "string"
|
|
351
|
+
},
|
|
352
|
+
advisorEffort: {
|
|
353
|
+
accepted: "a string",
|
|
354
|
+
current: () => advisorEffortRef,
|
|
355
|
+
persisted: true,
|
|
356
|
+
type: "string"
|
|
357
|
+
},
|
|
358
|
+
advisorFailureGate: {
|
|
359
|
+
accepted: "true or false",
|
|
360
|
+
current: () => advisorFailureGateRef,
|
|
361
|
+
persisted: true,
|
|
362
|
+
type: "boolean"
|
|
363
|
+
},
|
|
364
|
+
advisorGitContext: {
|
|
365
|
+
accepted: GIT_CONTEXT_LEVELS.join(", "),
|
|
366
|
+
current: () => advisorGitContextRef,
|
|
367
|
+
persisted: true,
|
|
368
|
+
type: "enum",
|
|
369
|
+
validate: isValidGitContextLevel
|
|
370
|
+
},
|
|
371
|
+
advisorGitContextMaxChars: {
|
|
372
|
+
accepted: "a non-negative safe integer",
|
|
373
|
+
current: () => advisorGitContextMaxCharsRef,
|
|
374
|
+
persisted: true,
|
|
375
|
+
type: "number",
|
|
376
|
+
validate: nonNegativeSafeInteger
|
|
377
|
+
},
|
|
378
|
+
advisorHerdrIntegration: {
|
|
379
|
+
accepted: "true or false",
|
|
380
|
+
current: () => advisorHerdrIntegrationRef,
|
|
381
|
+
persisted: true,
|
|
382
|
+
type: "boolean"
|
|
383
|
+
},
|
|
384
|
+
advisorLoopThreshold: {
|
|
385
|
+
accepted: "a safe integer of at least 2",
|
|
386
|
+
current: () => advisorLoopThresholdRef,
|
|
387
|
+
persisted: true,
|
|
388
|
+
type: "number",
|
|
389
|
+
validate: isValidLoopThreshold
|
|
390
|
+
},
|
|
391
|
+
advisorMaxCallsPerSession: {
|
|
392
|
+
accepted: "a non-negative safe integer",
|
|
393
|
+
current: () => advisorMaxCallsPerSessionRef,
|
|
394
|
+
persisted: true,
|
|
395
|
+
type: "number",
|
|
396
|
+
validate: isValidMaxCallsPerSession
|
|
397
|
+
},
|
|
398
|
+
advisorOutcomeLogging: {
|
|
399
|
+
accepted: "true or false",
|
|
400
|
+
current: () => advisorOutcomeLoggingRef,
|
|
401
|
+
persisted: false,
|
|
402
|
+
type: "boolean"
|
|
403
|
+
},
|
|
404
|
+
advisorPlanGate: {
|
|
405
|
+
accepted: "true or false",
|
|
406
|
+
current: () => advisorPlanGateRef,
|
|
407
|
+
persisted: true,
|
|
408
|
+
type: "boolean"
|
|
409
|
+
},
|
|
410
|
+
advisorRedactSecrets: {
|
|
411
|
+
accepted: "true or false",
|
|
412
|
+
current: () => advisorRedactSecretsRef,
|
|
413
|
+
persisted: true,
|
|
414
|
+
type: "boolean"
|
|
415
|
+
},
|
|
416
|
+
advisorScoutEnabled: {
|
|
417
|
+
accepted: "true or false",
|
|
418
|
+
current: () => advisorScoutEnabledRef,
|
|
419
|
+
persisted: true,
|
|
420
|
+
type: "boolean"
|
|
421
|
+
},
|
|
422
|
+
advisorSessionSummary: {
|
|
423
|
+
accepted: "true or false",
|
|
424
|
+
current: () => advisorSessionSummaryRef,
|
|
425
|
+
persisted: true,
|
|
426
|
+
type: "boolean"
|
|
427
|
+
},
|
|
428
|
+
advisorToolPolicies: {
|
|
429
|
+
accepted: "a JSON object with non-empty tool names and full, summary, or exclude values",
|
|
430
|
+
current: () => ({ ...advisorToolPoliciesRef }),
|
|
431
|
+
persisted: true,
|
|
432
|
+
type: "object",
|
|
433
|
+
validate: isValidAdvisorToolPolicies
|
|
434
|
+
},
|
|
435
|
+
advisorToolResultMaxBytes: {
|
|
436
|
+
accepted: "a non-negative safe integer",
|
|
437
|
+
current: () => advisorToolResultMaxBytesRef,
|
|
438
|
+
persisted: true,
|
|
439
|
+
type: "number",
|
|
440
|
+
validate: isValidToolResultMaxBytes
|
|
441
|
+
},
|
|
442
|
+
advisorToolResultMaxLines: {
|
|
443
|
+
accepted: "a non-negative safe integer",
|
|
444
|
+
current: () => advisorToolResultMaxLinesRef,
|
|
445
|
+
persisted: true,
|
|
446
|
+
type: "number",
|
|
447
|
+
validate: isValidToolResultMaxLines
|
|
448
|
+
},
|
|
449
|
+
advisorTrackedFileContent: {
|
|
450
|
+
accepted: "true or false",
|
|
451
|
+
current: () => advisorTrackedFileContentRef,
|
|
452
|
+
persisted: true,
|
|
453
|
+
type: "boolean"
|
|
454
|
+
},
|
|
455
|
+
advisorUntrackedContent: {
|
|
456
|
+
accepted: "true or false",
|
|
457
|
+
current: () => advisorUntrackedContentRef,
|
|
458
|
+
persisted: true,
|
|
459
|
+
type: "boolean"
|
|
460
|
+
},
|
|
461
|
+
alwaysOn: {
|
|
462
|
+
accepted: "true or false",
|
|
463
|
+
current: () => alwaysOnRef,
|
|
464
|
+
persisted: true,
|
|
465
|
+
type: "boolean"
|
|
466
|
+
},
|
|
467
|
+
contextMaxChars: {
|
|
468
|
+
accepted: `a safe integer from 0 through ${MAX_CONTEXT_MAX_CHARS}`,
|
|
469
|
+
current: () => contextMaxCharsRef,
|
|
470
|
+
persisted: true,
|
|
471
|
+
type: "number",
|
|
472
|
+
validate: isValidContextMaxChars
|
|
473
|
+
},
|
|
474
|
+
executor: {
|
|
475
|
+
accepted: "a provider/model string",
|
|
476
|
+
current: () => configuredModelRef(executorRef),
|
|
477
|
+
persisted: true,
|
|
478
|
+
type: "string"
|
|
479
|
+
},
|
|
480
|
+
executorEffort: {
|
|
481
|
+
accepted: "a string",
|
|
482
|
+
current: () => executorEffortRef,
|
|
483
|
+
persisted: true,
|
|
484
|
+
type: "string"
|
|
485
|
+
},
|
|
486
|
+
gateFailureMode: {
|
|
487
|
+
accepted: GATE_FAILURE_MODES.join(", "),
|
|
488
|
+
current: () => advisorFailureModeRef,
|
|
489
|
+
persisted: true,
|
|
490
|
+
type: "enum",
|
|
491
|
+
validate: isValidGateFailureMode
|
|
492
|
+
},
|
|
493
|
+
showUsageDetails: {
|
|
494
|
+
accepted: "true or false",
|
|
495
|
+
current: () => showUsageDetailsRef,
|
|
496
|
+
persisted: true,
|
|
497
|
+
type: "boolean"
|
|
498
|
+
},
|
|
499
|
+
showUsageFooter: {
|
|
500
|
+
accepted: "true or false",
|
|
501
|
+
current: () => showUsageFooterRef,
|
|
502
|
+
persisted: true,
|
|
503
|
+
type: "boolean"
|
|
504
|
+
},
|
|
505
|
+
simpleMode: {
|
|
506
|
+
accepted: "true or false",
|
|
507
|
+
current: () => simpleModeRef,
|
|
508
|
+
persisted: true,
|
|
509
|
+
type: "boolean"
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
var SAVED_CONFIG_KEYS = Object.keys(CONFIG_SCHEMA).filter((key) => CONFIG_SCHEMA[key].persisted);
|
|
513
|
+
var SCHEMA_BY_KEY = CONFIG_SCHEMA;
|
|
514
|
+
|
|
300
515
|
// src/config/validation.ts
|
|
301
|
-
var CONFIG_KEYS = new Set(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
"advisorCollapseResponses",
|
|
306
|
-
"advisorCompletionGate",
|
|
307
|
-
"advisorCustomInvocation",
|
|
308
|
-
"advisorEffort",
|
|
309
|
-
"advisorFailureGate",
|
|
310
|
-
"advisorGitContext",
|
|
311
|
-
"advisorGitContextMaxChars",
|
|
312
|
-
"advisorHerdrIntegration",
|
|
313
|
-
"advisorLoopThreshold",
|
|
314
|
-
"advisorMaxCallsPerSession",
|
|
315
|
-
"advisorPlanGate",
|
|
316
|
-
"advisorSessionSummary",
|
|
317
|
-
"advisorScoutEnabled",
|
|
318
|
-
"showUsageDetails",
|
|
319
|
-
"showUsageFooter",
|
|
320
|
-
"simpleMode",
|
|
321
|
-
"alwaysOn",
|
|
322
|
-
"advisorToolResultMaxBytes",
|
|
323
|
-
"advisorToolResultMaxLines",
|
|
324
|
-
"advisorTrackedFileContent",
|
|
325
|
-
"advisorRedactSecrets",
|
|
326
|
-
"advisorUntrackedContent",
|
|
327
|
-
"advisorOutcomeLogging",
|
|
328
|
-
"advisorToolPolicies",
|
|
329
|
-
"contextMaxChars",
|
|
330
|
-
"executor",
|
|
331
|
-
"executorEffort",
|
|
332
|
-
"gateFailureMode"
|
|
333
|
-
]);
|
|
334
|
-
var BOOLEAN_CONFIG_KEYS = [
|
|
335
|
-
"advisorPlanGate",
|
|
336
|
-
"advisorFailureGate",
|
|
337
|
-
"advisorCompletionGate",
|
|
338
|
-
"advisorCollapseResponses",
|
|
339
|
-
"advisorBlockOnBlocked",
|
|
340
|
-
"advisorAutoLoopGate",
|
|
341
|
-
"advisorSessionSummary",
|
|
342
|
-
"advisorScoutEnabled",
|
|
343
|
-
"showUsageDetails",
|
|
344
|
-
"showUsageFooter",
|
|
345
|
-
"simpleMode",
|
|
346
|
-
"alwaysOn",
|
|
347
|
-
"advisorHerdrIntegration",
|
|
348
|
-
"advisorTrackedFileContent",
|
|
349
|
-
"advisorRedactSecrets",
|
|
350
|
-
"advisorUntrackedContent",
|
|
351
|
-
"advisorOutcomeLogging"
|
|
352
|
-
];
|
|
353
|
-
var STRING_CONFIG_KEYS = [
|
|
354
|
-
"executor",
|
|
355
|
-
"advisor",
|
|
356
|
-
"executorEffort",
|
|
357
|
-
"advisorEffort",
|
|
358
|
-
"advisorCustomInvocation"
|
|
359
|
-
];
|
|
516
|
+
var CONFIG_KEYS = new Set(Object.keys(CONFIG_SCHEMA));
|
|
517
|
+
var keysOfType = (type) => Object.keys(CONFIG_SCHEMA).filter((key) => SCHEMA_BY_KEY[key].type === type);
|
|
518
|
+
var BOOLEAN_CONFIG_KEYS = keysOfType("boolean");
|
|
519
|
+
var STRING_CONFIG_KEYS = keysOfType("string");
|
|
360
520
|
var invalidConfigValue = (path, key, accepted) => {
|
|
361
521
|
throw new TypeError(`Invalid advisor configuration at ${path}, key ${JSON.stringify(key)}: expected ${accepted}.`);
|
|
362
522
|
};
|
|
@@ -364,65 +524,38 @@ var unknownConfigKeys = (config) => Object.keys(config).filter((key) => !CONFIG_
|
|
|
364
524
|
var validateStringValues = (config, path) => {
|
|
365
525
|
for (const key of STRING_CONFIG_KEYS) {
|
|
366
526
|
if (config[key] !== undefined && typeof config[key] !== "string") {
|
|
367
|
-
invalidConfigValue(path, key, key
|
|
527
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
368
528
|
}
|
|
369
529
|
}
|
|
370
530
|
};
|
|
371
531
|
var validateBooleanValues = (config, path) => {
|
|
372
532
|
for (const key of BOOLEAN_CONFIG_KEYS) {
|
|
373
533
|
if (config[key] !== undefined && typeof config[key] !== "boolean") {
|
|
374
|
-
invalidConfigValue(path, key,
|
|
534
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
375
535
|
}
|
|
376
536
|
}
|
|
377
537
|
};
|
|
378
|
-
var
|
|
379
|
-
|
|
380
|
-
|
|
538
|
+
var validateNumericValues = (config, path) => {
|
|
539
|
+
for (const key of keysOfType("number")) {
|
|
540
|
+
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
541
|
+
if (config[key] !== undefined && isValid !== undefined && !isValid(config[key])) {
|
|
542
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
543
|
+
}
|
|
381
544
|
}
|
|
382
|
-
return Object.entries(value).every(([toolName, policy]) => toolName.trim().length > 0 && typeof policy === "string" && ADVISOR_TOOL_POLICIES.includes(policy));
|
|
383
545
|
};
|
|
384
|
-
var
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
[
|
|
398
|
-
"advisorLoopThreshold",
|
|
399
|
-
isValidLoopThreshold,
|
|
400
|
-
"a safe integer of at least 2"
|
|
401
|
-
],
|
|
402
|
-
[
|
|
403
|
-
"advisorMaxCallsPerSession",
|
|
404
|
-
isValidMaxCallsPerSession,
|
|
405
|
-
"a non-negative safe integer"
|
|
406
|
-
],
|
|
407
|
-
[
|
|
408
|
-
"advisorToolResultMaxLines",
|
|
409
|
-
isValidToolResultMaxLines,
|
|
410
|
-
"a non-negative safe integer"
|
|
411
|
-
],
|
|
412
|
-
[
|
|
413
|
-
"advisorToolResultMaxBytes",
|
|
414
|
-
isValidToolResultMaxBytes,
|
|
415
|
-
"a non-negative safe integer"
|
|
416
|
-
],
|
|
417
|
-
[
|
|
418
|
-
"advisorGitContextMaxChars",
|
|
419
|
-
isValidToolResultMaxBytes,
|
|
420
|
-
"a non-negative safe integer"
|
|
421
|
-
]
|
|
422
|
-
];
|
|
423
|
-
for (const [key, isValid, description] of numericRules) {
|
|
424
|
-
if (config[key] !== undefined && !isValid(config[key])) {
|
|
425
|
-
invalidConfigValue(path, key, description);
|
|
546
|
+
var validateEnumValues = (config, path) => {
|
|
547
|
+
for (const key of keysOfType("enum")) {
|
|
548
|
+
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
549
|
+
if (config[key] !== undefined && isValid !== undefined && !isValid(config[key])) {
|
|
550
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
var validateObjectValues = (config, path) => {
|
|
555
|
+
for (const key of keysOfType("object")) {
|
|
556
|
+
const isValid = SCHEMA_BY_KEY[key].validate;
|
|
557
|
+
if (config[key] !== undefined && isValid !== undefined && !isValid(config[key])) {
|
|
558
|
+
invalidConfigValue(path, key, SCHEMA_BY_KEY[key].accepted);
|
|
426
559
|
}
|
|
427
560
|
}
|
|
428
561
|
};
|
|
@@ -434,15 +567,8 @@ var validateConfig = (value, path = "advisor.json") => {
|
|
|
434
567
|
validateStringValues(config, path);
|
|
435
568
|
validateBooleanValues(config, path);
|
|
436
569
|
validateNumericValues(config, path);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
if (config.gateFailureMode !== undefined && !isValidGateFailureMode(config.gateFailureMode)) {
|
|
441
|
-
invalidConfigValue(path, "gateFailureMode", GATE_FAILURE_MODES.join(", "));
|
|
442
|
-
}
|
|
443
|
-
if (config.advisorGitContext !== undefined && !isValidGitContextLevel(config.advisorGitContext)) {
|
|
444
|
-
invalidConfigValue(path, "advisorGitContext", GIT_CONTEXT_LEVELS.join(", "));
|
|
445
|
-
}
|
|
570
|
+
validateObjectValues(config, path);
|
|
571
|
+
validateEnumValues(config, path);
|
|
446
572
|
return true;
|
|
447
573
|
};
|
|
448
574
|
|
|
@@ -562,71 +688,7 @@ var applyConfig = (config) => {
|
|
|
562
688
|
};
|
|
563
689
|
|
|
564
690
|
// src/config/storage.ts
|
|
565
|
-
var
|
|
566
|
-
var SAVED_CONFIG_KEYS = [
|
|
567
|
-
"advisor",
|
|
568
|
-
"advisorAutoLoopGate",
|
|
569
|
-
"advisorBlockOnBlocked",
|
|
570
|
-
"advisorCollapseResponses",
|
|
571
|
-
"advisorCompletionGate",
|
|
572
|
-
"advisorCustomInvocation",
|
|
573
|
-
"advisorEffort",
|
|
574
|
-
"advisorFailureGate",
|
|
575
|
-
"advisorGitContext",
|
|
576
|
-
"advisorGitContextMaxChars",
|
|
577
|
-
"advisorHerdrIntegration",
|
|
578
|
-
"advisorLoopThreshold",
|
|
579
|
-
"advisorMaxCallsPerSession",
|
|
580
|
-
"advisorPlanGate",
|
|
581
|
-
"advisorRedactSecrets",
|
|
582
|
-
"advisorScoutEnabled",
|
|
583
|
-
"advisorSessionSummary",
|
|
584
|
-
"advisorToolPolicies",
|
|
585
|
-
"advisorToolResultMaxBytes",
|
|
586
|
-
"advisorToolResultMaxLines",
|
|
587
|
-
"advisorTrackedFileContent",
|
|
588
|
-
"advisorUntrackedContent",
|
|
589
|
-
"alwaysOn",
|
|
590
|
-
"contextMaxChars",
|
|
591
|
-
"executor",
|
|
592
|
-
"executorEffort",
|
|
593
|
-
"gateFailureMode",
|
|
594
|
-
"showUsageDetails",
|
|
595
|
-
"showUsageFooter",
|
|
596
|
-
"simpleMode"
|
|
597
|
-
];
|
|
598
|
-
var currentConfigState = () => ({
|
|
599
|
-
advisor: configuredModelRef(advisorRef),
|
|
600
|
-
advisorAutoLoopGate: advisorAutoLoopGateRef,
|
|
601
|
-
advisorBlockOnBlocked: advisorBlockOnBlockedRef,
|
|
602
|
-
advisorCollapseResponses: advisorCollapseResponsesRef,
|
|
603
|
-
advisorCompletionGate: advisorCompletionGateRef,
|
|
604
|
-
advisorCustomInvocation: advisorCustomInvocationRef,
|
|
605
|
-
advisorEffort: advisorEffortRef,
|
|
606
|
-
advisorFailureGate: advisorFailureGateRef,
|
|
607
|
-
advisorGitContext: advisorGitContextRef,
|
|
608
|
-
advisorGitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
609
|
-
advisorHerdrIntegration: advisorHerdrIntegrationRef,
|
|
610
|
-
advisorLoopThreshold: advisorLoopThresholdRef,
|
|
611
|
-
advisorMaxCallsPerSession: advisorMaxCallsPerSessionRef,
|
|
612
|
-
advisorPlanGate: advisorPlanGateRef,
|
|
613
|
-
advisorRedactSecrets: advisorRedactSecretsRef,
|
|
614
|
-
advisorScoutEnabled: advisorScoutEnabledRef,
|
|
615
|
-
advisorSessionSummary: advisorSessionSummaryRef,
|
|
616
|
-
advisorToolPolicies: { ...advisorToolPoliciesRef },
|
|
617
|
-
advisorToolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
618
|
-
advisorToolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
619
|
-
advisorTrackedFileContent: advisorTrackedFileContentRef,
|
|
620
|
-
advisorUntrackedContent: advisorUntrackedContentRef,
|
|
621
|
-
alwaysOn: alwaysOnRef,
|
|
622
|
-
contextMaxChars: contextMaxCharsRef,
|
|
623
|
-
executor: configuredModelRef(executorRef),
|
|
624
|
-
executorEffort: executorEffortRef,
|
|
625
|
-
gateFailureMode: advisorFailureModeRef,
|
|
626
|
-
showUsageDetails: showUsageDetailsRef,
|
|
627
|
-
showUsageFooter: showUsageFooterRef,
|
|
628
|
-
simpleMode: simpleModeRef
|
|
629
|
-
});
|
|
691
|
+
var currentConfigState = () => Object.fromEntries(SAVED_CONFIG_KEYS.map((key) => [key, CONFIG_SCHEMA[key].current()]));
|
|
630
692
|
var sameConfigValue = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
631
693
|
var readExistingConfig = (path) => {
|
|
632
694
|
try {
|
|
@@ -654,13 +716,9 @@ var applyChangedConfigValues = (data, current, changedKeys, persistAdvisor, pers
|
|
|
654
716
|
var loadedConfigState;
|
|
655
717
|
var loadedConfigPath;
|
|
656
718
|
var readConfig = (path) => {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
return config;
|
|
661
|
-
} catch (error) {
|
|
662
|
-
throw error instanceof Error ? error : new Error(String(error));
|
|
663
|
-
}
|
|
719
|
+
const config = JSON.parse(readFileSync(path, "utf8"));
|
|
720
|
+
validateConfig(config, path);
|
|
721
|
+
return config;
|
|
664
722
|
};
|
|
665
723
|
var configCache = new Map;
|
|
666
724
|
var warnedUnknownConfigIdentities = new Set;
|
|
@@ -1039,26 +1097,30 @@ var selectAdvisorModels = async (ctx, options) => {
|
|
|
1039
1097
|
// src/herdr.ts
|
|
1040
1098
|
import net from "node:net";
|
|
1041
1099
|
|
|
1042
|
-
// src/
|
|
1043
|
-
var isRecord = (value) => Boolean(value) && typeof value === "object";
|
|
1100
|
+
// src/content-utils.ts
|
|
1101
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1102
|
+
var byteLength = (value) => Buffer.byteLength(value, "utf8");
|
|
1044
1103
|
var contentParts = (content) => {
|
|
1045
1104
|
if (typeof content === "string") {
|
|
1046
1105
|
return [content];
|
|
1047
1106
|
}
|
|
1048
1107
|
return Array.isArray(content) ? content : [];
|
|
1049
1108
|
};
|
|
1050
|
-
var
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1109
|
+
var capUtf8Bytes = (value, maxBytes) => {
|
|
1110
|
+
let result = "";
|
|
1111
|
+
let used = 0;
|
|
1112
|
+
for (const character of value) {
|
|
1113
|
+
const characterBytes = byteLength(character);
|
|
1114
|
+
if (used + characterBytes > maxBytes) {
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
result += character;
|
|
1118
|
+
used += characterBytes;
|
|
1056
1119
|
}
|
|
1057
|
-
return
|
|
1120
|
+
return result;
|
|
1058
1121
|
};
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
var byteLength = (value) => Buffer.byteLength(value, "utf8");
|
|
1122
|
+
|
|
1123
|
+
// src/redaction.ts
|
|
1062
1124
|
var REDACTION_MARKER = "[REDACTED SECRET]";
|
|
1063
1125
|
var PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/gi;
|
|
1064
1126
|
var PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/i;
|
|
@@ -1088,263 +1150,85 @@ var redactSecrets = (value) => {
|
|
|
1088
1150
|
};
|
|
1089
1151
|
var redactAndCapText = (value, maxBytes, redact = true) => {
|
|
1090
1152
|
const source = redact ? redactSecrets(value) : value;
|
|
1091
|
-
|
|
1092
|
-
for (const character of source) {
|
|
1093
|
-
if (byteLength(result + character) > maxBytes) {
|
|
1094
|
-
break;
|
|
1095
|
-
}
|
|
1096
|
-
result += character;
|
|
1097
|
-
}
|
|
1098
|
-
return result;
|
|
1153
|
+
return capUtf8Bytes(source, maxBytes);
|
|
1099
1154
|
};
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1155
|
+
|
|
1156
|
+
// src/herdr.ts
|
|
1157
|
+
var HERDR_NOTIFICATION_METHOD = "notification.show";
|
|
1158
|
+
var SOURCE = "pi-advisor:advisor-activity";
|
|
1159
|
+
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1160
|
+
var NOTIFICATION_SOURCE = "pi-advisor:advisor-notification";
|
|
1161
|
+
var HERDR_PI_SOURCE = "herdr:pi";
|
|
1162
|
+
var sequence = Date.now() * 1000;
|
|
1163
|
+
var nextSequence = () => {
|
|
1164
|
+
sequence += 1;
|
|
1165
|
+
return sequence;
|
|
1166
|
+
};
|
|
1167
|
+
var emitBlocked;
|
|
1168
|
+
var setHerdrBlockedEmitter = (emitter) => {
|
|
1169
|
+
emitBlocked = emitter;
|
|
1170
|
+
};
|
|
1171
|
+
var safeEmitBlocked = (active, label = "Advisor blocked") => {
|
|
1172
|
+
try {
|
|
1173
|
+
emitBlocked?.(active, label);
|
|
1174
|
+
} catch {}
|
|
1175
|
+
};
|
|
1176
|
+
var isControlCharacter = (character) => character <= "\x1F" || character === "";
|
|
1177
|
+
var cleanNotification = (value, max) => [...redactSecrets(value)].map((character) => isControlCharacter(character) ? " " : character).join("").replace(/\s+/g, " ").trim().slice(0, max);
|
|
1178
|
+
var createHerdrNotificationRequest = (title, body) => ({
|
|
1179
|
+
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
1180
|
+
method: HERDR_NOTIFICATION_METHOD,
|
|
1181
|
+
params: {
|
|
1182
|
+
body: cleanNotification(body, 240),
|
|
1183
|
+
position: "top-left",
|
|
1184
|
+
sound: "request",
|
|
1185
|
+
title: cleanNotification(title, 80)
|
|
1113
1186
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
totalBytes,
|
|
1119
|
-
totalLines,
|
|
1120
|
-
truncated: false
|
|
1121
|
-
};
|
|
1187
|
+
});
|
|
1188
|
+
var sendToHerdr = (request) => {
|
|
1189
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
1190
|
+
return;
|
|
1122
1191
|
}
|
|
1123
|
-
const
|
|
1124
|
-
const
|
|
1125
|
-
if (
|
|
1126
|
-
|
|
1127
|
-
return {
|
|
1128
|
-
content,
|
|
1129
|
-
omittedLines: totalLines,
|
|
1130
|
-
totalBytes,
|
|
1131
|
-
totalLines,
|
|
1132
|
-
truncated: true
|
|
1133
|
-
};
|
|
1192
|
+
const paneId = process.env.HERDR_PANE_ID;
|
|
1193
|
+
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
1194
|
+
if (!(paneId && socketPath)) {
|
|
1195
|
+
return;
|
|
1134
1196
|
}
|
|
1135
|
-
const
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
selected.push(line);
|
|
1146
|
-
used = next;
|
|
1147
|
-
}
|
|
1148
|
-
return selected;
|
|
1149
|
-
};
|
|
1150
|
-
const availableBytes = maxBytes - markerBytes - 2;
|
|
1151
|
-
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
1152
|
-
const tail = collect(lines.slice(Math.max(head.length, lines.length - tailCount)), tailCount, availableBytes - byteLength(head.join(`
|
|
1153
|
-
`)));
|
|
1154
|
-
const content = [...head, marker, ...tail].join(`
|
|
1155
|
-
`);
|
|
1156
|
-
return {
|
|
1157
|
-
content,
|
|
1158
|
-
omittedLines: Math.max(0, totalLines - head.length - tail.length),
|
|
1159
|
-
totalBytes,
|
|
1160
|
-
totalLines,
|
|
1161
|
-
truncated: true
|
|
1162
|
-
};
|
|
1197
|
+
const endpoint = process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath;
|
|
1198
|
+
const socket = net.createConnection(endpoint);
|
|
1199
|
+
const timeout = setTimeout(() => socket.destroy(), 500);
|
|
1200
|
+
timeout.unref?.();
|
|
1201
|
+
socket.once("connect", () => socket.write(`${JSON.stringify(request)}
|
|
1202
|
+
`));
|
|
1203
|
+
socket.once("data", () => socket.destroy());
|
|
1204
|
+
socket.once("error", () => socket.destroy());
|
|
1205
|
+
socket.once("close", () => clearTimeout(timeout));
|
|
1163
1206
|
};
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1207
|
+
|
|
1208
|
+
class HerdrAdvisorActivity {
|
|
1209
|
+
#activeConsultations = 0;
|
|
1210
|
+
report;
|
|
1211
|
+
enabled;
|
|
1212
|
+
constructor(report = sendToHerdr, enabled = () => true) {
|
|
1213
|
+
this.report = report;
|
|
1214
|
+
this.enabled = enabled;
|
|
1169
1215
|
}
|
|
1170
|
-
|
|
1171
|
-
if (!
|
|
1172
|
-
|
|
1216
|
+
start() {
|
|
1217
|
+
if (!this.enabled()) {
|
|
1218
|
+
return;
|
|
1173
1219
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
parts.push(`[Tool Call: ${toolName}] (excluded by Advisor tool policy)`);
|
|
1178
|
-
continue;
|
|
1220
|
+
this.#activeConsultations += 1;
|
|
1221
|
+
if (this.#activeConsultations === 1) {
|
|
1222
|
+
this.safeReport(false);
|
|
1179
1223
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
`)}` : undefined;
|
|
1189
|
-
};
|
|
1190
|
-
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1191
|
-
const status = message.isError ? "error" : "success";
|
|
1192
|
-
const toolName = typeof message.toolName === "string" ? message.toolName : "unknown";
|
|
1193
|
-
const policy = policies[toolName] ?? "full";
|
|
1194
|
-
const source = textFrom(message.content);
|
|
1195
|
-
if (policy === "exclude") {
|
|
1196
|
-
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
1197
|
-
}
|
|
1198
|
-
if (policy === "summary") {
|
|
1199
|
-
const capped = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
|
|
1200
|
-
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped.totalLines} lines, ${capped.totalBytes} bytes; source output was${capped.truncated ? "" : " not"} truncated)`;
|
|
1201
|
-
}
|
|
1202
|
-
const disclosed = redact ? redactSecrets(source) : source;
|
|
1203
|
-
const capped = capToolResult(disclosed, toolResultMaxLines, toolResultMaxBytes);
|
|
1204
|
-
return `[Tool Result for ${toolName}] (${message.isError ? "Error " : ""}output):
|
|
1205
|
-
${capped.content}`;
|
|
1206
|
-
};
|
|
1207
|
-
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1208
|
-
if (!isRecord(entry)) {
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
if (entry.type === "compaction" && typeof entry.summary === "string") {
|
|
1212
|
-
return `[System Compaction Summary]: ${redact ? redactSecrets(entry.summary) : entry.summary}`;
|
|
1213
|
-
}
|
|
1214
|
-
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
const { message } = entry;
|
|
1218
|
-
if (message.role === "user") {
|
|
1219
|
-
const text = textFrom(message.content);
|
|
1220
|
-
return text ? `User: ${redact ? redactSecrets(text) : text}` : undefined;
|
|
1221
|
-
}
|
|
1222
|
-
if (message.role === "assistant") {
|
|
1223
|
-
return assistantEntry(message, policies, redact);
|
|
1224
|
-
}
|
|
1225
|
-
if (message.role === "toolResult" || message.role === "tool") {
|
|
1226
|
-
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
var selectRecentEntries = (entries, maxChars) => {
|
|
1230
|
-
const separator = `
|
|
1231
|
-
|
|
1232
|
-
`;
|
|
1233
|
-
const joined = entries.join(separator);
|
|
1234
|
-
if (joined.length <= maxChars || maxChars === Number.MAX_SAFE_INTEGER) {
|
|
1235
|
-
return joined;
|
|
1236
|
-
}
|
|
1237
|
-
const newestTruncated = "[Newest entry truncated]";
|
|
1238
|
-
if (entries.length === 1) {
|
|
1239
|
-
const prefix = `${newestTruncated}${separator}`;
|
|
1240
|
-
return `${prefix}${entries[0].slice(0, Math.max(0, maxChars - prefix.length))}`.slice(0, maxChars);
|
|
1241
|
-
}
|
|
1242
|
-
const selected = [];
|
|
1243
|
-
let selectedLength = 0;
|
|
1244
|
-
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
1245
|
-
const entry = entries[index];
|
|
1246
|
-
const candidateCount = selected.length + 1;
|
|
1247
|
-
const omitted = entries.length - candidateCount;
|
|
1248
|
-
const marker = `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]`;
|
|
1249
|
-
const candidateLength = selectedLength + entry.length + (selected.length > 0 ? separator.length : 0);
|
|
1250
|
-
if (marker.length + separator.length + candidateLength > maxChars) {
|
|
1251
|
-
break;
|
|
1252
|
-
}
|
|
1253
|
-
selected.unshift(entry);
|
|
1254
|
-
selectedLength = candidateLength;
|
|
1255
|
-
}
|
|
1256
|
-
const omitted = entries.length - Math.max(1, selected.length);
|
|
1257
|
-
const marker = `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]`;
|
|
1258
|
-
if (selected.length > 0) {
|
|
1259
|
-
return `${marker}${separator}${selected.join(separator)}`;
|
|
1260
|
-
}
|
|
1261
|
-
const prefix = `${marker}${separator}${newestTruncated}${separator}`;
|
|
1262
|
-
return `${prefix}${entries.at(-1)?.slice(0, Math.max(0, maxChars - prefix.length)) ?? ""}`.slice(0, maxChars);
|
|
1263
|
-
};
|
|
1264
|
-
var recentConversation = (ctx, maxChars = 15000, toolResultMaxLines = advisorToolResultMaxLinesRef, toolResultMaxBytes = advisorToolResultMaxBytesRef, policies = advisorToolPoliciesRef, redact = advisorRedactSecretsRef) => {
|
|
1265
|
-
if (maxChars === 0) {
|
|
1266
|
-
return "";
|
|
1267
|
-
}
|
|
1268
|
-
const entries = ctx.sessionManager.getBranch().map((entry) => conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes, policies, redact)).filter((entry) => entry !== undefined);
|
|
1269
|
-
return selectRecentEntries(entries, maxChars);
|
|
1270
|
-
};
|
|
1271
|
-
|
|
1272
|
-
// src/herdr.ts
|
|
1273
|
-
var HERDR_NOTIFICATION_METHOD = `${"notification"}.${"show"}`;
|
|
1274
|
-
var SOURCE = "pi-advisor:advisor-activity";
|
|
1275
|
-
var BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
1276
|
-
var NOTIFICATION_SOURCE = "pi-advisor:advisor-notification";
|
|
1277
|
-
var HERDR_PI_SOURCE = "herdr:pi";
|
|
1278
|
-
var sequence = Date.now() * 1000;
|
|
1279
|
-
var nextSequence = () => {
|
|
1280
|
-
sequence += 1;
|
|
1281
|
-
return sequence;
|
|
1282
|
-
};
|
|
1283
|
-
var emitBlocked;
|
|
1284
|
-
var setHerdrBlockedEmitter = (emitter) => {
|
|
1285
|
-
emitBlocked = emitter;
|
|
1286
|
-
};
|
|
1287
|
-
var safeEmitBlocked = (active, label = "Advisor blocked") => {
|
|
1288
|
-
try {
|
|
1289
|
-
emitBlocked?.(active, label);
|
|
1290
|
-
} catch {}
|
|
1291
|
-
};
|
|
1292
|
-
var isControlCharacter = (character) => character <= "\x1F" || character === "";
|
|
1293
|
-
var cleanNotification = (value, max) => [...redactSecrets(value)].map((character) => isControlCharacter(character) ? " " : character).join("").replace(/\s+/g, " ").trim().slice(0, max);
|
|
1294
|
-
var createHerdrNotificationRequest = (title, body) => ({
|
|
1295
|
-
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
1296
|
-
method: HERDR_NOTIFICATION_METHOD,
|
|
1297
|
-
params: {
|
|
1298
|
-
body: cleanNotification(body, 240),
|
|
1299
|
-
position: "top-left",
|
|
1300
|
-
sound: "request",
|
|
1301
|
-
title: cleanNotification(title, 80)
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
var sendToHerdr = (request) => {
|
|
1305
|
-
if (process.env.HERDR_ENV !== "1") {
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
const paneId = process.env.HERDR_PANE_ID;
|
|
1309
|
-
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
1310
|
-
if (!(paneId && socketPath)) {
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
const endpoint = process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath;
|
|
1314
|
-
const socket = net.createConnection(endpoint);
|
|
1315
|
-
const timeout = setTimeout(() => socket.destroy(), 500);
|
|
1316
|
-
timeout.unref?.();
|
|
1317
|
-
socket.once("connect", () => socket.write(`${JSON.stringify(request)}
|
|
1318
|
-
`));
|
|
1319
|
-
socket.once("data", () => socket.destroy());
|
|
1320
|
-
socket.once("error", () => socket.destroy());
|
|
1321
|
-
socket.once("close", () => clearTimeout(timeout));
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
class HerdrAdvisorActivity {
|
|
1325
|
-
#activeConsultations = 0;
|
|
1326
|
-
report;
|
|
1327
|
-
enabled;
|
|
1328
|
-
constructor(report = sendToHerdr, enabled = () => true) {
|
|
1329
|
-
this.report = report;
|
|
1330
|
-
this.enabled = enabled;
|
|
1331
|
-
}
|
|
1332
|
-
start() {
|
|
1333
|
-
if (!this.enabled()) {
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
this.#activeConsultations += 1;
|
|
1337
|
-
if (this.#activeConsultations === 1) {
|
|
1338
|
-
this.safeReport(false);
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
finish() {
|
|
1342
|
-
if (this.#activeConsultations === 0) {
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
this.#activeConsultations -= 1;
|
|
1346
|
-
if (this.#activeConsultations === 0) {
|
|
1347
|
-
this.safeReport(true);
|
|
1224
|
+
}
|
|
1225
|
+
finish() {
|
|
1226
|
+
if (this.#activeConsultations === 0) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
this.#activeConsultations -= 1;
|
|
1230
|
+
if (this.#activeConsultations === 0) {
|
|
1231
|
+
this.safeReport(true);
|
|
1348
1232
|
}
|
|
1349
1233
|
}
|
|
1350
1234
|
clear() {
|
|
@@ -1596,57 +1480,444 @@ var collectTextStream = async (resolved, options, streamModel = stream) => {
|
|
|
1596
1480
|
};
|
|
1597
1481
|
};
|
|
1598
1482
|
|
|
1599
|
-
// src/
|
|
1483
|
+
// src/attachments.ts
|
|
1484
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1485
|
+
import { constants } from "node:fs";
|
|
1600
1486
|
import { lstat, open, realpath } from "node:fs/promises";
|
|
1601
|
-
import {
|
|
1602
|
-
var
|
|
1603
|
-
var
|
|
1604
|
-
var
|
|
1487
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
1488
|
+
var ADVISOR_FILE_MAX_BYTES = 8 * 1024;
|
|
1489
|
+
var ADVISOR_FILES_TOTAL_MAX_BYTES = 24 * 1024;
|
|
1490
|
+
var PATH_SEGMENTS = /[\\/]/;
|
|
1491
|
+
var within = (root, candidate) => {
|
|
1605
1492
|
const path = relative(root, candidate);
|
|
1606
|
-
return path
|
|
1493
|
+
return path !== "" && !path.startsWith("..") && !path.includes("../");
|
|
1607
1494
|
};
|
|
1608
|
-
var
|
|
1609
|
-
|
|
1495
|
+
var normalizeRelativePath = (root, path) => relative(root, resolve(root, path));
|
|
1496
|
+
var normalizeRequestedPath = (root, value) => {
|
|
1497
|
+
if (typeof value !== "string" || !value || isAbsolute(value) || value.split(PATH_SEGMENTS).includes("..")) {
|
|
1610
1498
|
return;
|
|
1611
1499
|
}
|
|
1500
|
+
return normalizeRelativePath(root, value);
|
|
1501
|
+
};
|
|
1502
|
+
var git = (cwd, args) => execFileSync2("git", args, {
|
|
1503
|
+
cwd,
|
|
1504
|
+
encoding: "utf8",
|
|
1505
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
1506
|
+
shell: false,
|
|
1507
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1508
|
+
timeout: 5000,
|
|
1509
|
+
windowsHide: true
|
|
1510
|
+
});
|
|
1511
|
+
var repositoryRoot = (cwd) => {
|
|
1612
1512
|
try {
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1513
|
+
return realpath(git(cwd, ["rev-parse", "--show-toplevel"]).trim());
|
|
1514
|
+
} catch {
|
|
1515
|
+
return Promise.resolve(undefined);
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
var untracked = (cwd, path) => {
|
|
1519
|
+
const output = git(cwd, [
|
|
1520
|
+
"ls-files",
|
|
1521
|
+
"--others",
|
|
1522
|
+
"--exclude-standard",
|
|
1523
|
+
"-z",
|
|
1524
|
+
"--",
|
|
1525
|
+
path
|
|
1526
|
+
]);
|
|
1527
|
+
const expected = normalizeRelativePath(cwd, path);
|
|
1528
|
+
return output.split("\x00").filter(Boolean).some((entry) => normalizeRelativePath(cwd, entry) === expected);
|
|
1529
|
+
};
|
|
1530
|
+
var tracked = (cwd, path) => {
|
|
1531
|
+
const output = git(cwd, ["ls-files", "--stage", "-z", "--", path]);
|
|
1532
|
+
const expected = normalizeRelativePath(cwd, path);
|
|
1533
|
+
return output.split("\x00").some((entry) => {
|
|
1534
|
+
if (!entry) {
|
|
1535
|
+
return false;
|
|
1618
1536
|
}
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1537
|
+
const [metadata, name] = entry.split("\t");
|
|
1538
|
+
return name && normalizeRelativePath(cwd, name) === expected && !metadata.startsWith("160000 ");
|
|
1539
|
+
});
|
|
1540
|
+
};
|
|
1541
|
+
var isPermitted = (root, path, kind) => kind === "tracked" ? tracked(root, path) : untracked(root, path);
|
|
1542
|
+
var readAttachment = async (root, normalizedName, redact, available) => {
|
|
1543
|
+
const absolute = resolve(root, normalizedName);
|
|
1544
|
+
if (!(within(root, absolute) && available > 0)) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const stats = await lstat(absolute);
|
|
1548
|
+
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const resolved = await realpath(absolute);
|
|
1552
|
+
if (!within(root, resolved)) {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const flags = constants.O_NOFOLLOW ? constants.O_RDONLY | constants.O_NOFOLLOW : constants.O_RDONLY;
|
|
1556
|
+
const file = await open(resolved, flags);
|
|
1557
|
+
try {
|
|
1558
|
+
const openedStats = await file.stat();
|
|
1559
|
+
if (!openedStats.isFile()) {
|
|
1621
1560
|
return;
|
|
1622
1561
|
}
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
const capped = redactAndCapText(source, maxBytes, redact);
|
|
1629
|
-
return { bytes: Buffer.byteLength(capped, "utf8"), text: capped };
|
|
1630
|
-
} finally {
|
|
1631
|
-
await file.close();
|
|
1562
|
+
const buffer = Buffer.alloc(available + 1);
|
|
1563
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1564
|
+
const raw = buffer.subarray(0, bytesRead).toString("utf8");
|
|
1565
|
+
if (raw.includes("\x00")) {
|
|
1566
|
+
return;
|
|
1632
1567
|
}
|
|
1633
|
-
|
|
1568
|
+
const text = redactAndCapText(raw, available, redact);
|
|
1569
|
+
return {
|
|
1570
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
1571
|
+
path: normalizedName,
|
|
1572
|
+
text
|
|
1573
|
+
};
|
|
1574
|
+
} finally {
|
|
1575
|
+
await file.close();
|
|
1576
|
+
}
|
|
1634
1577
|
};
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1578
|
+
var readFiles = async (cwd, requested, enabled, redact, kind, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => {
|
|
1579
|
+
if (!(enabled && Array.isArray(requested))) {
|
|
1580
|
+
return [];
|
|
1581
|
+
}
|
|
1582
|
+
const root = await repositoryRoot(cwd);
|
|
1583
|
+
if (!root) {
|
|
1584
|
+
return [];
|
|
1585
|
+
}
|
|
1586
|
+
const unique = new Set;
|
|
1587
|
+
const attachments = [];
|
|
1588
|
+
let total = 0;
|
|
1589
|
+
for (const name of requested) {
|
|
1590
|
+
const normalizedName = normalizeRequestedPath(root, name);
|
|
1591
|
+
if (!normalizedName || unique.has(normalizedName)) {
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
unique.add(normalizedName);
|
|
1595
|
+
try {
|
|
1596
|
+
if (!isPermitted(root, normalizedName, kind)) {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
const available = Math.min(ADVISOR_FILE_MAX_BYTES, totalLimit - total);
|
|
1600
|
+
if (available <= 0) {
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
const attachment = await readAttachment(root, normalizedName, redact, available);
|
|
1604
|
+
if (attachment) {
|
|
1605
|
+
attachments.push(attachment);
|
|
1606
|
+
total += attachment.bytes;
|
|
1607
|
+
}
|
|
1608
|
+
} catch {}
|
|
1609
|
+
}
|
|
1610
|
+
return attachments;
|
|
1611
|
+
};
|
|
1612
|
+
var readTrackedFiles = (cwd, requested, enabled, redact, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => readFiles(cwd, requested, enabled, redact, "tracked", totalLimit);
|
|
1613
|
+
var readUntrackedFiles = (cwd, requested, enabled, redact, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => readFiles(cwd, requested, enabled, redact, "untracked", totalLimit);
|
|
1614
|
+
|
|
1615
|
+
// src/preferences.ts
|
|
1616
|
+
import { lstat as lstat2, open as open2, realpath as realpath2 } from "node:fs/promises";
|
|
1617
|
+
import { join as join2, relative as relative2 } from "node:path";
|
|
1618
|
+
var PREFERENCES_MAX_BYTES = 8 * 1024;
|
|
1619
|
+
var PREFERENCES_FILENAME = ["advisor-preferences", "md"].join(".");
|
|
1620
|
+
var inside = (root, candidate) => {
|
|
1621
|
+
const path = relative2(root, candidate);
|
|
1622
|
+
return path === "" || !(path.startsWith("..") || path.includes("../"));
|
|
1623
|
+
};
|
|
1624
|
+
var readProjectPreferences = async (ctx, maxBytes = PREFERENCES_MAX_BYTES, redact = true) => {
|
|
1625
|
+
if (!ctx.isProjectTrusted()) {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
const root = await realpath2(ctx.cwd);
|
|
1630
|
+
const candidate = join2(ctx.cwd, ".pi", PREFERENCES_FILENAME);
|
|
1631
|
+
const stats = await lstat2(candidate);
|
|
1632
|
+
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const resolved = await realpath2(candidate);
|
|
1636
|
+
if (!inside(root, resolved)) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
const file = await open2(resolved, "r");
|
|
1640
|
+
try {
|
|
1641
|
+
const buffer = Buffer.alloc(maxBytes + 1);
|
|
1642
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
1643
|
+
const source = buffer.subarray(0, bytesRead).toString("utf8");
|
|
1644
|
+
const capped = redactAndCapText(source, maxBytes, redact);
|
|
1645
|
+
return { bytes: Buffer.byteLength(capped, "utf8"), text: capped };
|
|
1646
|
+
} finally {
|
|
1647
|
+
await file.close();
|
|
1648
|
+
}
|
|
1649
|
+
} catch {}
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
// src/scout-groups.ts
|
|
1653
|
+
import { createHash } from "node:crypto";
|
|
1654
|
+
|
|
1655
|
+
// src/tool-result-cap.ts
|
|
1656
|
+
var OMITTED_MARKER = "[... omitted tool-result section ...]";
|
|
1657
|
+
var capToolResult = (value, maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES, maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES) => {
|
|
1658
|
+
const lines = value.split(`
|
|
1659
|
+
`);
|
|
1660
|
+
const totalLines = lines.length;
|
|
1661
|
+
const totalBytes = byteLength(value);
|
|
1662
|
+
if ((maxLines === 0 || maxBytes === 0) && value.length > 0) {
|
|
1663
|
+
return {
|
|
1664
|
+
content: "[Tool result omitted: configured limit is zero]",
|
|
1665
|
+
omittedLines: totalLines,
|
|
1666
|
+
totalBytes,
|
|
1667
|
+
totalLines,
|
|
1668
|
+
truncated: true
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
1672
|
+
return {
|
|
1673
|
+
content: value,
|
|
1674
|
+
omittedLines: 0,
|
|
1675
|
+
totalBytes,
|
|
1676
|
+
totalLines,
|
|
1677
|
+
truncated: false
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
const markerBytes = byteLength(OMITTED_MARKER);
|
|
1681
|
+
if (maxBytes < markerBytes || maxLines === 1) {
|
|
1682
|
+
return {
|
|
1683
|
+
content: capUtf8Bytes(OMITTED_MARKER, maxBytes),
|
|
1684
|
+
omittedLines: totalLines,
|
|
1685
|
+
totalBytes,
|
|
1686
|
+
totalLines,
|
|
1687
|
+
truncated: true
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const headCount = Math.floor((maxLines - 1) / 2);
|
|
1691
|
+
const tailCount = maxLines - 1 - headCount;
|
|
1692
|
+
const collect = (candidates, maxEntries, maxContentBytes) => {
|
|
1693
|
+
const selected = [];
|
|
1694
|
+
let used = 0;
|
|
1695
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
1696
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
1697
|
+
if (next > maxContentBytes) {
|
|
1698
|
+
break;
|
|
1699
|
+
}
|
|
1700
|
+
selected.push(line);
|
|
1701
|
+
used = next;
|
|
1702
|
+
}
|
|
1703
|
+
return selected;
|
|
1704
|
+
};
|
|
1705
|
+
const availableBytes = maxBytes - markerBytes - 2;
|
|
1706
|
+
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
1707
|
+
const tail = collect(lines.slice(Math.max(head.length, lines.length - tailCount)), tailCount, availableBytes - byteLength(head.join(`
|
|
1708
|
+
`)));
|
|
1709
|
+
const content = [...head, OMITTED_MARKER, ...tail].join(`
|
|
1710
|
+
`);
|
|
1711
|
+
return {
|
|
1712
|
+
content,
|
|
1713
|
+
omittedLines: Math.max(0, totalLines - head.length - tail.length),
|
|
1714
|
+
totalBytes,
|
|
1715
|
+
totalLines,
|
|
1716
|
+
truncated: true
|
|
1717
|
+
};
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// src/conversation.ts
|
|
1721
|
+
var textFromPart = (part) => {
|
|
1722
|
+
if (typeof part === "string") {
|
|
1723
|
+
return part;
|
|
1724
|
+
}
|
|
1725
|
+
if (!isRecord(part) || part.type !== "text") {
|
|
1726
|
+
return "";
|
|
1727
|
+
}
|
|
1728
|
+
return typeof part.text === "string" ? part.text : "";
|
|
1729
|
+
};
|
|
1730
|
+
var textFrom = (content) => contentParts(content).map(textFromPart).join(`
|
|
1731
|
+
`).trim();
|
|
1732
|
+
var assistantEntry = (message, policies, redact) => {
|
|
1733
|
+
const parts = [];
|
|
1734
|
+
const text = textFrom(message.content);
|
|
1735
|
+
if (text) {
|
|
1736
|
+
parts.push(redact ? redactSecrets(text) : text);
|
|
1737
|
+
}
|
|
1738
|
+
for (const part of contentParts(message.content)) {
|
|
1739
|
+
if (!isRecord(part) || part.type !== "toolCall") {
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
const toolName = typeof part.name === "string" ? part.name : "unknown";
|
|
1743
|
+
const policy = policies[toolName] ?? "full";
|
|
1744
|
+
if (policy === "exclude") {
|
|
1745
|
+
parts.push(`[Tool Call: ${toolName}] (excluded by Advisor tool policy)`);
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (policy === "summary") {
|
|
1749
|
+
parts.push(`[Tool Call: ${toolName}] (arguments omitted by Advisor tool policy: summary)`);
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
const argumentsText = JSON.stringify(part.arguments) ?? "undefined";
|
|
1753
|
+
parts.push(`[Tool Call: ${toolName}(${redact ? redactSecrets(argumentsText) : argumentsText})]`);
|
|
1754
|
+
}
|
|
1755
|
+
return parts.length > 0 ? `Executor: ${parts.join(`
|
|
1756
|
+
`)}` : undefined;
|
|
1757
|
+
};
|
|
1758
|
+
var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1759
|
+
const status = message.isError ? "error" : "success";
|
|
1760
|
+
const toolName = typeof message.toolName === "string" ? message.toolName : "unknown";
|
|
1761
|
+
const policy = policies[toolName] ?? "full";
|
|
1762
|
+
const source = textFrom(message.content);
|
|
1763
|
+
if (policy === "exclude") {
|
|
1764
|
+
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
1765
|
+
}
|
|
1766
|
+
if (policy === "summary") {
|
|
1767
|
+
const capped = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
|
|
1768
|
+
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped.totalLines} lines, ${capped.totalBytes} bytes; source output was${capped.truncated ? "" : " not"} truncated)`;
|
|
1769
|
+
}
|
|
1770
|
+
const disclosed = redact ? redactSecrets(source) : source;
|
|
1771
|
+
const capped = capToolResult(disclosed, toolResultMaxLines, toolResultMaxBytes);
|
|
1772
|
+
return `[Tool Result for ${toolName}] (${message.isError ? "Error " : ""}output):
|
|
1773
|
+
${capped.content}`;
|
|
1774
|
+
};
|
|
1775
|
+
var conversationEntry = (entry, toolResultMaxLines, toolResultMaxBytes, policies, redact) => {
|
|
1776
|
+
if (!isRecord(entry)) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
if (entry.type === "compaction" && typeof entry.summary === "string") {
|
|
1780
|
+
return `[System Compaction Summary]: ${redact ? redactSecrets(entry.summary) : entry.summary}`;
|
|
1781
|
+
}
|
|
1782
|
+
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
const { message } = entry;
|
|
1786
|
+
if (message.role === "user") {
|
|
1787
|
+
const text = textFrom(message.content);
|
|
1788
|
+
return text ? `User: ${redact ? redactSecrets(text) : text}` : undefined;
|
|
1789
|
+
}
|
|
1790
|
+
if (message.role === "assistant") {
|
|
1791
|
+
return assistantEntry(message, policies, redact);
|
|
1792
|
+
}
|
|
1793
|
+
if (message.role === "toolResult" || message.role === "tool") {
|
|
1794
|
+
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
var omissionMarker = (omitted) => `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]`;
|
|
1798
|
+
var selectRecentEntries = (entries, maxChars) => {
|
|
1799
|
+
const separator = `
|
|
1800
|
+
|
|
1801
|
+
`;
|
|
1802
|
+
const joined = entries.join(separator);
|
|
1803
|
+
if (joined.length <= maxChars || maxChars === Number.MAX_SAFE_INTEGER) {
|
|
1804
|
+
return joined;
|
|
1805
|
+
}
|
|
1806
|
+
const newestTruncated = "[Newest entry truncated]";
|
|
1807
|
+
if (entries.length === 1) {
|
|
1808
|
+
const prefix = `${newestTruncated}${separator}`;
|
|
1809
|
+
return `${prefix}${entries[0].slice(0, Math.max(0, maxChars - prefix.length))}`.slice(0, maxChars);
|
|
1810
|
+
}
|
|
1811
|
+
const selected = [];
|
|
1812
|
+
let selectedLength = 0;
|
|
1813
|
+
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
1814
|
+
const entry = entries[index];
|
|
1815
|
+
const candidateCount = selected.length + 1;
|
|
1816
|
+
const omitted = entries.length - candidateCount;
|
|
1817
|
+
const candidateLength = selectedLength + entry.length + (selected.length > 0 ? separator.length : 0);
|
|
1818
|
+
if (omissionMarker(omitted).length + separator.length + candidateLength > maxChars) {
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
selected.unshift(entry);
|
|
1822
|
+
selectedLength = candidateLength;
|
|
1823
|
+
}
|
|
1824
|
+
const omitted = entries.length - Math.max(1, selected.length);
|
|
1825
|
+
const marker = omissionMarker(omitted);
|
|
1826
|
+
if (selected.length > 0) {
|
|
1827
|
+
return `${marker}${separator}${selected.join(separator)}`;
|
|
1828
|
+
}
|
|
1829
|
+
const prefix = `${marker}${separator}${newestTruncated}${separator}`;
|
|
1830
|
+
return `${prefix}${entries.at(-1)?.slice(0, Math.max(0, maxChars - prefix.length)) ?? ""}`.slice(0, maxChars);
|
|
1831
|
+
};
|
|
1832
|
+
var recentConversation = (ctx, maxChars = 15000, toolResultMaxLines = advisorToolResultMaxLinesRef, toolResultMaxBytes = advisorToolResultMaxBytesRef, policies = advisorToolPoliciesRef, redact = advisorRedactSecretsRef) => {
|
|
1833
|
+
if (maxChars === 0) {
|
|
1834
|
+
return "";
|
|
1835
|
+
}
|
|
1836
|
+
const entries = ctx.sessionManager.getBranch().map((entry) => conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes, policies, redact)).filter((entry) => entry !== undefined);
|
|
1837
|
+
return selectRecentEntries(entries, maxChars);
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
// src/scout-protocol.ts
|
|
1841
|
+
var invalid = (message) => ({
|
|
1842
|
+
message,
|
|
1843
|
+
ok: false,
|
|
1844
|
+
reason: "invalid-protocol"
|
|
1845
|
+
});
|
|
1846
|
+
var toolCalls = (message) => contentParts(message.content).filter((part) => isRecord(part) && part.type === "toolCall");
|
|
1847
|
+
var toolCallId = (part) => typeof part.id === "string" ? part.id : undefined;
|
|
1848
|
+
var indexToolCalls = (entries) => {
|
|
1849
|
+
const callOwners = new Map;
|
|
1850
|
+
const resultsByCall = new Map;
|
|
1851
|
+
const state = { latestUserIndex: -1 };
|
|
1852
|
+
for (let index = 0;index < entries.length; index += 1) {
|
|
1853
|
+
const failure = indexEntry(entries[index], index, callOwners, resultsByCall, state);
|
|
1854
|
+
if (failure) {
|
|
1855
|
+
return failure;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
for (const [id, results] of resultsByCall) {
|
|
1859
|
+
if (results.length > 1) {
|
|
1860
|
+
return invalid(`Tool call ${id} has duplicate result messages.`);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return {
|
|
1864
|
+
index: {
|
|
1865
|
+
callOwners,
|
|
1866
|
+
latestUserIndex: state.latestUserIndex,
|
|
1867
|
+
resultsByCall
|
|
1868
|
+
},
|
|
1869
|
+
ok: true
|
|
1870
|
+
};
|
|
1871
|
+
};
|
|
1872
|
+
var indexEntry = (entry, index, callOwners, resultsByCall, state) => {
|
|
1873
|
+
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
if (entry.message.role === "user" && textFrom(entry.message.content)) {
|
|
1877
|
+
state.latestUserIndex = index;
|
|
1878
|
+
}
|
|
1879
|
+
if (entry.message.role === "assistant") {
|
|
1880
|
+
return indexAssistantCalls(entry.message, index, callOwners);
|
|
1881
|
+
}
|
|
1882
|
+
if (entry.message.role === "toolResult") {
|
|
1883
|
+
return indexToolResult(entry, entry.message, index, resultsByCall);
|
|
1884
|
+
}
|
|
1885
|
+
return;
|
|
1886
|
+
};
|
|
1887
|
+
var indexToolResult = (entry, message, index, resultsByCall) => {
|
|
1888
|
+
const id = message.toolCallId;
|
|
1889
|
+
if (typeof id !== "string") {
|
|
1890
|
+
return invalid(`Tool result at context entry ${index} has no tool-call ID.`);
|
|
1891
|
+
}
|
|
1892
|
+
const results = resultsByCall.get(id) ?? [];
|
|
1893
|
+
results.push({ entry, index });
|
|
1894
|
+
resultsByCall.set(id, results);
|
|
1895
|
+
return;
|
|
1896
|
+
};
|
|
1897
|
+
var indexAssistantCalls = (message, index, callOwners) => {
|
|
1898
|
+
for (const call of toolCalls(message)) {
|
|
1899
|
+
const id = toolCallId(call);
|
|
1900
|
+
if (!id || callOwners.has(id)) {
|
|
1901
|
+
return invalid(`Assistant tool calls at context entry ${index} have missing or duplicate IDs.`);
|
|
1902
|
+
}
|
|
1903
|
+
callOwners.set(id, {
|
|
1904
|
+
index,
|
|
1905
|
+
name: typeof call.name === "string" ? call.name : "unknown"
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
return;
|
|
1909
|
+
};
|
|
1910
|
+
|
|
1911
|
+
// src/scout-types.ts
|
|
1912
|
+
var SCOUT_MANIFEST_MAX_BYTES = 64 * 1024;
|
|
1639
1913
|
var SCOUT_MANIFEST_MAX_GROUPS = 64;
|
|
1640
1914
|
var SCOUT_GROUP_MAX_BYTES = 24 * 1024;
|
|
1641
1915
|
var SCOUT_LABEL_MAX_CHARS = 160;
|
|
1642
1916
|
var SCOUT_SELECTION_MAX_IDS = 32;
|
|
1643
1917
|
var SCOUT_SYNTHESIS_MAX_BYTES = 4 * 1024;
|
|
1644
|
-
|
|
1645
|
-
|
|
1918
|
+
|
|
1919
|
+
// src/scout-groups.ts
|
|
1646
1920
|
var SPEAKER_PREFIX = /^(User|Executor):\s*/;
|
|
1647
|
-
var contentParts2 = (content) => Array.isArray(content) ? content : [];
|
|
1648
|
-
var toolCalls = (message) => contentParts2(message.content).filter((part) => isRecord2(part) && part.type === "toolCall");
|
|
1649
|
-
var toolCallId = (part) => typeof part.id === "string" ? part.id : undefined;
|
|
1650
1921
|
var boundedLabel = (value) => [...value.replace(/\s+/g, " ").trim()].slice(0, SCOUT_LABEL_MAX_CHARS).join("");
|
|
1651
1922
|
var labelFor = (kind, content) => {
|
|
1652
1923
|
const preview = boundedLabel(content.replace(SPEAKER_PREFIX, ""));
|
|
@@ -1660,16 +1931,17 @@ var labelFor = (kind, content) => {
|
|
|
1660
1931
|
return boundedLabel(`${prefix[kind]}: ${preview || "(no text)"}`);
|
|
1661
1932
|
};
|
|
1662
1933
|
var stableId = (index, entryIds, kind, content) => `g_${createHash("sha256").update(JSON.stringify([index, entryIds, kind, content])).digest("hex").slice(0, 16)}`;
|
|
1663
|
-
var
|
|
1934
|
+
var groupWire = (group) => ({
|
|
1664
1935
|
bytes: group.bytes,
|
|
1665
1936
|
content: group.content,
|
|
1666
1937
|
id: group.id,
|
|
1667
1938
|
kind: group.kind,
|
|
1668
1939
|
label: group.label,
|
|
1669
1940
|
required: group.required
|
|
1670
|
-
})
|
|
1941
|
+
});
|
|
1942
|
+
var groupWireBytes = (group) => byteLength(JSON.stringify(groupWire(group)));
|
|
1671
1943
|
var createGroup = (originalIndex, entryIds, kind, content, required) => ({
|
|
1672
|
-
bytes:
|
|
1944
|
+
bytes: byteLength(content),
|
|
1673
1945
|
content,
|
|
1674
1946
|
id: stableId(originalIndex, entryIds, kind, content),
|
|
1675
1947
|
kind,
|
|
@@ -1678,7 +1950,7 @@ var createGroup = (originalIndex, entryIds, kind, content, required) => ({
|
|
|
1678
1950
|
required
|
|
1679
1951
|
});
|
|
1680
1952
|
var pendingAdvisorArguments = (value) => {
|
|
1681
|
-
if (!
|
|
1953
|
+
if (!isRecord(value)) {
|
|
1682
1954
|
return {};
|
|
1683
1955
|
}
|
|
1684
1956
|
const allowed = {};
|
|
@@ -1690,241 +1962,166 @@ var pendingAdvisorArguments = (value) => {
|
|
|
1690
1962
|
return allowed;
|
|
1691
1963
|
};
|
|
1692
1964
|
var pendingInvocationDisclosure = (entry, invocationId, toolResultMaxLines, toolResultMaxBytes, policies, redact, disclosed) => {
|
|
1693
|
-
if (!
|
|
1965
|
+
if (!isRecord(entry.message)) {
|
|
1694
1966
|
return disclosed;
|
|
1695
1967
|
}
|
|
1696
|
-
const content =
|
|
1697
|
-
if (!
|
|
1968
|
+
const content = contentParts(entry.message.content).map((part) => {
|
|
1969
|
+
if (!isRecord(part) || part.type !== "toolCall" || toolCallId(part) !== invocationId || part.name !== "ask_advisor") {
|
|
1698
1970
|
return part;
|
|
1699
1971
|
}
|
|
1700
1972
|
return { ...part, arguments: pendingAdvisorArguments(part.arguments) };
|
|
1701
1973
|
});
|
|
1702
1974
|
return conversationEntry({ ...entry, message: { ...entry.message, content } }, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
1703
1975
|
};
|
|
1704
|
-
var
|
|
1705
|
-
const
|
|
1706
|
-
const
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1976
|
+
var toolExchangeGroup = (entry, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
1977
|
+
const calls = toolCalls(entry.message);
|
|
1978
|
+
const callIds = calls.map(toolCallId);
|
|
1979
|
+
const adjacentFailure = adjacentResultMismatch(immediate, index, callIds, indexed.callOwners);
|
|
1980
|
+
if (adjacentFailure) {
|
|
1981
|
+
return { kind: "invalid", message: adjacentFailure };
|
|
1982
|
+
}
|
|
1983
|
+
const missing = new Set;
|
|
1984
|
+
const resultParts = [];
|
|
1985
|
+
const resultEntryIds = [];
|
|
1986
|
+
const matchFailure = collectResults(calls, callIds, index, indexed.resultsByCall, consumedResultIndexes, caps, missing, resultParts, resultEntryIds);
|
|
1987
|
+
if (matchFailure) {
|
|
1988
|
+
return { kind: "invalid", message: matchFailure };
|
|
1989
|
+
}
|
|
1990
|
+
if (missing.size > 0) {
|
|
1991
|
+
return missingOutcome(entry, index, entryId, disclosed, callIds, missing, resultParts, resultEntryIds, caps);
|
|
1992
|
+
}
|
|
1993
|
+
return {
|
|
1994
|
+
group: createGroup(index, [entryId, ...resultEntryIds], "tool-exchange", [disclosed, ...resultParts].join(`
|
|
1995
|
+
|
|
1996
|
+
`), false),
|
|
1997
|
+
kind: "group"
|
|
1998
|
+
};
|
|
1999
|
+
};
|
|
2000
|
+
var adjacentResultMismatch = (immediate, index, callIds, callOwners) => {
|
|
2001
|
+
const next = immediate;
|
|
2002
|
+
if (next?.type === "message" && isRecord(next.message) && next.message.role === "toolResult" && typeof next.message.toolCallId === "string" && !callIds.includes(next.message.toolCallId) && !callOwners.has(next.message.toolCallId)) {
|
|
2003
|
+
return `Tool result at context entry ${index + 1} does not match its adjacent assistant group.`;
|
|
2004
|
+
}
|
|
2005
|
+
return;
|
|
2006
|
+
};
|
|
2007
|
+
var collectResults = (calls, callIds, index, resultsByCall, consumedResultIndexes, caps, missing, resultParts, resultEntryIds) => {
|
|
2008
|
+
for (const [callIndex, callId] of callIds.entries()) {
|
|
2009
|
+
const resultMatch = resultsByCall.get(callId)?.[0];
|
|
2010
|
+
if (!resultMatch) {
|
|
2011
|
+
missing.add(callId);
|
|
1723
2012
|
continue;
|
|
1724
2013
|
}
|
|
1725
|
-
if (
|
|
1726
|
-
|
|
2014
|
+
if (resultMatch.index <= index) {
|
|
2015
|
+
return `Tool result at context entry ${resultMatch.index} precedes its assistant call.`;
|
|
1727
2016
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
return {
|
|
1733
|
-
message: `Assistant tool calls at context entry ${index} have missing or duplicate IDs.`,
|
|
1734
|
-
ok: false,
|
|
1735
|
-
reason: "invalid-protocol"
|
|
1736
|
-
};
|
|
1737
|
-
}
|
|
1738
|
-
callOwners.set(id, {
|
|
1739
|
-
index,
|
|
1740
|
-
name: typeof call.name === "string" ? call.name : "unknown"
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
} else if (entry.message.role === "toolResult") {
|
|
1744
|
-
const id = entry.message.toolCallId;
|
|
1745
|
-
if (typeof id !== "string") {
|
|
1746
|
-
return {
|
|
1747
|
-
message: `Tool result at context entry ${index} has no tool-call ID.`,
|
|
1748
|
-
ok: false,
|
|
1749
|
-
reason: "invalid-protocol"
|
|
1750
|
-
};
|
|
1751
|
-
}
|
|
1752
|
-
const results = resultsByCall.get(id) ?? [];
|
|
1753
|
-
results.push({ entry, index });
|
|
1754
|
-
resultsByCall.set(id, results);
|
|
2017
|
+
const resultMessage = resultMatch.entry.message;
|
|
2018
|
+
const expectedName = typeof calls[callIndex].name === "string" ? calls[callIndex].name : "unknown";
|
|
2019
|
+
if (!isRecord(resultMessage) || resultMessage.toolName !== expectedName) {
|
|
2020
|
+
return `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`;
|
|
1755
2021
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
if (
|
|
1759
|
-
|
|
1760
|
-
message: `Tool call ${id} has duplicate result messages.`,
|
|
1761
|
-
ok: false,
|
|
1762
|
-
reason: "invalid-protocol"
|
|
1763
|
-
};
|
|
2022
|
+
consumedResultIndexes.add(resultMatch.index);
|
|
2023
|
+
const resultText = conversationEntry(resultMatch.entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
2024
|
+
if (resultText) {
|
|
2025
|
+
resultParts.push(resultText);
|
|
1764
2026
|
}
|
|
2027
|
+
resultEntryIds.push(typeof resultMatch.entry.id === "string" ? resultMatch.entry.id : String(resultMatch.index));
|
|
1765
2028
|
}
|
|
2029
|
+
return;
|
|
2030
|
+
};
|
|
2031
|
+
var missingOutcome = (entry, index, entryId, disclosed, callIds, missing, resultParts, resultEntryIds, caps) => {
|
|
2032
|
+
const { currentInvocationId } = caps;
|
|
2033
|
+
const pendingCurrentInvocation = currentInvocationId !== undefined && missing.size === 1 && missing.has(currentInvocationId) && callIds.includes(currentInvocationId);
|
|
2034
|
+
if (!pendingCurrentInvocation) {
|
|
2035
|
+
return {
|
|
2036
|
+
bytes: byteLength([disclosed, ...resultParts].join(`
|
|
2037
|
+
|
|
2038
|
+
`)),
|
|
2039
|
+
kind: "omitted"
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
const pendingDisclosed = pendingInvocationDisclosure(entry, currentInvocationId, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact, disclosed);
|
|
2043
|
+
return {
|
|
2044
|
+
group: createGroup(index, [entryId, ...resultEntryIds], "pending-invocation", [pendingDisclosed ?? disclosed, ...resultParts].join(`
|
|
2045
|
+
|
|
2046
|
+
`), true),
|
|
2047
|
+
kind: "group"
|
|
2048
|
+
};
|
|
2049
|
+
};
|
|
2050
|
+
var buildGroups = (entries, indexed, caps) => {
|
|
1766
2051
|
const groups = [];
|
|
1767
2052
|
const consumedResultIndexes = new Set;
|
|
1768
2053
|
let protocolOmittedBytes = 0;
|
|
1769
2054
|
let protocolOmittedCount = 0;
|
|
1770
2055
|
for (let index = 0;index < entries.length; index += 1) {
|
|
1771
2056
|
const entry = entries[index];
|
|
1772
|
-
const disclosed = conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
2057
|
+
const disclosed = conversationEntry(entry, caps.toolResultMaxLines, caps.toolResultMaxBytes, caps.policies, caps.redact);
|
|
1773
2058
|
if (!disclosed) {
|
|
1774
2059
|
continue;
|
|
1775
2060
|
}
|
|
1776
|
-
const
|
|
1777
|
-
if (
|
|
1778
|
-
|
|
1779
|
-
continue;
|
|
2061
|
+
const outcome = groupForEntry(entry, index, disclosed, entries[index + 1], indexed, consumedResultIndexes, caps);
|
|
2062
|
+
if (outcome.kind === "invalid") {
|
|
2063
|
+
return invalid(outcome.message);
|
|
1780
2064
|
}
|
|
1781
|
-
|
|
1782
|
-
if (message.role === "user") {
|
|
1783
|
-
groups.push(createGroup(index, [entryId], "user", disclosed, index === latestUserIndex));
|
|
1784
|
-
continue;
|
|
1785
|
-
}
|
|
1786
|
-
if (message.role === "toolResult") {
|
|
1787
|
-
if (consumedResultIndexes.has(index)) {
|
|
1788
|
-
continue;
|
|
1789
|
-
}
|
|
1790
|
-
const resultId = message.toolCallId;
|
|
1791
|
-
const owner = typeof resultId === "string" ? callOwners.get(resultId) : undefined;
|
|
1792
|
-
if (owner) {
|
|
1793
|
-
return {
|
|
1794
|
-
message: `Tool result at context entry ${index} precedes or conflicts with its retained call.`,
|
|
1795
|
-
ok: false,
|
|
1796
|
-
reason: "invalid-protocol"
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
2065
|
+
if (outcome.kind === "omitted") {
|
|
1799
2066
|
protocolOmittedCount += 1;
|
|
1800
|
-
protocolOmittedBytes +=
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
if (message.role !== "assistant") {
|
|
1804
|
-
continue;
|
|
1805
|
-
}
|
|
1806
|
-
const calls = toolCalls(message);
|
|
1807
|
-
if (calls.length === 0) {
|
|
1808
|
-
groups.push(createGroup(index, [entryId], "assistant", disclosed, false));
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
const callIds = calls.map(toolCallId);
|
|
1812
|
-
const immediate = entries[index + 1];
|
|
1813
|
-
if (immediate?.type === "message" && isRecord2(immediate.message) && immediate.message.role === "toolResult" && typeof immediate.message.toolCallId === "string" && !callIds.includes(immediate.message.toolCallId) && !callOwners.has(immediate.message.toolCallId)) {
|
|
1814
|
-
return {
|
|
1815
|
-
message: `Tool result at context entry ${index + 1} does not match its adjacent assistant group.`,
|
|
1816
|
-
ok: false,
|
|
1817
|
-
reason: "invalid-protocol"
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
const missing = new Set;
|
|
1821
|
-
const resultParts = [];
|
|
1822
|
-
const resultEntryIds = [];
|
|
1823
|
-
for (const [callIndex, callId] of callIds.entries()) {
|
|
1824
|
-
const resultMatch = resultsByCall.get(callId)?.[0];
|
|
1825
|
-
if (!resultMatch) {
|
|
1826
|
-
missing.add(callId);
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
1829
|
-
if (resultMatch.index <= index) {
|
|
1830
|
-
return {
|
|
1831
|
-
message: `Tool result at context entry ${resultMatch.index} precedes its assistant call.`,
|
|
1832
|
-
ok: false,
|
|
1833
|
-
reason: "invalid-protocol"
|
|
1834
|
-
};
|
|
1835
|
-
}
|
|
1836
|
-
const resultMessage = resultMatch.entry.message;
|
|
1837
|
-
const expectedName = typeof calls[callIndex].name === "string" ? calls[callIndex].name : "unknown";
|
|
1838
|
-
if (!isRecord2(resultMessage) || resultMessage.toolName !== expectedName) {
|
|
1839
|
-
return {
|
|
1840
|
-
message: `Tool result at context entry ${resultMatch.index} conflicts with call ${callId}.`,
|
|
1841
|
-
ok: false,
|
|
1842
|
-
reason: "invalid-protocol"
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
consumedResultIndexes.add(resultMatch.index);
|
|
1846
|
-
const resultText = conversationEntry(resultMatch.entry, toolResultMaxLines, toolResultMaxBytes, policies, redact);
|
|
1847
|
-
if (resultText) {
|
|
1848
|
-
resultParts.push(resultText);
|
|
1849
|
-
}
|
|
1850
|
-
resultEntryIds.push(typeof resultMatch.entry.id === "string" ? resultMatch.entry.id : String(resultMatch.index));
|
|
1851
|
-
}
|
|
1852
|
-
if (missing.size > 0) {
|
|
1853
|
-
const { currentInvocationId } = options;
|
|
1854
|
-
const pendingCurrentInvocation = currentInvocationId !== undefined && missing.size === 1 && missing.has(currentInvocationId) && callIds.includes(currentInvocationId);
|
|
1855
|
-
if (!pendingCurrentInvocation) {
|
|
1856
|
-
protocolOmittedCount += 1;
|
|
1857
|
-
protocolOmittedBytes += byteLength2([disclosed, ...resultParts].join(`
|
|
1858
|
-
|
|
1859
|
-
`));
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
const pendingDisclosed = pendingInvocationDisclosure(entry, currentInvocationId, toolResultMaxLines, toolResultMaxBytes, policies, redact, disclosed);
|
|
1863
|
-
groups.push(createGroup(index, [entryId, ...resultEntryIds], "pending-invocation", [pendingDisclosed ?? disclosed, ...resultParts].join(`
|
|
1864
|
-
|
|
1865
|
-
`), true));
|
|
1866
|
-
continue;
|
|
2067
|
+
protocolOmittedBytes += outcome.bytes;
|
|
2068
|
+
} else if (outcome.kind === "group") {
|
|
2069
|
+
groups.push(outcome.group);
|
|
1867
2070
|
}
|
|
1868
|
-
groups.push(createGroup(index, [entryId, ...resultEntryIds], "tool-exchange", [disclosed, ...resultParts].join(`
|
|
1869
|
-
|
|
1870
|
-
`), false));
|
|
1871
2071
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2072
|
+
return {
|
|
2073
|
+
groups,
|
|
2074
|
+
ok: true,
|
|
2075
|
+
protocolOmittedBytes,
|
|
2076
|
+
protocolOmittedCount
|
|
2077
|
+
};
|
|
2078
|
+
};
|
|
2079
|
+
var groupForEntry = (entry, index, disclosed, immediate, indexed, consumedResultIndexes, caps) => {
|
|
2080
|
+
const entryId = typeof entry.id === "string" ? entry.id : String(index);
|
|
2081
|
+
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
1875
2082
|
return {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
availableCount: 0,
|
|
1879
|
-
groups: [],
|
|
1880
|
-
omittedBytes: 0,
|
|
1881
|
-
omittedCount: 0
|
|
1882
|
-
},
|
|
1883
|
-
ok: true
|
|
2083
|
+
group: createGroup(index, [entryId], "compaction", disclosed, false),
|
|
2084
|
+
kind: "group"
|
|
1884
2085
|
};
|
|
1885
2086
|
}
|
|
1886
|
-
const
|
|
1887
|
-
if (
|
|
2087
|
+
const { message } = entry;
|
|
2088
|
+
if (message.role === "user") {
|
|
1888
2089
|
return {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
reason: "required-group-overflow"
|
|
2090
|
+
group: createGroup(index, [entryId], "user", disclosed, index === indexed.latestUserIndex),
|
|
2091
|
+
kind: "group"
|
|
1892
2092
|
};
|
|
1893
2093
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
2094
|
+
if (message.role === "toolResult") {
|
|
2095
|
+
return toolResultOutcome(message, index, disclosed, indexed.callOwners, consumedResultIndexes);
|
|
2096
|
+
}
|
|
2097
|
+
if (message.role !== "assistant") {
|
|
2098
|
+
return { kind: "skipped" };
|
|
2099
|
+
}
|
|
2100
|
+
if (!hasCalls(message)) {
|
|
1896
2101
|
return {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
reason: "required-group-overflow"
|
|
2102
|
+
group: createGroup(index, [entryId], "assistant", disclosed, false),
|
|
2103
|
+
kind: "group"
|
|
1900
2104
|
};
|
|
1901
2105
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
return {
|
|
1908
|
-
message: "Required Scout context exceeds fixed manifest limits.",
|
|
1909
|
-
ok: false,
|
|
1910
|
-
reason: "required-group-overflow"
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
selected.splice(optionalIndex, 1);
|
|
2106
|
+
return toolExchangeGroup(entry, index, entryId, disclosed, immediate, indexed, consumedResultIndexes, caps);
|
|
2107
|
+
};
|
|
2108
|
+
var toolResultOutcome = (message, index, disclosed, callOwners, consumedResultIndexes) => {
|
|
2109
|
+
if (consumedResultIndexes.has(index)) {
|
|
2110
|
+
return { kind: "skipped" };
|
|
1914
2111
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
omittedBytes: protocolOmittedBytes + omitted.reduce((sum, group) => sum + group.bytes, 0),
|
|
1923
|
-
omittedCount: protocolOmittedCount + omitted.length
|
|
1924
|
-
},
|
|
1925
|
-
ok: true
|
|
1926
|
-
};
|
|
2112
|
+
if (ownerOf(message, callOwners)) {
|
|
2113
|
+
return {
|
|
2114
|
+
kind: "invalid",
|
|
2115
|
+
message: `Tool result at context entry ${index} precedes or conflicts with its retained call.`
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
return { bytes: byteLength(disclosed), kind: "omitted" };
|
|
1927
2119
|
};
|
|
2120
|
+
var ownerOf = (message, callOwners) => typeof message.toolCallId === "string" ? callOwners.get(message.toolCallId) : undefined;
|
|
2121
|
+
var hasCalls = (message) => contentPartsOf(message).some((part) => isRecord(part) && part.type === "toolCall");
|
|
2122
|
+
var contentPartsOf = (message) => Array.isArray(message.content) ? message.content : [];
|
|
2123
|
+
|
|
2124
|
+
// src/scout-reconstruct.ts
|
|
1928
2125
|
var prefixWithinCharBudget = (value, maxChars) => {
|
|
1929
2126
|
let result = "";
|
|
1930
2127
|
for (const character of value) {
|
|
@@ -1965,13 +2162,12 @@ ${synthesis.trim()}` : undefined;
|
|
|
1965
2162
|
// src/usage.ts
|
|
1966
2163
|
var finite = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
1967
2164
|
var add = (left, right) => left === undefined || right === undefined ? left ?? right : left + right;
|
|
1968
|
-
var isRecord3 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1969
2165
|
var costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
|
|
1970
2166
|
var snapshotAdvisorUsage = (usage) => {
|
|
1971
|
-
if (!
|
|
2167
|
+
if (!isRecord(usage)) {
|
|
1972
2168
|
return;
|
|
1973
2169
|
}
|
|
1974
|
-
const cost =
|
|
2170
|
+
const cost = isRecord(usage.cost) ? usage.cost : undefined;
|
|
1975
2171
|
const snapshot = {
|
|
1976
2172
|
cacheRead: finite(usage.cacheRead),
|
|
1977
2173
|
cacheWrite: finite(usage.cacheWrite),
|
|
@@ -1986,10 +2182,10 @@ var snapshotAdvisorUsage = (usage) => {
|
|
|
1986
2182
|
var advisorUsageCost = (usage) => snapshotAdvisorUsage(usage)?.cost;
|
|
1987
2183
|
var advisorUsageForPi = (usage) => {
|
|
1988
2184
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
1989
|
-
if (!(snapshot &&
|
|
2185
|
+
if (!(snapshot && isRecord(usage))) {
|
|
1990
2186
|
return;
|
|
1991
2187
|
}
|
|
1992
|
-
const cost =
|
|
2188
|
+
const cost = isRecord(usage.cost) ? usage.cost : undefined;
|
|
1993
2189
|
const input = snapshot.input ?? 0;
|
|
1994
2190
|
const output = snapshot.output ?? 0;
|
|
1995
2191
|
const cacheRead = snapshot.cacheRead ?? 0;
|
|
@@ -2105,20 +2301,13 @@ var defaultDependencies = {
|
|
|
2105
2301
|
collect: collectTextStream,
|
|
2106
2302
|
resolve: resolveConfiguredModel
|
|
2107
2303
|
};
|
|
2108
|
-
var
|
|
2304
|
+
var byteLength2 = (value) => Buffer.byteLength(value, "utf8");
|
|
2109
2305
|
var AUTH_ERROR_PATTERN = /api key|auth|login|credential/i;
|
|
2110
2306
|
var manifestMessage = (manifest) => ({
|
|
2111
2307
|
content: [
|
|
2112
2308
|
{
|
|
2113
2309
|
text: JSON.stringify({
|
|
2114
|
-
groups: manifest.groups.map(
|
|
2115
|
-
bytes: group.bytes,
|
|
2116
|
-
content: group.content,
|
|
2117
|
-
id: group.id,
|
|
2118
|
-
kind: group.kind,
|
|
2119
|
-
label: group.label,
|
|
2120
|
-
required: group.required
|
|
2121
|
-
})),
|
|
2310
|
+
groups: manifest.groups.map(groupWire),
|
|
2122
2311
|
omittedBeforeScout: {
|
|
2123
2312
|
bytes: manifest.omittedBytes,
|
|
2124
2313
|
groups: manifest.omittedCount
|
|
@@ -2168,7 +2357,7 @@ var parseScoutSelection = (text, manifest) => {
|
|
|
2168
2357
|
if (typeof record.synthesis !== "string") {
|
|
2169
2358
|
throw new Error("Scout synthesis must be a string.");
|
|
2170
2359
|
}
|
|
2171
|
-
if (
|
|
2360
|
+
if (byteLength2(record.synthesis) > SCOUT_SYNTHESIS_MAX_BYTES) {
|
|
2172
2361
|
throw new Error(`Scout synthesis exceeds ${SCOUT_SYNTHESIS_MAX_BYTES} UTF-8 bytes.`);
|
|
2173
2362
|
}
|
|
2174
2363
|
return {
|
|
@@ -2197,32 +2386,36 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2197
2386
|
const publish = (event) => {
|
|
2198
2387
|
onEvent?.(event);
|
|
2199
2388
|
};
|
|
2200
|
-
|
|
2389
|
+
const cancelled = () => {
|
|
2201
2390
|
publish({ type: "cancelled" });
|
|
2202
2391
|
return { cancelled: true, ok: false };
|
|
2392
|
+
};
|
|
2393
|
+
const fallback = (category, message, usage) => {
|
|
2394
|
+
const outcome = {
|
|
2395
|
+
category,
|
|
2396
|
+
message,
|
|
2397
|
+
metrics: usage === undefined ? baseMetrics(manifest, startedAt) : { ...baseMetrics(manifest, startedAt), usage },
|
|
2398
|
+
model: executorRef,
|
|
2399
|
+
ok: false
|
|
2400
|
+
};
|
|
2401
|
+
publish({ outcome, type: "fallback" });
|
|
2402
|
+
return outcome;
|
|
2403
|
+
};
|
|
2404
|
+
if (parentSignal?.aborted) {
|
|
2405
|
+
return cancelled();
|
|
2203
2406
|
}
|
|
2204
2407
|
let resolved;
|
|
2205
2408
|
try {
|
|
2206
2409
|
resolved = await dependencies.resolve(ctx, executorRef, "Scout");
|
|
2207
2410
|
} catch (error) {
|
|
2208
2411
|
if (parentSignal?.aborted) {
|
|
2209
|
-
|
|
2210
|
-
return { cancelled: true, ok: false };
|
|
2412
|
+
return cancelled();
|
|
2211
2413
|
}
|
|
2212
2414
|
const message = error instanceof Error ? error.message : String(error);
|
|
2213
|
-
|
|
2214
|
-
category: classifyResolutionError(message),
|
|
2215
|
-
message,
|
|
2216
|
-
metrics: baseMetrics(manifest, startedAt),
|
|
2217
|
-
model: executorRef,
|
|
2218
|
-
ok: false
|
|
2219
|
-
};
|
|
2220
|
-
publish({ outcome, type: "fallback" });
|
|
2221
|
-
return outcome;
|
|
2415
|
+
return fallback(classifyResolutionError(message), message);
|
|
2222
2416
|
}
|
|
2223
2417
|
if (parentSignal?.aborted) {
|
|
2224
|
-
|
|
2225
|
-
return { cancelled: true, ok: false };
|
|
2418
|
+
return cancelled();
|
|
2226
2419
|
}
|
|
2227
2420
|
publish({ model: executorRef, type: "call" });
|
|
2228
2421
|
const controller = new AbortController;
|
|
@@ -2242,6 +2435,11 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2242
2435
|
once: true
|
|
2243
2436
|
});
|
|
2244
2437
|
});
|
|
2438
|
+
const teardown = () => {
|
|
2439
|
+
clearTimeout(timer);
|
|
2440
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2441
|
+
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2442
|
+
};
|
|
2245
2443
|
let streamed;
|
|
2246
2444
|
try {
|
|
2247
2445
|
const collection = dependencies.collect(resolved, {
|
|
@@ -2255,50 +2453,25 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2255
2453
|
signal: controller.signal,
|
|
2256
2454
|
systemPrompt: SCOUT_SYSTEM
|
|
2257
2455
|
});
|
|
2258
|
-
streamed = await Promise.race([collection, abortPromise]);
|
|
2259
|
-
} catch (error) {
|
|
2260
|
-
|
|
2261
|
-
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2262
|
-
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2456
|
+
streamed = await Promise.race([collection, abortPromise]);
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
teardown();
|
|
2263
2459
|
if (parentSignal?.aborted) {
|
|
2264
|
-
|
|
2265
|
-
return { cancelled: true, ok: false };
|
|
2460
|
+
return cancelled();
|
|
2266
2461
|
}
|
|
2267
2462
|
const message = error instanceof Error ? error.message : String(error);
|
|
2268
|
-
|
|
2269
|
-
category: timedOut ? "timeout" : "provider-error",
|
|
2270
|
-
message: timedOut ? `Scout timed out after ${timeoutMs} ms.` : message,
|
|
2271
|
-
metrics: baseMetrics(manifest, startedAt),
|
|
2272
|
-
model: executorRef,
|
|
2273
|
-
ok: false
|
|
2274
|
-
};
|
|
2275
|
-
publish({ outcome, type: "fallback" });
|
|
2276
|
-
return outcome;
|
|
2463
|
+
return timedOut ? fallback("timeout", `Scout timed out after ${timeoutMs} ms.`) : fallback("provider-error", message);
|
|
2277
2464
|
}
|
|
2278
|
-
|
|
2279
|
-
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
2280
|
-
controller.signal.removeEventListener("abort", onControllerAbort);
|
|
2465
|
+
teardown();
|
|
2281
2466
|
if (parentSignal?.aborted) {
|
|
2282
|
-
|
|
2283
|
-
return { cancelled: true, ok: false };
|
|
2467
|
+
return cancelled();
|
|
2284
2468
|
}
|
|
2285
2469
|
let selection;
|
|
2286
2470
|
try {
|
|
2287
2471
|
selection = parseScoutSelection(streamed.text, manifest);
|
|
2288
2472
|
} catch (error) {
|
|
2289
2473
|
const message = error instanceof Error ? error.message : String(error);
|
|
2290
|
-
|
|
2291
|
-
category: streamed.text.trim() ? "invalid-selection" : "empty-response",
|
|
2292
|
-
message,
|
|
2293
|
-
metrics: {
|
|
2294
|
-
...baseMetrics(manifest, startedAt),
|
|
2295
|
-
usage: snapshotAdvisorUsage(streamed.usage)
|
|
2296
|
-
},
|
|
2297
|
-
model: executorRef,
|
|
2298
|
-
ok: false
|
|
2299
|
-
};
|
|
2300
|
-
publish({ outcome, type: "fallback" });
|
|
2301
|
-
return outcome;
|
|
2474
|
+
return fallback(streamed.text.trim() ? "invalid-selection" : "empty-response", message, snapshotAdvisorUsage(streamed.usage));
|
|
2302
2475
|
}
|
|
2303
2476
|
const outcome = {
|
|
2304
2477
|
conversation: reconstructScoutConversation(manifest, selection.selectedIds, selection.synthesis),
|
|
@@ -2319,234 +2492,137 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2319
2492
|
return outcome;
|
|
2320
2493
|
};
|
|
2321
2494
|
|
|
2322
|
-
// src/
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
};
|
|
2334
|
-
var normalizeRelativePath = (root, path) => relative2(root, resolve(root, path));
|
|
2335
|
-
var normalizeRequestedPath = (root, value) => {
|
|
2336
|
-
if (typeof value !== "string" || !value || isAbsolute(value) || value.split(PATH_SEGMENTS).includes("..")) {
|
|
2337
|
-
return;
|
|
2338
|
-
}
|
|
2339
|
-
return normalizeRelativePath(root, value);
|
|
2340
|
-
};
|
|
2341
|
-
var git = (cwd, args) => execFileSync2("git", args, {
|
|
2342
|
-
cwd,
|
|
2343
|
-
encoding: "utf8",
|
|
2344
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
2345
|
-
shell: false,
|
|
2346
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2347
|
-
timeout: 5000,
|
|
2348
|
-
windowsHide: true
|
|
2495
|
+
// src/scout-context.ts
|
|
2496
|
+
var resolveCaps = (options) => ({
|
|
2497
|
+
currentInvocationId: options.currentInvocationId,
|
|
2498
|
+
maxConversationChars: options.maxConversationChars,
|
|
2499
|
+
maxGroupBytes: options.maxGroupBytes ?? SCOUT_GROUP_MAX_BYTES,
|
|
2500
|
+
maxGroups: options.maxGroups ?? SCOUT_MANIFEST_MAX_GROUPS,
|
|
2501
|
+
maxManifestBytes: options.maxManifestBytes ?? options.maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
2502
|
+
policies: options.policies ?? advisorToolPoliciesRef,
|
|
2503
|
+
redact: options.redact ?? advisorRedactSecretsRef,
|
|
2504
|
+
toolResultMaxBytes: options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef,
|
|
2505
|
+
toolResultMaxLines: options.toolResultMaxLines ?? advisorToolResultMaxLinesRef
|
|
2349
2506
|
});
|
|
2350
|
-
var
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
};
|
|
2357
|
-
var untracked = (cwd, path) => {
|
|
2358
|
-
const output = git(cwd, [
|
|
2359
|
-
"ls-files",
|
|
2360
|
-
"--others",
|
|
2361
|
-
"--exclude-standard",
|
|
2362
|
-
"-z",
|
|
2363
|
-
"--",
|
|
2364
|
-
path
|
|
2365
|
-
]);
|
|
2366
|
-
const expected = normalizeRelativePath(cwd, path);
|
|
2367
|
-
return output.split("\x00").filter(Boolean).some((entry) => normalizeRelativePath(cwd, entry) === expected);
|
|
2368
|
-
};
|
|
2369
|
-
var tracked = (cwd, path) => {
|
|
2370
|
-
const output = git(cwd, ["ls-files", "--stage", "-z", "--", path]);
|
|
2371
|
-
const expected = normalizeRelativePath(cwd, path);
|
|
2372
|
-
return output.split("\x00").some((entry) => {
|
|
2373
|
-
if (!entry) {
|
|
2374
|
-
return false;
|
|
2375
|
-
}
|
|
2376
|
-
const [metadata, name] = entry.split("\t");
|
|
2377
|
-
return name && normalizeRelativePath(cwd, name) === expected && !metadata.startsWith("160000 ");
|
|
2378
|
-
});
|
|
2379
|
-
};
|
|
2380
|
-
var isPermitted = (root, path, kind) => kind === "tracked" ? tracked(root, path) : untracked(root, path);
|
|
2381
|
-
var readAttachment = async (root, normalizedName, redact, available) => {
|
|
2382
|
-
const absolute = resolve(root, normalizedName);
|
|
2383
|
-
if (!(within(root, absolute) && available > 0)) {
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
const stats = await lstat2(absolute);
|
|
2387
|
-
if (stats.isSymbolicLink() || !stats.isFile()) {
|
|
2388
|
-
return;
|
|
2507
|
+
var buildScoutManifest = (ctx, options = {}) => {
|
|
2508
|
+
const entries = ctx.sessionManager.buildContextEntries();
|
|
2509
|
+
const caps = resolveCaps(options);
|
|
2510
|
+
const indexed = indexToolCalls(entries);
|
|
2511
|
+
if (!indexed.ok) {
|
|
2512
|
+
return indexed;
|
|
2389
2513
|
}
|
|
2390
|
-
const
|
|
2391
|
-
if (!
|
|
2392
|
-
return;
|
|
2514
|
+
const built = buildGroups(entries, indexed.index, caps);
|
|
2515
|
+
if (!built.ok) {
|
|
2516
|
+
return built;
|
|
2393
2517
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
const raw = buffer.subarray(0, bytesRead).toString("utf8");
|
|
2404
|
-
if (raw.includes("\x00")) {
|
|
2405
|
-
return;
|
|
2406
|
-
}
|
|
2407
|
-
const text = redactAndCapText(raw, available, redact);
|
|
2518
|
+
return fitToBudget(built, caps);
|
|
2519
|
+
};
|
|
2520
|
+
var fits = (selected, caps) => selected.length <= caps.maxGroups && selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <= caps.maxManifestBytes && (caps.maxConversationChars === undefined || contentChars(selected) <= caps.maxConversationChars);
|
|
2521
|
+
var contentChars = (items) => items.reduce((sum, group) => sum + group.content.length, 0) + Math.max(0, items.length - 1) * 2;
|
|
2522
|
+
var fitToBudget = (built, caps) => {
|
|
2523
|
+
const { groups, protocolOmittedBytes, protocolOmittedCount } = built;
|
|
2524
|
+
const availableCount = groups.length + protocolOmittedCount;
|
|
2525
|
+
const availableBytes = groups.reduce((sum, group) => sum + groupWireBytes(group), 0) + protocolOmittedBytes;
|
|
2526
|
+
if (caps.maxManifestBytes <= 0) {
|
|
2408
2527
|
return {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2528
|
+
manifest: {
|
|
2529
|
+
availableBytes: 0,
|
|
2530
|
+
availableCount: 0,
|
|
2531
|
+
groups: [],
|
|
2532
|
+
omittedBytes: 0,
|
|
2533
|
+
omittedCount: 0
|
|
2534
|
+
},
|
|
2535
|
+
ok: true
|
|
2412
2536
|
};
|
|
2413
|
-
} finally {
|
|
2414
|
-
await file.close();
|
|
2415
|
-
}
|
|
2416
|
-
};
|
|
2417
|
-
var readFiles = async (cwd, requested, enabled, redact, kind, totalLimit = ADVISOR_FILES_TOTAL_MAX_BYTES) => {
|
|
2418
|
-
if (!(enabled && Array.isArray(requested))) {
|
|
2419
|
-
return [];
|
|
2420
2537
|
}
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const unique = new Set;
|
|
2426
|
-
const attachments = [];
|
|
2427
|
-
let total = 0;
|
|
2428
|
-
for (const name of requested) {
|
|
2429
|
-
const normalizedName = normalizeRequestedPath(root, name);
|
|
2430
|
-
if (!normalizedName || unique.has(normalizedName)) {
|
|
2431
|
-
continue;
|
|
2432
|
-
}
|
|
2433
|
-
unique.add(normalizedName);
|
|
2434
|
-
try {
|
|
2435
|
-
if (!isPermitted(root, normalizedName, kind)) {
|
|
2436
|
-
continue;
|
|
2437
|
-
}
|
|
2438
|
-
const available = Math.min(ADVISOR_FILE_MAX_BYTES, totalLimit - total);
|
|
2439
|
-
if (available <= 0) {
|
|
2440
|
-
break;
|
|
2441
|
-
}
|
|
2442
|
-
const attachment = await readAttachment(root, normalizedName, redact, available);
|
|
2443
|
-
if (attachment) {
|
|
2444
|
-
attachments.push(attachment);
|
|
2445
|
-
total += attachment.bytes;
|
|
2446
|
-
}
|
|
2447
|
-
} catch {}
|
|
2538
|
+
const required = groups.filter((group) => group.required);
|
|
2539
|
+
const overflow = requiredOverflow(required, caps);
|
|
2540
|
+
if (overflow) {
|
|
2541
|
+
return overflow;
|
|
2448
2542
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
var advanceFence = (openingFence, marker, suffix) => {
|
|
2459
|
-
if (!openingFence) {
|
|
2460
|
-
if (marker[0] === "`" && suffix.includes("`")) {
|
|
2461
|
-
return { closed: false, openingFence: undefined };
|
|
2543
|
+
const selected = groups.filter((group) => group.required || group.bytes <= caps.maxGroupBytes);
|
|
2544
|
+
while (!fits(selected, caps)) {
|
|
2545
|
+
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
2546
|
+
if (optionalIndex < 0) {
|
|
2547
|
+
return {
|
|
2548
|
+
message: "Required Scout context exceeds fixed manifest limits.",
|
|
2549
|
+
ok: false,
|
|
2550
|
+
reason: "required-group-overflow"
|
|
2551
|
+
};
|
|
2462
2552
|
}
|
|
2463
|
-
|
|
2464
|
-
closed: false,
|
|
2465
|
-
openingFence: { character: marker[0], length: marker.length }
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
if (suffix.trim().length > 0 || marker[0] !== openingFence.character || marker.length < openingFence.length) {
|
|
2469
|
-
return { closed: false, openingFence };
|
|
2553
|
+
selected.splice(optionalIndex, 1);
|
|
2470
2554
|
}
|
|
2471
|
-
|
|
2555
|
+
const selectedIds = new Set(selected.map((group) => group.id));
|
|
2556
|
+
const omitted = groups.filter((group) => !selectedIds.has(group.id));
|
|
2557
|
+
return {
|
|
2558
|
+
manifest: {
|
|
2559
|
+
availableBytes,
|
|
2560
|
+
availableCount,
|
|
2561
|
+
groups: selected,
|
|
2562
|
+
omittedBytes: protocolOmittedBytes + omitted.reduce((sum, group) => sum + group.bytes, 0),
|
|
2563
|
+
omittedCount: protocolOmittedCount + omitted.length
|
|
2564
|
+
},
|
|
2565
|
+
ok: true
|
|
2566
|
+
};
|
|
2472
2567
|
};
|
|
2473
|
-
var
|
|
2474
|
-
|
|
2475
|
-
const nonEmpty = lines.findIndex((line) => line.trim().length > 0);
|
|
2476
|
-
if (nonEmpty === -1) {
|
|
2568
|
+
var requiredOverflow = (required, caps) => {
|
|
2569
|
+
if (required.some((group) => group.bytes > caps.maxGroupBytes) || required.length > caps.maxGroups || required.reduce((sum, group) => sum + groupWireBytes(group), 0) > caps.maxManifestBytes) {
|
|
2477
2570
|
return {
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2571
|
+
message: "Required Scout context exceeds the Scout manifest transport limit.",
|
|
2572
|
+
ok: false,
|
|
2573
|
+
reason: "required-group-overflow"
|
|
2481
2574
|
};
|
|
2482
2575
|
}
|
|
2483
|
-
|
|
2484
|
-
const match = DECISION_LINE.exec(first);
|
|
2485
|
-
if (!match) {
|
|
2576
|
+
if (caps.maxConversationChars !== undefined && contentChars(required) > caps.maxConversationChars) {
|
|
2486
2577
|
return {
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
ok: false
|
|
2578
|
+
message: "Required Scout context exceeds the Advisor conversation budget.",
|
|
2579
|
+
ok: false,
|
|
2580
|
+
reason: "required-group-overflow"
|
|
2491
2581
|
};
|
|
2492
2582
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
}
|
|
2522
|
-
for (const repeated of decisions) {
|
|
2523
|
-
if (repeated === decision) {
|
|
2524
|
-
return {
|
|
2525
|
-
category: "duplicate-decision",
|
|
2526
|
-
markdown: text,
|
|
2527
|
-
message: "Advisor gate response contains duplicate decision lines.",
|
|
2528
|
-
ok: false
|
|
2529
|
-
};
|
|
2530
|
-
}
|
|
2531
|
-
return {
|
|
2532
|
-
category: "contradictory-decision",
|
|
2533
|
-
markdown: text,
|
|
2534
|
-
message: "Advisor gate response contains contradictory decision lines.",
|
|
2583
|
+
return;
|
|
2584
|
+
};
|
|
2585
|
+
|
|
2586
|
+
// src/scout-curation.ts
|
|
2587
|
+
var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout, enabled = advisorScoutEnabledRef, runScout = runAdvisorScout, currentInvocationId, maxChars) => {
|
|
2588
|
+
if (!enabled) {
|
|
2589
|
+
return { conversation: legacyConversation };
|
|
2590
|
+
}
|
|
2591
|
+
if (maxChars !== undefined && maxChars <= 0) {
|
|
2592
|
+
return { conversation: "" };
|
|
2593
|
+
}
|
|
2594
|
+
const built = buildScoutManifest(ctx, {
|
|
2595
|
+
currentInvocationId,
|
|
2596
|
+
maxConversationChars: maxChars,
|
|
2597
|
+
maxManifestBytes: SCOUT_MANIFEST_MAX_BYTES
|
|
2598
|
+
});
|
|
2599
|
+
if (!built.ok) {
|
|
2600
|
+
const scout = {
|
|
2601
|
+
category: built.reason,
|
|
2602
|
+
message: built.message,
|
|
2603
|
+
metrics: {
|
|
2604
|
+
availableCount: 0,
|
|
2605
|
+
inputBytes: 0,
|
|
2606
|
+
latencyMs: 0,
|
|
2607
|
+
omittedBeforeScout: 0,
|
|
2608
|
+
selectedCount: 0
|
|
2609
|
+
},
|
|
2610
|
+
model: executorRef,
|
|
2535
2611
|
ok: false
|
|
2536
2612
|
};
|
|
2613
|
+
onScout?.({ outcome: scout, type: "fallback" });
|
|
2614
|
+
return { conversation: legacyConversation, scout };
|
|
2537
2615
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
}
|
|
2616
|
+
const outcome = await runScout(ctx, built.manifest, signal, onScout, undefined, undefined);
|
|
2617
|
+
if (!outcome.ok && outcome.cancelled) {
|
|
2618
|
+
throw signal?.reason instanceof Error ? signal.reason : new Error("Advisor operation cancelled during Scout.");
|
|
2619
|
+
}
|
|
2620
|
+
let conversation = legacyConversation;
|
|
2621
|
+
if (outcome.ok) {
|
|
2622
|
+
conversation = maxChars === undefined ? outcome.conversation : reconstructScoutConversation(built.manifest, outcome.selection.selectedIds, outcome.selection.synthesis, maxChars);
|
|
2623
|
+
}
|
|
2624
|
+
return { conversation, scout: outcome };
|
|
2546
2625
|
};
|
|
2547
|
-
var adviceForGateText = (result) => `**Decision: ${result.decision}**
|
|
2548
|
-
|
|
2549
|
-
${result.markdown}`;
|
|
2550
2626
|
|
|
2551
2627
|
// src/tools/prompts.ts
|
|
2552
2628
|
var advisorMessageText = (conversation, question, changes, draft, preferences, untracked, tracked) => {
|
|
@@ -2657,72 +2733,152 @@ var ADVISOR_DECISION_SYSTEM = [
|
|
|
2657
2733
|
"Use blocked only for a critical issue requiring the user. Never claim verification that the supplied evidence does not show."
|
|
2658
2734
|
].join(" ");
|
|
2659
2735
|
|
|
2660
|
-
// src/tools/
|
|
2661
|
-
var
|
|
2662
|
-
|
|
2663
|
-
|
|
2736
|
+
// src/tools/consult-context.ts
|
|
2737
|
+
var ATTACHMENT_TEXT_MAX_BYTES = 8 * 1024;
|
|
2738
|
+
var ATTACHMENTS_TOTAL_MAX_BYTES = 24 * 1024;
|
|
2739
|
+
var assembleConsultationContext = async (options) => {
|
|
2740
|
+
const { ctx } = options;
|
|
2741
|
+
const allowed = advisorGitContextRef;
|
|
2742
|
+
const level = clampGitContextLevel(options.gitContext ?? allowed, allowed);
|
|
2743
|
+
const gitBudget = advisorGitContextBudget(contextMaxCharsRef, advisorGitContextMaxCharsRef);
|
|
2744
|
+
const changes = collectGitContext(ctx.cwd, level, gitBudget, advisorRedactSecretsRef ? redactSecrets : undefined);
|
|
2745
|
+
const changeText = advisorRepositoryContext(changes, options.gitContext ?? allowed, level, gitBudget);
|
|
2746
|
+
const conversationBudget = Math.max(0, contextMaxCharsRef - changeText.length);
|
|
2747
|
+
const legacyConversation = advisorRequestConversation(ctx, conversationBudget);
|
|
2748
|
+
const curated = await curateAdvisorConversation(ctx, legacyConversation, options.signal, options.onScout, advisorScoutEnabledRef, runAdvisorScout, options.currentInvocationId, conversationBudget);
|
|
2749
|
+
const preferences = await readProjectPreferences(ctx, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef);
|
|
2750
|
+
const draftText = options.draft ? redactAndCapText(options.draft, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef) : undefined;
|
|
2751
|
+
const untracked = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
|
|
2752
|
+
const tracked = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked.reduce((sum, item) => sum + item.bytes, 0)));
|
|
2753
|
+
return {
|
|
2754
|
+
changeText,
|
|
2755
|
+
conversation: curated.conversation,
|
|
2756
|
+
draftText,
|
|
2757
|
+
preferences,
|
|
2758
|
+
scout: curated.scout,
|
|
2759
|
+
tracked,
|
|
2760
|
+
untracked
|
|
2761
|
+
};
|
|
2762
|
+
};
|
|
2763
|
+
|
|
2764
|
+
// src/tools/gate-protocol.ts
|
|
2765
|
+
var DECISION_LINE = /^Decision\s*:\s*(proceed|revise|blocked)\s*$/i;
|
|
2766
|
+
var CODE_FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
2767
|
+
var LINE_BREAK = /\r?\n/;
|
|
2768
|
+
var advanceFence = (openingFence, marker, suffix) => {
|
|
2769
|
+
if (!openingFence) {
|
|
2770
|
+
if (marker[0] === "`" && suffix.includes("`")) {
|
|
2771
|
+
return { closed: false, openingFence: undefined };
|
|
2772
|
+
}
|
|
2773
|
+
return {
|
|
2774
|
+
closed: false,
|
|
2775
|
+
openingFence: { character: marker[0], length: marker.length }
|
|
2776
|
+
};
|
|
2664
2777
|
}
|
|
2665
|
-
if (
|
|
2666
|
-
return {
|
|
2778
|
+
if (suffix.trim().length > 0 || marker[0] !== openingFence.character || marker.length < openingFence.length) {
|
|
2779
|
+
return { closed: false, openingFence };
|
|
2667
2780
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
if (
|
|
2674
|
-
|
|
2675
|
-
category:
|
|
2676
|
-
message:
|
|
2677
|
-
metrics: {
|
|
2678
|
-
availableCount: 0,
|
|
2679
|
-
inputBytes: 0,
|
|
2680
|
-
latencyMs: 0,
|
|
2681
|
-
omittedBeforeScout: 0,
|
|
2682
|
-
selectedCount: 0
|
|
2683
|
-
},
|
|
2684
|
-
model: executorRef,
|
|
2781
|
+
return { closed: true, openingFence: undefined };
|
|
2782
|
+
};
|
|
2783
|
+
var parseAutomaticDecision = (text) => {
|
|
2784
|
+
const lines = text.split(LINE_BREAK);
|
|
2785
|
+
const nonEmpty = lines.findIndex((line) => line.trim().length > 0);
|
|
2786
|
+
if (nonEmpty === -1) {
|
|
2787
|
+
return {
|
|
2788
|
+
category: "empty-response",
|
|
2789
|
+
message: "Advisor returned an empty gate response.",
|
|
2685
2790
|
ok: false
|
|
2686
2791
|
};
|
|
2687
|
-
onScout?.({ outcome: scout, type: "fallback" });
|
|
2688
|
-
return { conversation: legacyConversation, scout };
|
|
2689
2792
|
}
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2793
|
+
const first = lines[nonEmpty].trim();
|
|
2794
|
+
const match = DECISION_LINE.exec(first);
|
|
2795
|
+
if (!match) {
|
|
2796
|
+
return {
|
|
2797
|
+
category: first.toLowerCase().startsWith("decision:") ? "malformed-decision" : "missing-decision",
|
|
2798
|
+
markdown: text,
|
|
2799
|
+
message: "Advisor gate response must begin with Decision: proceed, Decision: revise, or Decision: blocked.",
|
|
2800
|
+
ok: false
|
|
2801
|
+
};
|
|
2693
2802
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2803
|
+
const decision = match[1].toLowerCase();
|
|
2804
|
+
let openingFence;
|
|
2805
|
+
const decisions = [];
|
|
2806
|
+
let pendingFencedDecisions = [];
|
|
2807
|
+
for (const line of lines.slice(nonEmpty + 1)) {
|
|
2808
|
+
const trimmed = line.trim();
|
|
2809
|
+
const fence = CODE_FENCE.exec(line);
|
|
2810
|
+
if (fence) {
|
|
2811
|
+
const { closed, openingFence: nextOpeningFence } = advanceFence(openingFence, fence[1], fence[2]);
|
|
2812
|
+
openingFence = nextOpeningFence;
|
|
2813
|
+
if (closed) {
|
|
2814
|
+
pendingFencedDecisions = [];
|
|
2815
|
+
}
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
const subsequent = DECISION_LINE.exec(trimmed);
|
|
2819
|
+
if (!subsequent) {
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
const repeated = subsequent[1].trim().toLowerCase();
|
|
2823
|
+
if (openingFence) {
|
|
2824
|
+
pendingFencedDecisions.push(repeated);
|
|
2825
|
+
} else {
|
|
2826
|
+
decisions.push(repeated);
|
|
2827
|
+
}
|
|
2697
2828
|
}
|
|
2698
|
-
|
|
2829
|
+
if (openingFence) {
|
|
2830
|
+
decisions.push(...pendingFencedDecisions);
|
|
2831
|
+
}
|
|
2832
|
+
for (const repeated of decisions) {
|
|
2833
|
+
if (repeated === decision) {
|
|
2834
|
+
return {
|
|
2835
|
+
category: "duplicate-decision",
|
|
2836
|
+
markdown: text,
|
|
2837
|
+
message: "Advisor gate response contains duplicate decision lines.",
|
|
2838
|
+
ok: false
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
return {
|
|
2842
|
+
category: "contradictory-decision",
|
|
2843
|
+
markdown: text,
|
|
2844
|
+
message: "Advisor gate response contains contradictory decision lines.",
|
|
2845
|
+
ok: false
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
return {
|
|
2849
|
+
decision,
|
|
2850
|
+
markdown: text,
|
|
2851
|
+
model: "",
|
|
2852
|
+
ok: true,
|
|
2853
|
+
thinkingText: "",
|
|
2854
|
+
trigger: "repeated-tool-call"
|
|
2855
|
+
};
|
|
2699
2856
|
};
|
|
2700
|
-
var
|
|
2857
|
+
var adviceForGateText = (result) => `**Decision: ${result.decision}**
|
|
2858
|
+
|
|
2859
|
+
${result.markdown}`;
|
|
2860
|
+
|
|
2861
|
+
// src/tools/consultation.ts
|
|
2862
|
+
class AdvisorNoAdviceError extends Error {
|
|
2863
|
+
constructor() {
|
|
2864
|
+
super("Advisor returned no advice.");
|
|
2865
|
+
this.name = "AdvisorNoAdviceError";
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
var fileTag = (item) => `<file path=${JSON.stringify(item.path)}>
|
|
2869
|
+
${item.text}
|
|
2870
|
+
</file>`;
|
|
2871
|
+
var collectAdvisorResponse = async (options) => {
|
|
2872
|
+
const { ctx, question, signal, systemPrompt } = options;
|
|
2701
2873
|
loadConfig(ctx);
|
|
2702
2874
|
const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
|
|
2703
|
-
const
|
|
2704
|
-
const level = clampGitContextLevel(gitContext ?? allowed, allowed);
|
|
2705
|
-
const gitBudget = advisorGitContextBudget(contextMaxCharsRef, advisorGitContextMaxCharsRef);
|
|
2706
|
-
const changes = collectGitContext(ctx.cwd, level, gitBudget, advisorRedactSecretsRef ? redactSecrets : undefined);
|
|
2707
|
-
const changeText = advisorRepositoryContext(changes, gitContext ?? allowed, level, gitBudget);
|
|
2708
|
-
const conversationBudget = Math.max(0, contextMaxCharsRef - changeText.length);
|
|
2709
|
-
const legacyConversation = advisorRequestConversation(ctx, conversationBudget);
|
|
2710
|
-
const curated = await curateAdvisorConversation(ctx, legacyConversation, signal, onScout, advisorScoutEnabledRef, runAdvisorScout, currentInvocationId, conversationBudget);
|
|
2711
|
-
const { conversation, scout } = curated;
|
|
2712
|
-
const preferences = await readProjectPreferences(ctx, 8 * 1024, advisorRedactSecretsRef);
|
|
2713
|
-
const draftText = draft ? redactAndCapText(draft, 8 * 1024, advisorRedactSecretsRef) : undefined;
|
|
2714
|
-
const untracked = await readUntrackedFiles(ctx.cwd, includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
|
|
2715
|
-
const tracked = await readTrackedFiles(ctx.cwd, includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, 24 * 1024 - untracked.reduce((sum, item) => sum + item.bytes, 0)));
|
|
2875
|
+
const context = await assembleConsultationContext(options);
|
|
2716
2876
|
const outboundQuestion = advisorRedactSecretsRef && question !== undefined ? redactSecrets(question) : question;
|
|
2717
2877
|
const messages = [
|
|
2718
2878
|
{
|
|
2719
2879
|
content: [
|
|
2720
2880
|
{
|
|
2721
|
-
text: advisorMessageText(conversation, outboundQuestion, changeText, draftText, preferences?.text, untracked.map(
|
|
2722
|
-
${item.text}
|
|
2723
|
-
</file>`), tracked.map((item) => `<file path=${JSON.stringify(item.path)}>
|
|
2724
|
-
${item.text}
|
|
2725
|
-
</file>`)),
|
|
2881
|
+
text: advisorMessageText(context.conversation, outboundQuestion, context.changeText, context.draftText, context.preferences?.text, context.untracked.map(fileTag), context.tracked.map(fileTag)),
|
|
2726
2882
|
type: "text"
|
|
2727
2883
|
}
|
|
2728
2884
|
],
|
|
@@ -2732,34 +2888,54 @@ ${item.text}
|
|
|
2732
2888
|
];
|
|
2733
2889
|
const streamed = await collectTextStream(resolved, {
|
|
2734
2890
|
messages,
|
|
2735
|
-
onChunk,
|
|
2891
|
+
onChunk: options.onChunk,
|
|
2736
2892
|
reasoning: advisorEffortRef,
|
|
2737
2893
|
signal,
|
|
2738
2894
|
systemPrompt
|
|
2739
2895
|
});
|
|
2740
2896
|
const markdown = streamed.text;
|
|
2741
2897
|
if (!markdown.trim()) {
|
|
2742
|
-
throw new
|
|
2898
|
+
throw new AdvisorNoAdviceError;
|
|
2743
2899
|
}
|
|
2744
2900
|
return {
|
|
2745
|
-
draftBytes: draftText ? Buffer.byteLength(draftText, "utf8") : undefined,
|
|
2901
|
+
draftBytes: context.draftText ? Buffer.byteLength(context.draftText, "utf8") : undefined,
|
|
2746
2902
|
markdown,
|
|
2747
2903
|
model: advisorRef,
|
|
2748
|
-
preferenceBytes: preferences?.bytes,
|
|
2904
|
+
preferenceBytes: context.preferences?.bytes,
|
|
2749
2905
|
thinkingText: streamed.thinking,
|
|
2750
|
-
trackedBytes: tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
2751
|
-
untrackedBytes: untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
2906
|
+
trackedBytes: context.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
2907
|
+
untrackedBytes: context.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
2752
2908
|
usage: streamed.usage,
|
|
2753
|
-
...scout ? { scout } : {}
|
|
2909
|
+
...context.scout ? { scout: context.scout } : {}
|
|
2754
2910
|
};
|
|
2755
2911
|
};
|
|
2756
2912
|
var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-requested", gitContext, draft, includeUntracked, includeTracked, onScout, currentInvocationId) => {
|
|
2757
|
-
const result = await collectAdvisorResponse(
|
|
2913
|
+
const result = await collectAdvisorResponse({
|
|
2914
|
+
ctx,
|
|
2915
|
+
currentInvocationId,
|
|
2916
|
+
draft,
|
|
2917
|
+
gitContext,
|
|
2918
|
+
includeTracked,
|
|
2919
|
+
includeUntracked,
|
|
2920
|
+
onChunk,
|
|
2921
|
+
onScout,
|
|
2922
|
+
question,
|
|
2923
|
+
signal,
|
|
2924
|
+
systemPrompt: ADVISOR_SYSTEM
|
|
2925
|
+
});
|
|
2758
2926
|
return { ...result, adviceId: randomUUID(), trigger };
|
|
2759
2927
|
};
|
|
2760
2928
|
var runAdvisorGate = async (ctx, question, trigger = "repeated-tool-call", signal, onChunk, onScout, currentInvocationId) => {
|
|
2761
2929
|
try {
|
|
2762
|
-
const result = await collectAdvisorResponse(
|
|
2930
|
+
const result = await collectAdvisorResponse({
|
|
2931
|
+
ctx,
|
|
2932
|
+
currentInvocationId,
|
|
2933
|
+
onChunk,
|
|
2934
|
+
onScout,
|
|
2935
|
+
question,
|
|
2936
|
+
signal,
|
|
2937
|
+
systemPrompt: ADVISOR_DECISION_SYSTEM
|
|
2938
|
+
});
|
|
2763
2939
|
const parsed = parseAutomaticDecision(result.markdown);
|
|
2764
2940
|
if (!parsed.ok) {
|
|
2765
2941
|
return { ...parsed, usage: result.usage };
|
|
@@ -2777,7 +2953,7 @@ var runAdvisorGate = async (ctx, question, trigger = "repeated-tool-call", signa
|
|
|
2777
2953
|
}
|
|
2778
2954
|
const message = error instanceof Error ? error.message : String(error);
|
|
2779
2955
|
return {
|
|
2780
|
-
category:
|
|
2956
|
+
category: error instanceof AdvisorNoAdviceError ? "empty-response" : "provider-error",
|
|
2781
2957
|
message,
|
|
2782
2958
|
ok: false
|
|
2783
2959
|
};
|
|
@@ -2796,6 +2972,46 @@ import {
|
|
|
2796
2972
|
truncateToWidth as truncateToWidth2,
|
|
2797
2973
|
visibleWidth
|
|
2798
2974
|
} from "@earendil-works/pi-tui";
|
|
2975
|
+
|
|
2976
|
+
// src/pi-settings.ts
|
|
2977
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
2978
|
+
import { join as join3 } from "node:path";
|
|
2979
|
+
import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
2980
|
+
var SETTING_RECHECK_INTERVAL_MS = 2000;
|
|
2981
|
+
var cache = new Map;
|
|
2982
|
+
var settingsIdentity = (path) => {
|
|
2983
|
+
try {
|
|
2984
|
+
const stats = statSync2(path, { bigint: true });
|
|
2985
|
+
return `${stats.mtimeNs}:${stats.ctimeNs}:${stats.size}:${stats.ino}`;
|
|
2986
|
+
} catch {
|
|
2987
|
+
return `unstattable:${process.hrtime.bigint()}`;
|
|
2988
|
+
}
|
|
2989
|
+
};
|
|
2990
|
+
var readHideThinking = (path) => {
|
|
2991
|
+
try {
|
|
2992
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
2993
|
+
return typeof parsed === "object" && parsed !== null && parsed.hideThinkingBlock === true;
|
|
2994
|
+
} catch {
|
|
2995
|
+
return false;
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
var piHideThinkingEnabled = () => {
|
|
2999
|
+
const path = join3(getAgentDir2(), "settings.json");
|
|
3000
|
+
if (!existsSync2(path)) {
|
|
3001
|
+
return false;
|
|
3002
|
+
}
|
|
3003
|
+
const now = Date.now();
|
|
3004
|
+
const cached = cache.get(path);
|
|
3005
|
+
if (cached && now - cached.readAt < SETTING_RECHECK_INTERVAL_MS) {
|
|
3006
|
+
return cached.hidden;
|
|
3007
|
+
}
|
|
3008
|
+
const identity = settingsIdentity(path);
|
|
3009
|
+
const hidden = cached && cached.identity === identity ? cached.hidden : readHideThinking(path);
|
|
3010
|
+
cache.set(path, { hidden, identity, readAt: now });
|
|
3011
|
+
return hidden;
|
|
3012
|
+
};
|
|
3013
|
+
|
|
3014
|
+
// src/tools/render-common.ts
|
|
2799
3015
|
var SPINNER_FRAMES = [
|
|
2800
3016
|
"⠋",
|
|
2801
3017
|
"⠙",
|
|
@@ -2808,7 +3024,7 @@ var SPINNER_FRAMES = [
|
|
|
2808
3024
|
"⠇",
|
|
2809
3025
|
"⠏"
|
|
2810
3026
|
];
|
|
2811
|
-
var THINKING_PREFIX = "
|
|
3027
|
+
var THINKING_PREFIX = " ";
|
|
2812
3028
|
var THINKING_PREFIX_WIDTH = visibleWidth(THINKING_PREFIX);
|
|
2813
3029
|
|
|
2814
3030
|
class ThinkingMarkdown {
|
|
@@ -2831,7 +3047,18 @@ class ThinkingMarkdown {
|
|
|
2831
3047
|
this.markdown.invalidate();
|
|
2832
3048
|
}
|
|
2833
3049
|
}
|
|
2834
|
-
|
|
3050
|
+
|
|
3051
|
+
class HiddenThinkingLabel {
|
|
3052
|
+
label;
|
|
3053
|
+
constructor(theme) {
|
|
3054
|
+
this.label = theme.fg("thinkingText", `${THINKING_PREFIX}Thinking…`);
|
|
3055
|
+
}
|
|
3056
|
+
render() {
|
|
3057
|
+
return [this.label];
|
|
3058
|
+
}
|
|
3059
|
+
invalidate() {}
|
|
3060
|
+
}
|
|
3061
|
+
var renderThinkingMarkdown = (thinking, theme) => piHideThinkingEnabled() ? new HiddenThinkingLabel(theme) : new ThinkingMarkdown(thinking, theme);
|
|
2835
3062
|
var resolveAdvisorRequest = (question) => question?.trim() || undefined;
|
|
2836
3063
|
var renderAdvisorCallBox = (question, theme) => {
|
|
2837
3064
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
@@ -3078,42 +3305,41 @@ var normalizeToolInput = (toolName, input) => {
|
|
|
3078
3305
|
return visit(input);
|
|
3079
3306
|
};
|
|
3080
3307
|
var normalizedToolSignature = (toolName, input) => `${toolName}:${JSON.stringify(normalizeToolInput(toolName, input))}`;
|
|
3308
|
+
var freshRepetition = () => ({
|
|
3309
|
+
count: 0,
|
|
3310
|
+
interventions: 0
|
|
3311
|
+
});
|
|
3312
|
+
var freshAdviceLedger = () => ({
|
|
3313
|
+
draftConsultations: 0,
|
|
3314
|
+
issued: new Map,
|
|
3315
|
+
outcomes: 0,
|
|
3316
|
+
pending: new Set,
|
|
3317
|
+
reported: new Set
|
|
3318
|
+
});
|
|
3319
|
+
var freshUsage = () => ({
|
|
3320
|
+
invocations: [],
|
|
3321
|
+
totals: emptyAdvisorUsageTotals()
|
|
3322
|
+
});
|
|
3081
3323
|
|
|
3082
3324
|
class AdvisorSessionState {
|
|
3083
|
-
#
|
|
3084
|
-
#repetitions = 0;
|
|
3325
|
+
#repetition = freshRepetition();
|
|
3085
3326
|
#blockedReason;
|
|
3086
|
-
#
|
|
3087
|
-
#
|
|
3327
|
+
#ledger = freshAdviceLedger();
|
|
3328
|
+
#usage = freshUsage();
|
|
3088
3329
|
#consumedCalls = 0;
|
|
3089
|
-
#issuedAdvice = new Map;
|
|
3090
|
-
#pendingAdvice = new Set;
|
|
3091
|
-
#reportedAdvice = new Set;
|
|
3092
|
-
#draftConsultations = 0;
|
|
3093
|
-
#outcomes = 0;
|
|
3094
|
-
#lastAdvice;
|
|
3095
|
-
#usage = emptyAdvisorUsageTotals();
|
|
3096
3330
|
resetTask() {
|
|
3097
|
-
this.#
|
|
3098
|
-
this.#repetitions = 0;
|
|
3331
|
+
this.#repetition = freshRepetition();
|
|
3099
3332
|
this.#blockedReason = undefined;
|
|
3100
|
-
this.#
|
|
3101
|
-
this.#
|
|
3333
|
+
this.#ledger = freshAdviceLedger();
|
|
3334
|
+
this.#usage = freshUsage();
|
|
3102
3335
|
this.#consumedCalls = 0;
|
|
3103
|
-
this.#issuedAdvice.clear();
|
|
3104
|
-
this.#pendingAdvice.clear();
|
|
3105
|
-
this.#reportedAdvice.clear();
|
|
3106
|
-
this.#draftConsultations = 0;
|
|
3107
|
-
this.#outcomes = 0;
|
|
3108
|
-
this.#lastAdvice = undefined;
|
|
3109
|
-
this.#usage = emptyAdvisorUsageTotals();
|
|
3110
3336
|
}
|
|
3111
3337
|
clearBlocked() {
|
|
3112
3338
|
this.#blockedReason = undefined;
|
|
3113
3339
|
}
|
|
3114
3340
|
resetRepetition() {
|
|
3115
|
-
this.#
|
|
3116
|
-
this.#
|
|
3341
|
+
this.#repetition.count = 0;
|
|
3342
|
+
this.#repetition.previousSignature = undefined;
|
|
3117
3343
|
}
|
|
3118
3344
|
get blocked() {
|
|
3119
3345
|
return this.#blockedReason !== undefined;
|
|
@@ -3129,12 +3355,12 @@ class AdvisorSessionState {
|
|
|
3129
3355
|
return false;
|
|
3130
3356
|
}
|
|
3131
3357
|
const signature = normalizedToolSignature(toolName, input);
|
|
3132
|
-
this.#
|
|
3133
|
-
this.#previousSignature = signature;
|
|
3134
|
-
if (this.#
|
|
3358
|
+
this.#repetition.count = signature === this.#repetition.previousSignature ? this.#repetition.count + 1 : 1;
|
|
3359
|
+
this.#repetition.previousSignature = signature;
|
|
3360
|
+
if (this.#repetition.count < threshold) {
|
|
3135
3361
|
return false;
|
|
3136
3362
|
}
|
|
3137
|
-
this.#
|
|
3363
|
+
this.#repetition.interventions += 1;
|
|
3138
3364
|
return true;
|
|
3139
3365
|
}
|
|
3140
3366
|
canConsult(limit) {
|
|
@@ -3150,58 +3376,59 @@ class AdvisorSessionState {
|
|
|
3150
3376
|
return this.#consumedCalls;
|
|
3151
3377
|
}
|
|
3152
3378
|
get usageTotals() {
|
|
3153
|
-
return { ...this.#usage };
|
|
3379
|
+
return { ...this.#usage.totals };
|
|
3154
3380
|
}
|
|
3155
3381
|
usageStatus() {
|
|
3156
|
-
return formatAdvisorUsageStatus(this.#usage);
|
|
3382
|
+
return formatAdvisorUsageStatus(this.#usage.totals);
|
|
3157
3383
|
}
|
|
3158
3384
|
recordInvocation(record) {
|
|
3159
|
-
this.#invocations.push(record);
|
|
3160
|
-
addAdvisorUsage(this.#usage, record.usage);
|
|
3385
|
+
this.#usage.invocations.push(record);
|
|
3386
|
+
addAdvisorUsage(this.#usage.totals, record.usage);
|
|
3161
3387
|
}
|
|
3162
3388
|
issueAdvice(id, advice, trigger, draft = false) {
|
|
3163
|
-
this.#
|
|
3164
|
-
this.#lastAdvice = advice;
|
|
3389
|
+
this.#ledger.issued.set(id, { advice, trigger });
|
|
3390
|
+
this.#ledger.lastAdvice = advice;
|
|
3165
3391
|
if (draft) {
|
|
3166
|
-
this.#draftConsultations += 1;
|
|
3392
|
+
this.#ledger.draftConsultations += 1;
|
|
3167
3393
|
}
|
|
3168
3394
|
}
|
|
3169
3395
|
claimTrackedFiles(paths) {
|
|
3170
|
-
|
|
3396
|
+
const advice = this.#ledger.lastAdvice;
|
|
3397
|
+
if (!advice || paths.length === 0) {
|
|
3171
3398
|
return false;
|
|
3172
3399
|
}
|
|
3173
3400
|
const mentioned = paths.every((path) => {
|
|
3174
3401
|
const escaped = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3175
3402
|
const boundary = "(^|[\\s\\\"'`()\\[])" + escaped + "(?=$|[\\s\\\"'`),;:!?\\]]|\\.(?=\\s|$))";
|
|
3176
|
-
return new RegExp(boundary).test(
|
|
3403
|
+
return new RegExp(boundary).test(advice);
|
|
3177
3404
|
});
|
|
3178
3405
|
if (!mentioned) {
|
|
3179
3406
|
return false;
|
|
3180
3407
|
}
|
|
3181
|
-
this.#lastAdvice = undefined;
|
|
3408
|
+
this.#ledger.lastAdvice = undefined;
|
|
3182
3409
|
return true;
|
|
3183
3410
|
}
|
|
3184
3411
|
reserveAdvice(id) {
|
|
3185
|
-
if (this.#
|
|
3412
|
+
if (this.#ledger.reported.has(id) || this.#ledger.pending.has(id)) {
|
|
3186
3413
|
return;
|
|
3187
3414
|
}
|
|
3188
|
-
const advice = this.#
|
|
3415
|
+
const advice = this.#ledger.issued.get(id);
|
|
3189
3416
|
if (!advice) {
|
|
3190
3417
|
return;
|
|
3191
3418
|
}
|
|
3192
|
-
this.#
|
|
3419
|
+
this.#ledger.pending.add(id);
|
|
3193
3420
|
return advice;
|
|
3194
3421
|
}
|
|
3195
3422
|
commitAdvice(id) {
|
|
3196
|
-
if (!this.#
|
|
3423
|
+
if (!this.#ledger.pending.delete(id)) {
|
|
3197
3424
|
return false;
|
|
3198
3425
|
}
|
|
3199
|
-
this.#
|
|
3200
|
-
this.#outcomes += 1;
|
|
3426
|
+
this.#ledger.reported.add(id);
|
|
3427
|
+
this.#ledger.outcomes += 1;
|
|
3201
3428
|
return true;
|
|
3202
3429
|
}
|
|
3203
3430
|
releaseAdvice(id) {
|
|
3204
|
-
this.#
|
|
3431
|
+
this.#ledger.pending.delete(id);
|
|
3205
3432
|
}
|
|
3206
3433
|
claimAdvice(id) {
|
|
3207
3434
|
const advice = this.reserveAdvice(id);
|
|
@@ -3211,34 +3438,47 @@ class AdvisorSessionState {
|
|
|
3211
3438
|
this.commitAdvice(id);
|
|
3212
3439
|
return advice;
|
|
3213
3440
|
}
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
}
|
|
3218
|
-
const markdown = this.#invocations.filter((item) => item.kind === "markdown");
|
|
3219
|
-
const gates = this.#invocations.filter((item) => item.kind === "gate");
|
|
3220
|
-
const countTrigger = (trigger) => this.#invocations.filter((item) => item.trigger === trigger).length;
|
|
3221
|
-
const decisions = ["proceed", "revise", "blocked"].map((decision) => [
|
|
3441
|
+
#decisionsLine() {
|
|
3442
|
+
const gates = this.#usage.invocations.filter((item) => item.kind === "gate");
|
|
3443
|
+
return ["proceed", "revise", "blocked"].map((decision) => [
|
|
3222
3444
|
decision,
|
|
3223
3445
|
gates.filter((item) => item.decision === decision).length
|
|
3224
3446
|
]).filter(([, count]) => count > 0).map(([decision, count]) => `${count} ${decision}`).join(", ") || "none";
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3447
|
+
}
|
|
3448
|
+
#countTrigger(trigger) {
|
|
3449
|
+
return this.#usage.invocations.filter((item) => item.trigger === trigger).length;
|
|
3450
|
+
}
|
|
3451
|
+
#triggersLine() {
|
|
3452
|
+
return [
|
|
3453
|
+
"manual",
|
|
3454
|
+
"executor-requested",
|
|
3455
|
+
"repeated-tool-call",
|
|
3456
|
+
"completion-review",
|
|
3457
|
+
"custom-rule"
|
|
3458
|
+
].filter((trigger) => this.#countTrigger(trigger) > 0).join(", ") || "none";
|
|
3459
|
+
}
|
|
3460
|
+
summary(limit) {
|
|
3461
|
+
const { invocations, totals } = this.#usage;
|
|
3462
|
+
if (invocations.length === 0 && this.#repetition.interventions === 0) {
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
const markdown = invocations.filter((item) => item.kind === "markdown");
|
|
3466
|
+
const gates = invocations.filter((item) => item.kind === "gate");
|
|
3467
|
+
const effects = (effect) => invocations.filter((item) => item.executionEffect === effect).length;
|
|
3468
|
+
const failures = invocations.filter((item) => item.failure).map((item) => item.failure);
|
|
3469
|
+
const models = [...new Set(invocations.map((item) => item.model).filter(Boolean))].join(", ") || "unknown";
|
|
3230
3470
|
const budget = limit === undefined ? `${this.#consumedCalls} used; unlimited remaining` : `${this.#consumedCalls} / ${limit} used; ${Math.max(0, limit - this.#consumedCalls)} remaining`;
|
|
3231
3471
|
return [
|
|
3232
3472
|
"[Session Advisor Summary]",
|
|
3233
|
-
`Consultations: ${markdown.length} Markdown (${countTrigger("manual")} manual, ${countTrigger("executor-requested")} executor-requested), automatic gates: ${gates.length}`,
|
|
3234
|
-
`Triggers: ${
|
|
3473
|
+
`Consultations: ${markdown.length} Markdown (${this.#countTrigger("manual")} manual, ${this.#countTrigger("executor-requested")} executor-requested), automatic gates: ${gates.length}`,
|
|
3474
|
+
`Triggers: ${this.#triggersLine()}`,
|
|
3235
3475
|
`Models: ${models}`,
|
|
3236
3476
|
`Budget: ${budget}`,
|
|
3237
|
-
`Usage: ${formatAdvisorUsageTotals(
|
|
3238
|
-
`Markdown advice: ${markdown.length} responses (${this.#draftConsultations} with drafts)`,
|
|
3239
|
-
`Outcome reports: ${this.#outcomes}`,
|
|
3240
|
-
`Gate decisions: ${
|
|
3241
|
-
`Loop matching: normalized tool signatures; ${this.#
|
|
3477
|
+
`Usage: ${formatAdvisorUsageTotals(totals)}`,
|
|
3478
|
+
`Markdown advice: ${markdown.length} responses (${this.#ledger.draftConsultations} with drafts)`,
|
|
3479
|
+
`Outcome reports: ${this.#ledger.outcomes}`,
|
|
3480
|
+
`Gate decisions: ${this.#decisionsLine()}`,
|
|
3481
|
+
`Loop matching: normalized tool signatures; ${this.#repetition.interventions} gate intervention${this.#repetition.interventions === 1 ? "" : "s"}`,
|
|
3242
3482
|
`Execution effects: ${effects("tool-blocked")} tool blocked, ${effects("session-blocked")} sessions blocked, ${effects("continued")} continued`,
|
|
3243
3483
|
`Failures: ${failures.length ? failures.join(", ") : "none"}`
|
|
3244
3484
|
].join(`
|
|
@@ -4099,7 +4339,6 @@ import {
|
|
|
4099
4339
|
|
|
4100
4340
|
// src/ui/settings-formatting.ts
|
|
4101
4341
|
import { visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
4102
|
-
var DEFAULT_EFFORT_LEVEL2 = "Default (Model Default)";
|
|
4103
4342
|
var TOGGLE_VALUES = ["On", "Off"];
|
|
4104
4343
|
var SIMPLE_MODE_GRADIENT_INTERVAL_MS = 100;
|
|
4105
4344
|
var SIMPLE_MODE_GRADIENT_COLORS = [
|
|
@@ -4156,7 +4395,7 @@ var contextDescription = (presets, contextMaxChars) => {
|
|
|
4156
4395
|
${meterPrefix}${meter} full
|
|
4157
4396
|
${markerLabel}`;
|
|
4158
4397
|
};
|
|
4159
|
-
var currentEffort = (effort) => effort ||
|
|
4398
|
+
var currentEffort = (effort) => effort || DEFAULT_EFFORT_LEVEL;
|
|
4160
4399
|
var rainbowGradient = (text, startedAt) => {
|
|
4161
4400
|
const frame = Math.floor((Date.now() - startedAt) / SIMPLE_MODE_GRADIENT_INTERVAL_MS);
|
|
4162
4401
|
const shinePosition = frame % (text.length * 2);
|
|
@@ -4746,8 +4985,8 @@ import {
|
|
|
4746
4985
|
unlink,
|
|
4747
4986
|
writeFile
|
|
4748
4987
|
} from "node:fs/promises";
|
|
4749
|
-
import { join as
|
|
4750
|
-
import { getAgentDir as
|
|
4988
|
+
import { join as join4 } from "node:path";
|
|
4989
|
+
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
4751
4990
|
var ADOPTIONS = [
|
|
4752
4991
|
"followed",
|
|
4753
4992
|
"not-followed",
|
|
@@ -4760,11 +4999,11 @@ var VALIDATIONS = [
|
|
|
4760
4999
|
"unknown"
|
|
4761
5000
|
];
|
|
4762
5001
|
var MAX_LOG_BYTES = 1024 * 1024;
|
|
4763
|
-
var statePath = () =>
|
|
4764
|
-
var outcomeLogPath = () =>
|
|
5002
|
+
var statePath = () => join4(getAgentDir3(), "advisor-outcomes-salt");
|
|
5003
|
+
var outcomeLogPath = () => join4(getAgentDir3(), "advisor-outcomes.jsonl");
|
|
4765
5004
|
var salt = async () => {
|
|
4766
5005
|
const path = statePath();
|
|
4767
|
-
await mkdir(
|
|
5006
|
+
await mkdir(getAgentDir3(), { mode: 448, recursive: true });
|
|
4768
5007
|
for (let attempt = 0;attempt < 20; attempt += 1) {
|
|
4769
5008
|
try {
|
|
4770
5009
|
const existing = await readFile(path);
|
|
@@ -4841,7 +5080,7 @@ var withOutcomeLock = async (run) => {
|
|
|
4841
5080
|
var adviceDigest = (advice, key) => createHmac("sha256", key).update(advice).digest("hex").slice(0, 16);
|
|
4842
5081
|
var appendOutcome = async (record) => {
|
|
4843
5082
|
const path = outcomeLogPath();
|
|
4844
|
-
await mkdir(
|
|
5083
|
+
await mkdir(getAgentDir3(), { mode: 448, recursive: true });
|
|
4845
5084
|
return withOutcomeLock(async () => {
|
|
4846
5085
|
const next = {
|
|
4847
5086
|
adoption: record.adoption,
|
|
@@ -4949,7 +5188,7 @@ var reserveAdvisorCall = (event, ctx, session, reservedCalls) => {
|
|
|
4949
5188
|
import {
|
|
4950
5189
|
getMarkdownTheme as getMarkdownTheme4
|
|
4951
5190
|
} from "@earendil-works/pi-coding-agent";
|
|
4952
|
-
import { Box as Box3, Markdown as Markdown4, Text as Text5 } from "@earendil-works/pi-tui";
|
|
5191
|
+
import { Box as Box3, Markdown as Markdown4, Spacer, Text as Text5 } from "@earendil-works/pi-tui";
|
|
4953
5192
|
var advisorResultDetails = (result) => result.details;
|
|
4954
5193
|
var syncRenderPhase = (context, phase) => {
|
|
4955
5194
|
if (context.state.phase !== phase && context.state.timerId) {
|
|
@@ -5036,7 +5275,7 @@ var renderFinalAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
5036
5275
|
box.addChild(new Markdown4(adviceForDisplay(displayAdvice, expanded), 0, 0, getMarkdownTheme4()));
|
|
5037
5276
|
};
|
|
5038
5277
|
var renderAdvisorResult = (result, { isPartial, expanded }, theme, context) => {
|
|
5039
|
-
const box = context.lastComponent instanceof Box3 ? context.lastComponent : new Box3(1,
|
|
5278
|
+
const box = context.lastComponent instanceof Box3 ? context.lastComponent : new Box3(1, 0, (text) => theme.bg("customMessageBg", text));
|
|
5040
5279
|
box.setBgFn((text) => theme.bg("customMessageBg", text));
|
|
5041
5280
|
box.clear();
|
|
5042
5281
|
if (isPartial) {
|
|
@@ -5044,6 +5283,7 @@ var renderAdvisorResult = (result, { isPartial, expanded }, theme, context) => {
|
|
|
5044
5283
|
} else {
|
|
5045
5284
|
renderFinalAdvisorResult(box, result, expanded, theme, context);
|
|
5046
5285
|
}
|
|
5286
|
+
box.addChild(new Spacer(1));
|
|
5047
5287
|
return box;
|
|
5048
5288
|
};
|
|
5049
5289
|
|
|
@@ -5231,6 +5471,44 @@ var sendAutomaticGateResult = (pi, result) => {
|
|
|
5231
5471
|
display: true
|
|
5232
5472
|
}, { deliverAs: "steer" });
|
|
5233
5473
|
};
|
|
5474
|
+
var applyGateDecision = (pi, ctx, session, result, reason, failureMode) => {
|
|
5475
|
+
if (!result.ok) {
|
|
5476
|
+
session.recordInvocation({
|
|
5477
|
+
executionEffect: gateFailureEffectForMode(failureMode),
|
|
5478
|
+
failure: result.category,
|
|
5479
|
+
kind: "gate",
|
|
5480
|
+
model: advisorRef,
|
|
5481
|
+
trigger: "repeated-tool-call",
|
|
5482
|
+
usage: result.usage
|
|
5483
|
+
});
|
|
5484
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
5485
|
+
const failure = failureEffect(result.category, result.message, ctx, session, failureMode);
|
|
5486
|
+
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, result.usage);
|
|
5487
|
+
return failure.block ? { block: true, reason: `${reason}
|
|
5488
|
+
${failure.reason}` } : undefined;
|
|
5489
|
+
}
|
|
5490
|
+
session.recordInvocation({
|
|
5491
|
+
cost: advisorUsageCost(result.usage),
|
|
5492
|
+
decision: result.decision,
|
|
5493
|
+
executionEffect: gateDecisionEffect(result.decision, failureMode),
|
|
5494
|
+
kind: "gate",
|
|
5495
|
+
model: result.model,
|
|
5496
|
+
trigger: result.trigger,
|
|
5497
|
+
usage: result.usage
|
|
5498
|
+
});
|
|
5499
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
5500
|
+
sendAutomaticGateResult(pi, result);
|
|
5501
|
+
if (result.decision === "proceed") {
|
|
5502
|
+
session.resetRepetition();
|
|
5503
|
+
return;
|
|
5504
|
+
}
|
|
5505
|
+
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
5506
|
+
if (result.decision === "blocked") {
|
|
5507
|
+
const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode);
|
|
5508
|
+
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
5509
|
+
}
|
|
5510
|
+
return { block: true, reason: gateReason };
|
|
5511
|
+
};
|
|
5234
5512
|
var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus) => {
|
|
5235
5513
|
if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
|
|
5236
5514
|
return;
|
|
@@ -5265,42 +5543,7 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
|
|
|
5265
5543
|
}
|
|
5266
5544
|
}, event.toolCallId);
|
|
5267
5545
|
ensureGateCall();
|
|
5268
|
-
|
|
5269
|
-
session.recordInvocation({
|
|
5270
|
-
executionEffect: gateFailureEffectForMode(failureMode),
|
|
5271
|
-
failure: result.category,
|
|
5272
|
-
kind: "gate",
|
|
5273
|
-
model: advisorRef,
|
|
5274
|
-
trigger: "repeated-tool-call",
|
|
5275
|
-
usage: result.usage
|
|
5276
|
-
});
|
|
5277
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
5278
|
-
const failure = failureEffect(result.category, result.message, ctx, session, failureMode);
|
|
5279
|
-
sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, result.usage);
|
|
5280
|
-
return failure.block ? { block: true, reason: `${reason}
|
|
5281
|
-
${failure.reason}` } : undefined;
|
|
5282
|
-
}
|
|
5283
|
-
session.recordInvocation({
|
|
5284
|
-
cost: advisorUsageCost(result.usage),
|
|
5285
|
-
decision: result.decision,
|
|
5286
|
-
executionEffect: gateDecisionEffect(result.decision, failureMode),
|
|
5287
|
-
kind: "gate",
|
|
5288
|
-
model: result.model,
|
|
5289
|
-
trigger: result.trigger,
|
|
5290
|
-
usage: result.usage
|
|
5291
|
-
});
|
|
5292
|
-
updateAdvisorUsageStatus(ctx, session);
|
|
5293
|
-
sendAutomaticGateResult(pi, result);
|
|
5294
|
-
if (result.decision === "proceed") {
|
|
5295
|
-
session.resetRepetition();
|
|
5296
|
-
return;
|
|
5297
|
-
}
|
|
5298
|
-
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
5299
|
-
if (result.decision === "blocked") {
|
|
5300
|
-
const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode);
|
|
5301
|
-
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
5302
|
-
}
|
|
5303
|
-
return { block: true, reason: gateReason };
|
|
5546
|
+
return applyGateDecision(pi, ctx, session, result, reason, failureMode);
|
|
5304
5547
|
} finally {
|
|
5305
5548
|
scoutStatus.release(ctx, scoutStatusToken);
|
|
5306
5549
|
herdrAdvisorActivity.finish();
|