@aragon-agent/cli 0.6.38 → 0.6.40
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 +263 -0
- package/README.md +544 -11
- package/dist/agent/controller.d.ts +245 -10
- package/dist/agent/controller.js +367 -22
- package/dist/agent/model-profile-settings.js +12 -1
- package/dist/agent/plan-prompt.d.ts +1 -3
- package/dist/agent/plan-prompt.js +13 -7
- package/dist/agent/reducer.d.ts +70 -2
- package/dist/agent/reducer.js +59 -6
- package/dist/agent/skill-triage-prompt.d.ts +42 -0
- package/dist/agent/skill-triage-prompt.js +74 -0
- package/dist/agent/system-prompt.d.ts +28 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +106 -32
- package/dist/commands/builtins.js +24 -3
- package/dist/commands/keys-command.d.ts +23 -0
- package/dist/commands/keys-command.js +38 -0
- package/dist/commands/registry.d.ts +39 -0
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +25 -0
- package/dist/commands/subagent-command.js +242 -0
- package/dist/commands/task-command.d.ts +39 -0
- package/dist/commands/task-command.js +102 -0
- package/dist/compaction/command.js +16 -3
- package/dist/compaction/compactor.d.ts +25 -0
- package/dist/compaction/compactor.js +111 -10
- package/dist/compaction/digest.d.ts +24 -1
- package/dist/compaction/digest.js +139 -17
- package/dist/compaction/limits.d.ts +21 -0
- package/dist/compaction/limits.js +21 -0
- package/dist/compaction/reason-text.d.ts +36 -0
- package/dist/compaction/reason-text.js +78 -0
- package/dist/compaction/types.d.ts +82 -14
- package/dist/compaction/wiring.d.ts +13 -8
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +23 -0
- package/dist/config/cli-commands.js +36 -0
- package/dist/config/env.js +17 -4
- package/dist/config/load.js +55 -3
- package/dist/config/schema.d.ts +125 -10
- package/dist/config/schema.js +138 -14
- package/dist/config/store.js +23 -3
- package/dist/diagnostics/doctor.d.ts +9 -0
- package/dist/diagnostics/doctor.js +34 -0
- package/dist/diagnostics/info.d.ts +71 -0
- package/dist/diagnostics/info.js +91 -5
- package/dist/exec/bridge/caller-tools.d.ts +96 -0
- package/dist/exec/bridge/caller-tools.js +213 -0
- package/dist/exec/bridge/decisions.d.ts +78 -0
- package/dist/exec/bridge/decisions.js +116 -0
- package/dist/exec/bridge/human-answers.d.ts +38 -0
- package/dist/exec/bridge/human-answers.js +164 -0
- package/dist/exec/bridge/limits.d.ts +68 -0
- package/dist/exec/bridge/limits.js +68 -0
- package/dist/exec/bridge/ndjson.d.ts +17 -0
- package/dist/exec/bridge/ndjson.js +83 -0
- package/dist/exec/bridge/pending.d.ts +70 -0
- package/dist/exec/bridge/pending.js +132 -0
- package/dist/exec/bridge/protocol.d.ts +346 -0
- package/dist/exec/bridge/protocol.js +256 -0
- package/dist/exec/bridge/server.d.ts +142 -0
- package/dist/exec/bridge/server.js +502 -0
- package/dist/exec/bridge/source.d.ts +58 -0
- package/dist/exec/bridge/source.js +145 -0
- package/dist/exec/bridge/writer.d.ts +26 -0
- package/dist/exec/bridge/writer.js +156 -0
- package/dist/exec/cli-commands.js +30 -2
- package/dist/exec/emitter.d.ts +16 -2
- package/dist/exec/emitter.js +26 -6
- package/dist/exec/events.d.ts +465 -10
- package/dist/exec/events.js +47 -1
- package/dist/exec/hooks/dispatch.d.ts +76 -0
- package/dist/exec/hooks/dispatch.js +169 -0
- package/dist/exec/hooks/index.d.ts +83 -0
- package/dist/exec/hooks/index.js +205 -0
- package/dist/exec/hooks/limits.d.ts +56 -0
- package/dist/exec/hooks/limits.js +49 -0
- package/dist/exec/hooks/registry.d.ts +49 -0
- package/dist/exec/hooks/registry.js +91 -0
- package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
- package/dist/exec/hooks/tool-wrapper.js +154 -0
- package/dist/exec/hooks/turn.d.ts +61 -0
- package/dist/exec/hooks/turn.js +59 -0
- package/dist/exec/hooks/validate.d.ts +85 -0
- package/dist/exec/hooks/validate.js +310 -0
- package/dist/exec/index.d.ts +130 -4
- package/dist/exec/index.js +501 -10
- package/dist/exec/options.d.ts +124 -2
- package/dist/exec/options.js +413 -6
- package/dist/exec/permission.d.ts +9 -1
- package/dist/exec/permission.js +7 -1
- package/dist/exec/request/limits.d.ts +51 -0
- package/dist/exec/request/limits.js +49 -0
- package/dist/exec/request/scope.d.ts +106 -0
- package/dist/exec/request/scope.js +149 -0
- package/dist/exec/result/limits.d.ts +74 -0
- package/dist/exec/result/limits.js +72 -0
- package/dist/exec/result/prompt.d.ts +28 -0
- package/dist/exec/result/prompt.js +74 -0
- package/dist/exec/result/schema.d.ts +99 -0
- package/dist/exec/result/schema.js +464 -0
- package/dist/exec/result/sink.d.ts +56 -0
- package/dist/exec/result/sink.js +50 -0
- package/dist/exec/result/tool.d.ts +37 -0
- package/dist/exec/result/tool.js +118 -0
- package/dist/exec/runner.d.ts +395 -1
- package/dist/exec/runner.js +860 -8
- package/dist/exec/threads/aggregate.d.ts +34 -0
- package/dist/exec/threads/aggregate.js +30 -0
- package/dist/exec/threads/capacity.d.ts +23 -0
- package/dist/exec/threads/capacity.js +38 -0
- package/dist/exec/threads/dispatch-source.d.ts +60 -0
- package/dist/exec/threads/dispatch-source.js +106 -0
- package/dist/exec/threads/execute-threads.d.ts +53 -0
- package/dist/exec/threads/execute-threads.js +234 -0
- package/dist/exec/threads/gate.d.ts +42 -0
- package/dist/exec/threads/gate.js +81 -0
- package/dist/exec/threads/identity.d.ts +36 -0
- package/dist/exec/threads/identity.js +67 -0
- package/dist/exec/threads/interrupt.d.ts +29 -0
- package/dist/exec/threads/interrupt.js +79 -0
- package/dist/exec/threads/limits.d.ts +58 -0
- package/dist/exec/threads/limits.js +35 -0
- package/dist/exec/threads/outcome.d.ts +47 -0
- package/dist/exec/threads/outcome.js +26 -0
- package/dist/exec/threads/registry.d.ts +81 -0
- package/dist/exec/threads/registry.js +427 -0
- package/dist/exec/threads/runtime.d.ts +107 -0
- package/dist/exec/threads/runtime.js +523 -0
- package/dist/exec/threads/thread-source.d.ts +20 -0
- package/dist/exec/threads/thread-source.js +134 -0
- package/dist/fast/limits.d.ts +54 -0
- package/dist/fast/limits.js +54 -0
- package/dist/fast/summary-call.d.ts +72 -0
- package/dist/fast/summary-call.js +133 -0
- package/dist/fast/summary-prompt.d.ts +106 -0
- package/dist/fast/summary-prompt.js +320 -0
- package/dist/fast/task-summary-types.d.ts +66 -0
- package/dist/fast/task-summary-types.js +17 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/limits.d.ts +7 -0
- package/dist/proc/limits.js +7 -0
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- package/dist/proc/output-streams.d.ts +50 -0
- package/dist/proc/output-streams.js +54 -0
- package/dist/skills/service.d.ts +41 -0
- package/dist/skills/service.js +62 -0
- package/dist/skills/triage.d.ts +194 -0
- package/dist/skills/triage.js +393 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/limits.d.ts +21 -1
- package/dist/team/limits.js +21 -1
- package/dist/team/normalize.d.ts +15 -0
- package/dist/team/normalize.js +21 -4
- package/dist/team/prompt.d.ts +6 -3
- package/dist/team/prompt.js +9 -5
- package/dist/team/runtime.d.ts +9 -0
- package/dist/team/runtime.js +8 -0
- package/dist/team/subagent.d.ts +15 -0
- package/dist/team/subagent.js +45 -1
- package/dist/team/task-tool.js +11 -7
- package/dist/tools/bash-tool.d.ts +10 -0
- package/dist/tools/bash-tool.js +71 -6
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/index.d.ts +93 -1
- package/dist/tools/index.js +112 -3
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/tools/tool-gate.d.ts +100 -0
- package/dist/tools/tool-gate.js +87 -0
- package/dist/ui/App.js +238 -12
- package/dist/ui/Header.d.ts +28 -16
- package/dist/ui/Header.js +21 -19
- package/dist/ui/StatusBar.d.ts +1 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +11 -1
- package/dist/ui/entries/CompactionCard.js +65 -4
- package/dist/ui/interaction-hints.js +6 -0
- package/dist/ui/layout/OverlayFrame.d.ts +18 -0
- package/dist/ui/layout/OverlayFrame.js +19 -4
- package/dist/ui/layout/header-hints.d.ts +55 -62
- package/dist/ui/layout/header-hints.js +59 -166
- package/dist/ui/layout/header-left.d.ts +115 -0
- package/dist/ui/layout/header-left.js +206 -0
- package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
- package/dist/ui/layout/shortcut-panel-layout.js +321 -0
- package/dist/ui/layout/status-detail-layout.js +5 -1
- package/dist/ui/layout/status-layout.d.ts +10 -0
- package/dist/ui/layout/status-layout.js +18 -1
- package/dist/ui/layout/task-summary-cell.d.ts +61 -0
- package/dist/ui/layout/task-summary-cell.js +65 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
- package/dist/ui/overlays/HelpOverlay.js +29 -2
- package/dist/ui/overlays/SettingsScreen.js +29 -5
- package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
- package/dist/ui/overlays/ShortcutPanel.js +106 -0
- package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
- package/dist/ui/overlays/TeamFanoutPage.js +78 -0
- package/dist/ui/overlays/model-profile-state.d.ts +19 -1
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
- package/dist/ui/overlays/team-fanout-state.js +97 -0
- package/dist/ui/shortcuts.d.ts +81 -0
- package/dist/ui/shortcuts.js +221 -0
- package/package.json +2 -2
package/dist/config/schema.js
CHANGED
|
@@ -273,20 +273,23 @@ export function clampScrollResumeMs(v, fallback) {
|
|
|
273
273
|
// nested-object merge is hand-written and capped at one level; three scalars do
|
|
274
274
|
// not justify a second `clampXConfig` gate plus two more merge sites, and flat
|
|
275
275
|
// keys mean `store.ts` needs ZERO changes.
|
|
276
|
+
//
|
|
277
|
+
// ONE OF THOSE KEYS IS GONE: `planModeMaxAskRounds` capped `ask_user` at four
|
|
278
|
+
// rounds per turn. There is no cap any more, so there is no number to store, no
|
|
279
|
+
// range to clamp and nothing to configure. It is retired through
|
|
280
|
+
// `RETIRED_CONFIG_KEYS` below rather than defaulted to a large number — a
|
|
281
|
+
// ninety-nine in the config file is the same second source of truth as a four.
|
|
276
282
|
// ---------------------------------------------------------------------------
|
|
277
|
-
/** `ask_user` calls allowed per user turn. */
|
|
278
|
-
export const DEFAULT_PLAN_MAX_ASK_ROUNDS = 4;
|
|
279
|
-
const ASK_ROUNDS_RANGE = { min: 1, max: 10 };
|
|
280
283
|
/**
|
|
281
284
|
* Ceiling on ONE human wait, in ms. Thirty minutes is far outside "the user
|
|
282
285
|
* went to get coffee", but a ceiling has to exist: an unattended terminal must
|
|
283
286
|
* not wedge a CI job or a stale ssh session forever.
|
|
287
|
+
*
|
|
288
|
+
* THIS IS NOT A ROUND LIMIT and the distinction matters. It bounds a single wait
|
|
289
|
+
* (liveness); it says nothing about how many times the model may ask.
|
|
284
290
|
*/
|
|
285
291
|
export const DEFAULT_PLAN_HUMAN_TIMEOUT_MS = 1_800_000;
|
|
286
292
|
const HUMAN_TIMEOUT_RANGE = { min: 60_000, max: 7_200_000 };
|
|
287
|
-
export function clampAskRounds(v, fallback) {
|
|
288
|
-
return clampInt(v, fallback, ASK_ROUNDS_RANGE);
|
|
289
|
-
}
|
|
290
293
|
export function clampHumanTimeout(v, fallback) {
|
|
291
294
|
return clampInt(v, fallback, HUMAN_TIMEOUT_RANGE);
|
|
292
295
|
}
|
|
@@ -322,6 +325,9 @@ export const DEFAULT_SKILLS_CONFIG = {
|
|
|
322
325
|
integrity: 'warn',
|
|
323
326
|
usageTracking: true,
|
|
324
327
|
toolPolicy: 'enforce',
|
|
328
|
+
// ON by default: the capability this key controls is the feature's whole point,
|
|
329
|
+
// and it costs zero model calls (local matching over in-memory records).
|
|
330
|
+
triage: true,
|
|
325
331
|
};
|
|
326
332
|
const CATALOG_BYTES_RANGE = { min: 500, max: 40_000 };
|
|
327
333
|
/**
|
|
@@ -406,6 +412,7 @@ export function clampSkillsConfig(raw) {
|
|
|
406
412
|
integrity: clampSkillsIntegrity(src.integrity, DEFAULT_SKILLS_CONFIG.integrity),
|
|
407
413
|
usageTracking: bool(src.usageTracking, DEFAULT_SKILLS_CONFIG.usageTracking),
|
|
408
414
|
toolPolicy: clampSkillsToolPolicy(src.toolPolicy, DEFAULT_SKILLS_CONFIG.toolPolicy),
|
|
415
|
+
triage: bool(src.triage, DEFAULT_SKILLS_CONFIG.triage),
|
|
409
416
|
};
|
|
410
417
|
}
|
|
411
418
|
export const DEFAULT_SKILLS_RUNTIME = { forcedSkills: [], approveAll: false };
|
|
@@ -474,8 +481,12 @@ export function clampLogConfig(raw) {
|
|
|
474
481
|
export const HARD_MAX_SUBAGENTS = TEAM_LIMITS.hardMaxSubagents;
|
|
475
482
|
export const DEFAULT_TEAM_CONFIG = {
|
|
476
483
|
enabled: true,
|
|
477
|
-
|
|
478
|
-
maxConcurrent
|
|
484
|
+
// 8 IS THE DEFAULT, 10 IS THE CEILING, and they are not the same number
|
|
485
|
+
// (subagent-fanout D-1 / D-2). `maxConcurrent` moves WITH `maxSubagents`
|
|
486
|
+
// because a wider population behind a 3-wide pool shows the user nothing:
|
|
487
|
+
// children 4..8 simply queue (`team-bus.test.ts` records the queued phase).
|
|
488
|
+
maxSubagents: 8,
|
|
489
|
+
maxConcurrent: 8,
|
|
479
490
|
// 0 = NO LIMIT. Subagents run under the same regime as the lead: no
|
|
480
491
|
// wall clock, no turn cap; only the idle watchdog and the user's Esc.
|
|
481
492
|
subagentTimeoutMs: 0,
|
|
@@ -534,6 +545,13 @@ export function clampTeamConfig(raw) {
|
|
|
534
545
|
&& [...src.modelProfileId].length <= 128
|
|
535
546
|
? src.modelProfileId
|
|
536
547
|
: null,
|
|
548
|
+
// TRANSPARENT, and only ever `true` (subagent-fanout D-3 / R-6). The flag is
|
|
549
|
+
// not a setting: it is the one-shot marker the migration leaves behind, so a
|
|
550
|
+
// clamp that dropped it would re-migrate the user on every launch. Written
|
|
551
|
+
// as a spread rather than `=== true ? true : undefined` so the key stays
|
|
552
|
+
// ABSENT (not `undefined`) in the clamped result - `toEqual(DEFAULT_TEAM_CONFIG)`
|
|
553
|
+
// and the on-disk shape both depend on that.
|
|
554
|
+
...(src.fanoutMigrated === true ? { fanoutMigrated: true } : {}),
|
|
537
555
|
};
|
|
538
556
|
}
|
|
539
557
|
/**
|
|
@@ -574,6 +592,56 @@ export function legacyTeamCeilingPatch(raw) {
|
|
|
574
592
|
}
|
|
575
593
|
return matched ? patch : null;
|
|
576
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* The fan-out defaults cli <= 0.6.x persisted as its OWN defaults
|
|
597
|
+
* (subagent-fanout F-2 / R-1).
|
|
598
|
+
*
|
|
599
|
+
* THE SAME TRAP AS THE CEILINGS ABOVE, ONE SECTION OVER. `mergePersistedConfig`
|
|
600
|
+
* writes the WHOLE `team` section on every save, so a user who has ever touched
|
|
601
|
+
* any setting carries this exact pair in `config.json` - changing
|
|
602
|
+
* `DEFAULT_TEAM_CONFIG` alone would never reach them, and they would keep
|
|
603
|
+
* seeing "only 3 subagents at once" for ever.
|
|
604
|
+
*/
|
|
605
|
+
export const LEGACY_TEAM_FANOUT_DEFAULTS = {
|
|
606
|
+
maxSubagents: 5,
|
|
607
|
+
maxConcurrent: 3,
|
|
608
|
+
};
|
|
609
|
+
/**
|
|
610
|
+
* The `{ maxSubagents, maxConcurrent, fanoutMigrated }` patch for the legacy
|
|
611
|
+
* default pair, or `null` when nothing matches (subagent-fanout D-3).
|
|
612
|
+
*
|
|
613
|
+
* EXACT-EQUALITY ONLY, the rule `legacyTeamCeilingPatch` records: a value that
|
|
614
|
+
* differs was a deliberate user choice and survives untouched. Two differences
|
|
615
|
+
* from that function, both deliberate:
|
|
616
|
+
*
|
|
617
|
+
* - `fanoutMigrated: true` short-circuits to `null`. That is what makes "the
|
|
618
|
+
* user changed their mind and went back to 5/3" stick: without it the next
|
|
619
|
+
* launch would migrate them again, every launch, for ever.
|
|
620
|
+
* - `maxConcurrent` is allowed to be ABSENT as well as 3, because a
|
|
621
|
+
* hand-written section may name only one key and a half-written pair is
|
|
622
|
+
* still the legacy intent, not a user decision.
|
|
623
|
+
*
|
|
624
|
+
* Pure on purpose: `loadConfig` decides WHERE the patch is applied and
|
|
625
|
+
* persisted; this function only answers WHICH keys match, so the equality test
|
|
626
|
+
* lives beside the constants it reads.
|
|
627
|
+
*/
|
|
628
|
+
export function legacyTeamFanoutPatch(raw) {
|
|
629
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
630
|
+
return null;
|
|
631
|
+
const src = raw;
|
|
632
|
+
if (src.fanoutMigrated === true)
|
|
633
|
+
return null;
|
|
634
|
+
if (src.maxSubagents !== LEGACY_TEAM_FANOUT_DEFAULTS.maxSubagents)
|
|
635
|
+
return null;
|
|
636
|
+
if (src.maxConcurrent !== undefined
|
|
637
|
+
&& src.maxConcurrent !== LEGACY_TEAM_FANOUT_DEFAULTS.maxConcurrent)
|
|
638
|
+
return null;
|
|
639
|
+
return {
|
|
640
|
+
maxSubagents: DEFAULT_TEAM_CONFIG.maxSubagents,
|
|
641
|
+
maxConcurrent: DEFAULT_TEAM_CONFIG.maxConcurrent,
|
|
642
|
+
fanoutMigrated: true,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
577
645
|
export const DEFAULT_TODO_CONFIG = {
|
|
578
646
|
enabled: true,
|
|
579
647
|
panel: true,
|
|
@@ -966,7 +1034,6 @@ export function clampJobsConfig(raw) {
|
|
|
966
1034
|
export const DEFAULT_PROMPTS_CONFIG = {
|
|
967
1035
|
enabled: true,
|
|
968
1036
|
insertMode: 'insert',
|
|
969
|
-
hint: true,
|
|
970
1037
|
};
|
|
971
1038
|
const PROMPT_INSERT_MODES = ['insert', 'replace'];
|
|
972
1039
|
/**
|
|
@@ -985,7 +1052,26 @@ export function clampPromptsConfig(raw) {
|
|
|
985
1052
|
insertMode: PROMPT_INSERT_MODES.includes(mode)
|
|
986
1053
|
? mode
|
|
987
1054
|
: DEFAULT_PROMPTS_CONFIG.insertMode,
|
|
988
|
-
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
export const DEFAULT_TASK_SUMMARY_CONFIG = {
|
|
1058
|
+
enabled: true,
|
|
1059
|
+
fallback: true,
|
|
1060
|
+
};
|
|
1061
|
+
/**
|
|
1062
|
+
* THE single gate for every `taskSummary` read AND write, like the twelve sections
|
|
1063
|
+
* above it.
|
|
1064
|
+
*
|
|
1065
|
+
* BOTH DEFAULTS ARE `true`, AND THE DIRECTION MATTERS: a corrupt or hand-edited
|
|
1066
|
+
* section must not silently switch off a feature the user asked for, and - more
|
|
1067
|
+
* importantly - it must not switch off the FLOOR, which is the only thing standing
|
|
1068
|
+
* between a failed call and an empty header cell.
|
|
1069
|
+
*/
|
|
1070
|
+
export function clampTaskSummaryConfig(raw) {
|
|
1071
|
+
const src = (raw && typeof raw === 'object' ? raw : {});
|
|
1072
|
+
return {
|
|
1073
|
+
enabled: bool(src.enabled, DEFAULT_TASK_SUMMARY_CONFIG.enabled),
|
|
1074
|
+
fallback: bool(src.fallback, DEFAULT_TASK_SUMMARY_CONFIG.fallback),
|
|
989
1075
|
};
|
|
990
1076
|
}
|
|
991
1077
|
/**
|
|
@@ -1051,7 +1137,6 @@ export const DEFAULT_CONFIG = {
|
|
|
1051
1137
|
keyboardEnhancement: true,
|
|
1052
1138
|
scrollResumeMs: DEFAULT_SCROLL_RESUME_MS,
|
|
1053
1139
|
planModeDefault: false,
|
|
1054
|
-
planModeMaxAskRounds: DEFAULT_PLAN_MAX_ASK_ROUNDS,
|
|
1055
1140
|
planModeHumanTimeoutMs: DEFAULT_PLAN_HUMAN_TIMEOUT_MS,
|
|
1056
1141
|
skills: DEFAULT_SKILLS_CONFIG,
|
|
1057
1142
|
log: DEFAULT_LOG_CONFIG,
|
|
@@ -1093,14 +1178,22 @@ export const DEFAULT_CONFIG = {
|
|
|
1093
1178
|
// rather than quietly disabling something.
|
|
1094
1179
|
jobs: DEFAULT_JOBS_CONFIG,
|
|
1095
1180
|
// And one section later again, for the seventh time, and this one has BOTH
|
|
1096
|
-
// failure directions at once: `prompts.enabled`
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1181
|
+
// failure directions at once: `prompts.enabled` is `true` by default, so a
|
|
1182
|
+
// `config.json` predating this feature would resolve it to `undefined` (falsy)
|
|
1183
|
+
// and arrive with the key silently off - while
|
|
1099
1184
|
// `prompts.insertMode` would be `undefined`, which is neither `'insert'` nor
|
|
1100
1185
|
// `'replace'` and would make `planPromptFill` fall through to its `replace`
|
|
1101
1186
|
// branch. That branch discards the user's own words, so the miss would be a data
|
|
1102
1187
|
// loss rather than a missing hint.
|
|
1103
1188
|
prompts: DEFAULT_PROMPTS_CONFIG,
|
|
1189
|
+
// And one section later again, for the eighth time - and this one has a silent
|
|
1190
|
+
// DATA-LOSS-shaped miss: `taskSummary.enabled` and `.fallback` are both `true` by
|
|
1191
|
+
// default, and `App` reads `cfg.taskSummary?.fallback !== false` at call time. A
|
|
1192
|
+
// `config.json` predating this feature would resolve them to `undefined`, which
|
|
1193
|
+
// happens to behave like the defaults in those two readers but NOT in
|
|
1194
|
+
// `config list`, so the file would report a section it never wrote. One line
|
|
1195
|
+
// here, like the seven above it.
|
|
1196
|
+
taskSummary: DEFAULT_TASK_SUMMARY_CONFIG,
|
|
1104
1197
|
};
|
|
1105
1198
|
/**
|
|
1106
1199
|
* Runtime state that used to be stored in `config.json` by mistake
|
|
@@ -1136,6 +1229,37 @@ export function stripLegacyStateKeys(raw) {
|
|
|
1136
1229
|
delete out[key];
|
|
1137
1230
|
return out;
|
|
1138
1231
|
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Config keys whose MEANING was removed, mapped to the explanation a user gets if
|
|
1234
|
+
* they still try to set one.
|
|
1235
|
+
*
|
|
1236
|
+
* SEPARATE FROM `LEGACY_STATE_KEYS`, because the disposition differs: those keys'
|
|
1237
|
+
* values were MOVED somewhere else, while these have nowhere to go — the feature
|
|
1238
|
+
* they configured no longer exists. Merging the two lists would erase that
|
|
1239
|
+
* difference at exactly the moment someone is reading the file to find out what
|
|
1240
|
+
* happened to their setting.
|
|
1241
|
+
*
|
|
1242
|
+
* Stripped on the READ side for the same reason `stripLegacyStateKeys` is:
|
|
1243
|
+
* `loadPersistedConfig()` merges `{ ...DEFAULT_CONFIG, ...partial }`, so a key
|
|
1244
|
+
* left on disk rides that spread into memory and `updatePersistedConfig` writes it
|
|
1245
|
+
* straight back. The user deletes the line, launches once, and watches it return.
|
|
1246
|
+
*/
|
|
1247
|
+
export const RETIRED_CONFIG_KEYS = {
|
|
1248
|
+
planModeMaxAskRounds: 'ask_user has no round limit any more, so this key is gone. Nothing to set; ' +
|
|
1249
|
+
'the next config write removes it from config.json.',
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* THE single gate that keeps retired keys out of `config.json`. Applied on the
|
|
1253
|
+
* read side, so it is self-healing: every write is read-strip-merge-write, and a
|
|
1254
|
+
* hand-edited file, a downgrade to an older CLI and back, or a half-finished
|
|
1255
|
+
* migration all converge on a clean file at the next write.
|
|
1256
|
+
*/
|
|
1257
|
+
export function stripRetiredConfigKeys(raw) {
|
|
1258
|
+
const out = { ...raw };
|
|
1259
|
+
for (const key of Object.keys(RETIRED_CONFIG_KEYS))
|
|
1260
|
+
delete out[key];
|
|
1261
|
+
return out;
|
|
1262
|
+
}
|
|
1139
1263
|
// ---------------------------------------------------------------------------
|
|
1140
1264
|
// Coercion / validation helpers
|
|
1141
1265
|
// ---------------------------------------------------------------------------
|
package/dist/config/store.js
CHANGED
|
@@ -17,7 +17,7 @@ import { getConfigPath, getSessionsDir } from './app-paths.js';
|
|
|
17
17
|
import { registerProfileSecrets, registerSecretsFrom } from '../logging/secret-registry.js';
|
|
18
18
|
import { adaptLegacyConnectionPatch, ModelProfileConfigError, readModelSettingsDisk } from './model-profile-store.js';
|
|
19
19
|
import { normalizeModelProfiles } from './model-profiles.js';
|
|
20
|
-
import { clampBashConfig, clampJobsConfig, clampPromptsConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, } from './schema.js';
|
|
20
|
+
import { clampBashConfig, clampJobsConfig, clampPromptsConfig, clampTaskSummaryConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, stripRetiredConfigKeys, } from './schema.js';
|
|
21
21
|
/**
|
|
22
22
|
* Normalize `maxTokens` for both directions of the config file.
|
|
23
23
|
*
|
|
@@ -124,7 +124,12 @@ export function normalizePersistedConfig(input) {
|
|
|
124
124
|
// on disk would otherwise ride the spread into `merged` and be written back
|
|
125
125
|
// out by `updatePersistedConfig` — the "I deleted it and it grew back" bug
|
|
126
126
|
// that `stripLegacyStateKeys` exists to make impossible (§4.5).
|
|
127
|
-
|
|
127
|
+
//
|
|
128
|
+
// TWO PASSES, DELIBERATELY: `stripLegacyStateKeys` handles keys whose value
|
|
129
|
+
// MOVED, `stripRetiredConfigKeys` handles keys whose FEATURE was removed
|
|
130
|
+
// (`planModeMaxAskRounds`). Same door, different reasons, and each list keeps
|
|
131
|
+
// growing on its own schedule.
|
|
132
|
+
const partial = stripRetiredConfigKeys(stripLegacyStateKeys(input));
|
|
128
133
|
delete partial.fullscreen;
|
|
129
134
|
return {
|
|
130
135
|
...DEFAULT_CONFIG,
|
|
@@ -200,6 +205,14 @@ export function normalizePersistedConfig(input) {
|
|
|
200
205
|
// `'insert'` nor `'replace'`, so a fill into a non-empty draft would take the
|
|
201
206
|
// branch that REPLACES what the user typed.
|
|
202
207
|
prompts: clampPromptsConfig({ ...DEFAULT_CONFIG.prompts, ...(partial.prompts ?? {}) }),
|
|
208
|
+
// And the thirteenth, on the same line and for the same reason. Without it a
|
|
209
|
+
// `config.json` predating the header label resolves the whole section to
|
|
210
|
+
// `undefined`, and the two readers disagree: `App`'s `!== false` checks would
|
|
211
|
+
// treat it as ON while `config list` would report nothing at all.
|
|
212
|
+
taskSummary: clampTaskSummaryConfig({
|
|
213
|
+
...DEFAULT_CONFIG.taskSummary,
|
|
214
|
+
...(partial.taskSummary ?? {}),
|
|
215
|
+
}),
|
|
203
216
|
};
|
|
204
217
|
}
|
|
205
218
|
/**
|
|
@@ -308,11 +321,18 @@ export function mergePersistedConfig(current, patch) {
|
|
|
308
321
|
// user would never learn that their privacy switch had been turned back on.
|
|
309
322
|
jobs: clampJobsConfig({ ...current.jobs, ...(patch.jobs ?? {}) }),
|
|
310
323
|
// The twelfth section, and the seventh time this file records the same trap.
|
|
311
|
-
// `aragon config set prompts.
|
|
324
|
+
// `aragon config set prompts.insertMode replace` sends `{ prompts: { insertMode } }`;
|
|
312
325
|
// without this line the shallow top-level spread REPLACES the section and
|
|
313
326
|
// `insertMode` is written absent - which reads back as neither `'insert'` nor
|
|
314
327
|
// `'replace'`, i.e. the next fill overwrites the user's own draft.
|
|
315
328
|
prompts: clampPromptsConfig({ ...current.prompts, ...(patch.prompts ?? {}) }),
|
|
329
|
+
// The thirteenth section, and the eighth time this file records the same trap.
|
|
330
|
+
// `aragon config set taskSummary.fallback false` sends
|
|
331
|
+
// `{ taskSummary: { fallback: false } }`; without this line the shallow spread
|
|
332
|
+
// REPLACES the section and `enabled` is written absent - and the one direction
|
|
333
|
+
// that must never happen is a FAILED summary call landing on "show nothing"
|
|
334
|
+
// because a switch the user never touched defaulted away.
|
|
335
|
+
taskSummary: clampTaskSummaryConfig({ ...current.taskSummary, ...(patch.taskSummary ?? {}) }),
|
|
316
336
|
};
|
|
317
337
|
return merged;
|
|
318
338
|
}
|
|
@@ -27,3 +27,12 @@ export interface DoctorOptions {
|
|
|
27
27
|
probe?: boolean;
|
|
28
28
|
}
|
|
29
29
|
export declare function runDoctorChecks(flags: CliFlags, opts?: DoctorOptions): Promise<DoctorReport>;
|
|
30
|
+
/**
|
|
31
|
+
* Is the optional JSON-Schema validator importable?
|
|
32
|
+
*
|
|
33
|
+
* ASKED ONCE, HERE, AND REUSED BY `aragon info` - because "how strict is my result
|
|
34
|
+
* schema validated" is a property of this MACHINE, not of the contract, and there must
|
|
35
|
+
* be exactly one answer to it. `createRequire().resolve()` rather than `import('ajv')`
|
|
36
|
+
* because `info` is synchronous: resolution is the question, not loading.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasAjvValidator(): boolean;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* the caller guess, which is the state this command exists to end.
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
15
|
import process from 'node:process';
|
|
15
16
|
import { initProviders, ModelRegistry } from '@aragon-agent/core';
|
|
@@ -26,10 +27,43 @@ export async function runDoctorChecks(flags, opts = {}) {
|
|
|
26
27
|
checks.push(checkProviderKey(config));
|
|
27
28
|
checks.push(checkModel(config));
|
|
28
29
|
checks.push(checkSessionsDir());
|
|
30
|
+
checks.push(checkValidationStrength());
|
|
29
31
|
if (opts.probe)
|
|
30
32
|
checks.push(await probeProvider(config));
|
|
31
33
|
return { ok: checks.every((c) => c.verdict !== 'fail'), checks };
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Is the optional JSON-Schema validator importable?
|
|
37
|
+
*
|
|
38
|
+
* ASKED ONCE, HERE, AND REUSED BY `aragon info` - because "how strict is my result
|
|
39
|
+
* schema validated" is a property of this MACHINE, not of the contract, and there must
|
|
40
|
+
* be exactly one answer to it. `createRequire().resolve()` rather than `import('ajv')`
|
|
41
|
+
* because `info` is synchronous: resolution is the question, not loading.
|
|
42
|
+
*/
|
|
43
|
+
export function hasAjvValidator() {
|
|
44
|
+
try {
|
|
45
|
+
createRequire(import.meta.url).resolve('ajv');
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `ajv: present | absent` - a `pass`, never a `warn`.
|
|
54
|
+
*
|
|
55
|
+
* BOTH ANSWERS ARE CORRECT INSTALLS. `ajv` is an OPTIONAL dependency of Core, and its
|
|
56
|
+
* absence degrades validation honestly (the CLI's own belt still enforces `required`,
|
|
57
|
+
* recursive `properties`, `items` and `additionalProperties:false`). The check exists
|
|
58
|
+
* because the STRENGTH differs and nothing else in the product says so out loud.
|
|
59
|
+
*/
|
|
60
|
+
function checkValidationStrength() {
|
|
61
|
+
if (hasAjvValidator()) {
|
|
62
|
+
return pass('ajv', 'present: full JSON Schema keywords are enforced');
|
|
63
|
+
}
|
|
64
|
+
return pass('ajv', 'absent: required/properties/items/additionalProperties are still enforced by the ' +
|
|
65
|
+
'built-in validator; pattern/format/multipleOf are not');
|
|
66
|
+
}
|
|
33
67
|
function pass(name, detail) {
|
|
34
68
|
return { name, verdict: 'pass', detail, remedy: '' };
|
|
35
69
|
}
|
|
@@ -26,5 +26,76 @@ export interface ExecInfo {
|
|
|
26
26
|
inputFormats: string[];
|
|
27
27
|
permissionModes: string[];
|
|
28
28
|
features: string[];
|
|
29
|
+
/**
|
|
30
|
+
* The bridge protocol, as data (headless-host-bridge section 4.6).
|
|
31
|
+
*
|
|
32
|
+
* THE POINT OF THIS BLOCK IS THAT A CALLER SHOULD NOT HAVE TO PROBE. The
|
|
33
|
+
* protocol version, which decision kinds exist, which tool names are gated and
|
|
34
|
+
* the limits it will be held to are all answered here, because the alternative
|
|
35
|
+
* is "send a frame and see whether an error comes back" - discovery by damage.
|
|
36
|
+
*/
|
|
37
|
+
bridge: {
|
|
38
|
+
protocolVersion: number;
|
|
39
|
+
capabilities: string[];
|
|
40
|
+
decisionKinds: string[];
|
|
41
|
+
gatedTools: string[];
|
|
42
|
+
limits: {
|
|
43
|
+
maxCallerTools: number;
|
|
44
|
+
maxFrameBytes: number;
|
|
45
|
+
defaultDecisionTimeoutMs: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The per-request ceilings (headless-request-scope-and-typed-result section 4.6),
|
|
49
|
+
* BESIDE `limits` rather than inside it: `limits` is what one FRAME may carry,
|
|
50
|
+
* these bound one REQUEST. Both read their own `*_LIMITS` constant, so neither
|
|
51
|
+
* restates a number the enforcement does not use.
|
|
52
|
+
*/
|
|
53
|
+
requestLimits: {
|
|
54
|
+
maxRequestTurns: number;
|
|
55
|
+
maxRequestTimeoutMs: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The thread ceilings (headless-threads-and-lifecycle-hooks section 4.6).
|
|
59
|
+
*
|
|
60
|
+
* A SIBLING OF `limits` / `requestLimits` RATHER THAN A MEMBER, because the three answer
|
|
61
|
+
* three different questions: what one frame carries, what one request may spend, and how
|
|
62
|
+
* many conversations this process can host.
|
|
63
|
+
*/
|
|
64
|
+
threadLimits: {
|
|
65
|
+
maxOpenThreads: number;
|
|
66
|
+
defaultMaxOpenThreads: number;
|
|
67
|
+
defaultConcurrency: number;
|
|
68
|
+
maxThreadIdLength: number;
|
|
69
|
+
};
|
|
70
|
+
/** The hook ceilings and the four points, so a wrapper need not probe for them. */
|
|
71
|
+
hookLimits: {
|
|
72
|
+
maxHooks: number;
|
|
73
|
+
maxHooksPerPoint: number;
|
|
74
|
+
defaultTimeoutMs: number;
|
|
75
|
+
minTimeoutMs: number;
|
|
76
|
+
maxTimeoutMs: number;
|
|
77
|
+
allowModeMaxTimeoutMs: number;
|
|
78
|
+
points: string[];
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The structured-result contract, as data (section 4.6).
|
|
83
|
+
*
|
|
84
|
+
* A NEW BLOCK, so an older consumer simply ignores it. It exists because the
|
|
85
|
+
* STRENGTH of the validation is machine-dependent (`ajv` is an optional dependency
|
|
86
|
+
* of Core), and "which strength am I on" is not something a caller can discover by
|
|
87
|
+
* sending a frame.
|
|
88
|
+
*/
|
|
89
|
+
result: {
|
|
90
|
+
schemaBytes: number;
|
|
91
|
+
schemaDepth: number;
|
|
92
|
+
schemaRefs: number;
|
|
93
|
+
validationNodes: number;
|
|
94
|
+
maxViolations: number;
|
|
95
|
+
valueBytes: number;
|
|
96
|
+
defaultRepairTurns: number;
|
|
97
|
+
maxRepairTurns: number;
|
|
98
|
+
ajv: boolean;
|
|
99
|
+
};
|
|
29
100
|
}
|
|
30
101
|
export declare function buildInfo(flags: CliFlags, version: string): ExecInfo;
|
package/dist/diagnostics/info.js
CHANGED
|
@@ -15,8 +15,15 @@ import { readFileSync } from 'node:fs';
|
|
|
15
15
|
import process from 'node:process';
|
|
16
16
|
import { getConfigPath, getSessionsDir, getUserDataDir } from '../config/app-paths.js';
|
|
17
17
|
import { loadConfig, makeGetApiKey } from '../config/load.js';
|
|
18
|
-
import { HOST_TOOL_NAMES } from '../tools/index.js';
|
|
19
|
-
import {
|
|
18
|
+
import { CALLER_GATED_TOOLS, HOST_TOOL_NAMES } from '../tools/index.js';
|
|
19
|
+
import { BRIDGE_CAPABILITIES, BRIDGE_PROTOCOL_VERSION } from '../exec/bridge/protocol.js';
|
|
20
|
+
import { BRIDGE_LIMITS } from '../exec/bridge/limits.js';
|
|
21
|
+
import { EXEC_SCHEMA_VERSION, execCapabilities } from '../exec/events.js';
|
|
22
|
+
import { RESULT_LIMITS } from '../exec/result/limits.js';
|
|
23
|
+
import { REQUEST_LIMITS } from '../exec/request/limits.js';
|
|
24
|
+
import { THREAD_LIMITS } from '../exec/threads/limits.js';
|
|
25
|
+
import { HOOK_LIMITS, HOOK_POINTS } from '../exec/hooks/limits.js';
|
|
26
|
+
import { hasAjvValidator } from './doctor.js';
|
|
20
27
|
/**
|
|
21
28
|
* Everything this build can do, as names a wrapper can test for.
|
|
22
29
|
*
|
|
@@ -52,11 +59,90 @@ export function buildInfo(flags, version) {
|
|
|
52
59
|
// `HOST_TOOL_NAMES` means and exactly what `--allow-tool` validates against.
|
|
53
60
|
tools: [...HOST_TOOL_NAMES],
|
|
54
61
|
outputFormats: ['text', 'json', 'stream-json'],
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
// THESE TWO LINES ARE THE ANSWER TO "WHAT DOES THIS BUILD ACCEPT", and this is
|
|
63
|
+
// the only place that answer exists in machine-readable form. Leaving them at
|
|
64
|
+
// three and three while `system/init` announces `bridge` and a run accepts
|
|
65
|
+
// `--permission-mode ask` would be two machine-readable answers that contradict
|
|
66
|
+
// each other, with nothing to tell a caller which one is true.
|
|
67
|
+
inputFormats: ['text', 'stream-json', 'bridge'],
|
|
68
|
+
permissionModes: ['auto', 'plan', 'strict', 'ask'],
|
|
69
|
+
features: buildFeatures(),
|
|
70
|
+
bridge: {
|
|
71
|
+
protocolVersion: BRIDGE_PROTOCOL_VERSION,
|
|
72
|
+
capabilities: [...BRIDGE_CAPABILITIES],
|
|
73
|
+
decisionKinds: ['tool', 'questions', 'plan', 'skill_install'],
|
|
74
|
+
// FROM THE CONSTANT THE GATE USES, so a caller never has to read the docs to
|
|
75
|
+
// learn that `skill_create` also asks it - and so this list cannot drift from
|
|
76
|
+
// the one enforcement is built on.
|
|
77
|
+
gatedTools: [...CALLER_GATED_TOOLS],
|
|
78
|
+
limits: {
|
|
79
|
+
maxCallerTools: BRIDGE_LIMITS.maxCallerTools,
|
|
80
|
+
maxFrameBytes: BRIDGE_LIMITS.maxFrameBytes,
|
|
81
|
+
defaultDecisionTimeoutMs: BRIDGE_LIMITS.defaultDecisionTimeoutMs,
|
|
82
|
+
},
|
|
83
|
+
requestLimits: {
|
|
84
|
+
maxRequestTurns: REQUEST_LIMITS.maxRequestTurns,
|
|
85
|
+
maxRequestTimeoutMs: REQUEST_LIMITS.maxRequestTimeoutMs,
|
|
86
|
+
},
|
|
87
|
+
// THREADS AND HOOKS (headless-threads-and-lifecycle-hooks section 4.6). REFERENCED
|
|
88
|
+
// from `THREAD_LIMITS` / `HOOK_LIMITS`, never restated: this block and the runtime's
|
|
89
|
+
// clamps have to be the same numbers, and a second literal is how they stop being.
|
|
90
|
+
threadLimits: {
|
|
91
|
+
maxOpenThreads: THREAD_LIMITS.maxOpenThreads,
|
|
92
|
+
defaultMaxOpenThreads: THREAD_LIMITS.defaultMaxOpenThreads,
|
|
93
|
+
defaultConcurrency: THREAD_LIMITS.defaultConcurrency,
|
|
94
|
+
maxThreadIdLength: THREAD_LIMITS.maxThreadIdLength,
|
|
95
|
+
},
|
|
96
|
+
hookLimits: {
|
|
97
|
+
maxHooks: HOOK_LIMITS.maxHooks,
|
|
98
|
+
maxHooksPerPoint: HOOK_LIMITS.maxHooksPerPoint,
|
|
99
|
+
defaultTimeoutMs: HOOK_LIMITS.defaultTimeoutMs,
|
|
100
|
+
minTimeoutMs: HOOK_LIMITS.minTimeoutMs,
|
|
101
|
+
maxTimeoutMs: HOOK_LIMITS.maxTimeoutMs,
|
|
102
|
+
allowModeMaxTimeoutMs: HOOK_LIMITS.allowModeMaxTimeoutMs,
|
|
103
|
+
points: [...HOOK_POINTS],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
result: {
|
|
107
|
+
schemaBytes: RESULT_LIMITS.maxSchemaBytes,
|
|
108
|
+
schemaDepth: RESULT_LIMITS.maxSchemaDepth,
|
|
109
|
+
schemaRefs: RESULT_LIMITS.maxSchemaRefs,
|
|
110
|
+
validationNodes: RESULT_LIMITS.maxValidationNodes,
|
|
111
|
+
maxViolations: RESULT_LIMITS.maxViolations,
|
|
112
|
+
valueBytes: RESULT_LIMITS.maxResultValueBytes,
|
|
113
|
+
defaultRepairTurns: RESULT_LIMITS.defaultRepairTurns,
|
|
114
|
+
maxRepairTurns: RESULT_LIMITS.maxRepairTurns,
|
|
115
|
+
ajv: hasAjvValidator(),
|
|
116
|
+
},
|
|
58
117
|
};
|
|
59
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* `features` = this build's own names PLUS the capability strings of one
|
|
121
|
+
* fully-featured run.
|
|
122
|
+
*
|
|
123
|
+
* DERIVED FROM `execCapabilities()`, NOT WRITTEN OUT BY HAND (review P0-4). The whole
|
|
124
|
+
* point of `info` is that a caller can read it BEFORE deciding which frames to send;
|
|
125
|
+
* if this list and `system/init.capabilities` were two hand-maintained literals, the
|
|
126
|
+
* first one to gain a member would silently make that strategy wrong.
|
|
127
|
+
* `{ bridge: true, structuredResult: true }` is the right argument here because this
|
|
128
|
+
* command answers "what can this build do", not "what did one run do".
|
|
129
|
+
*/
|
|
130
|
+
function buildFeatures() {
|
|
131
|
+
const features = [...FEATURES];
|
|
132
|
+
for (const capability of execCapabilities({
|
|
133
|
+
bridge: true,
|
|
134
|
+
structuredResult: true,
|
|
135
|
+
// THE SUPERSET ARGUMENT AGAIN: `info` answers "what can this build do", so the two new
|
|
136
|
+
// capabilities are declared here and only HERE - a run that does not open `--threads`
|
|
137
|
+
// still reports them in its own `init` as absent, from the same derivation point.
|
|
138
|
+
threads: true,
|
|
139
|
+
hooks: true,
|
|
140
|
+
})) {
|
|
141
|
+
if (!features.includes(capability))
|
|
142
|
+
features.push(capability);
|
|
143
|
+
}
|
|
144
|
+
return features;
|
|
145
|
+
}
|
|
60
146
|
/**
|
|
61
147
|
* The installed `@aragon-agent/core` version, or `'unknown'`.
|
|
62
148
|
*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-served tools: what a `tool_register` frame becomes, and how it runs.
|
|
3
|
+
*
|
|
4
|
+
* ASCII ONLY - `src/exec/**` is inside the glyph scanner's scope.
|
|
5
|
+
*
|
|
6
|
+
* ## I-12: THESE TOOLS LIVE OUTSIDE THE HOST'S JURISDICTION, ON PURPOSE
|
|
7
|
+
*
|
|
8
|
+
* Everything below is built HERE and handed straight to the registry. That means a
|
|
9
|
+
* caller tool is NOT covered by the plan gate, NOT covered by a skill's
|
|
10
|
+
* `allowed-tools` ceiling, and has no job-notice tail - because it never passes
|
|
11
|
+
* through `createBuiltinTools`, which is where all three wrappers are applied.
|
|
12
|
+
*
|
|
13
|
+
* This is a decision, not an oversight, and it is written down because the next
|
|
14
|
+
* person to read this file will otherwise assume the wrappers are there. The
|
|
15
|
+
* defences that DO apply are: plan mode refuses the whole `tool_register` frame
|
|
16
|
+
* (`plan_mode_read_only`), and a caller tool may not shadow any host, team or
|
|
17
|
+
* skill tool name (`caller_tool_conflict`). Both are refusals at registration
|
|
18
|
+
* time, which is the only moment this module can still see the caller's intent.
|
|
19
|
+
*
|
|
20
|
+
* ## Why `validateCallerTools` REFUSES WHOLE FRAMES
|
|
21
|
+
*
|
|
22
|
+
* A partially accepted registration would leave the registry holding a mixture of
|
|
23
|
+
* two declarations - the caller's old `db_query` and its new `db_write` - with
|
|
24
|
+
* nothing anywhere saying which of the two the model is now using. The caller's
|
|
25
|
+
* natural recovery ("just send the fixed list again") only works if the previous
|
|
26
|
+
* frame left no trace, so every check below runs BEFORE the first write.
|
|
27
|
+
*/
|
|
28
|
+
import { type AgentTool, type ToolResult } from '@aragon-agent/core';
|
|
29
|
+
import type { BridgeServer } from './server.js';
|
|
30
|
+
import type { CallerToolSpec } from './protocol.js';
|
|
31
|
+
export interface CallerToolValidation {
|
|
32
|
+
ok: true;
|
|
33
|
+
specs: CallerToolSpec[];
|
|
34
|
+
/** Non-fatal notes for `ack.warnings` - a clipped description, so far. */
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface CallerToolRejection {
|
|
38
|
+
ok: false;
|
|
39
|
+
code: string;
|
|
40
|
+
message: string;
|
|
41
|
+
}
|
|
42
|
+
export type CallerToolValidationResult = CallerToolValidation | CallerToolRejection;
|
|
43
|
+
/**
|
|
44
|
+
* Narrow whatever arrived in `tool_register.tools` into usable specs.
|
|
45
|
+
*
|
|
46
|
+
* `reserved` is a PARAMETER rather than an import so the caller decides what a
|
|
47
|
+
* name may not collide with - and so the test can hand over a two-name set instead
|
|
48
|
+
* of modelling the whole host.
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateCallerTools(rawTools: readonly unknown[], reserved: ReadonlySet<string>): CallerToolValidationResult;
|
|
51
|
+
/**
|
|
52
|
+
* The effective ceiling for one call: `min(spec.timeoutMs, --caller-tool-timeout)`.
|
|
53
|
+
*
|
|
54
|
+
* MIN, NOT MAX, because both numbers are ceilings on the same thing: the caller's
|
|
55
|
+
* own declaration is a promise about its implementation ("this query takes at most
|
|
56
|
+
* 20 s") and the flag is the run's budget. Taking the larger of the two would let
|
|
57
|
+
* a declared 600 s override a 10 s budget.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveCallerToolTimeout(spec: CallerToolSpec | undefined, defaultTimeoutMs: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Turn specs into `AgentTool`s that round-trip through the server.
|
|
62
|
+
*
|
|
63
|
+
* EVERY PATH RETURNS A `ToolResult`. Nothing here throws: `ToolExecutor` would
|
|
64
|
+
* convert a throw into an error result of its own wording, and that wording says
|
|
65
|
+
* nothing about which of the four refusals happened.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createCallerTools(specs: readonly CallerToolSpec[], server: BridgeServer, defaultTimeoutMs: number): AgentTool[];
|
|
68
|
+
/**
|
|
69
|
+
* Coerce a `tool_result.content` into text, or refuse it.
|
|
70
|
+
*
|
|
71
|
+
* TEXT ONLY, THIS ROUND. An image part would need `imageResult` and the provider's
|
|
72
|
+
* multimodal path, which is a separate feature with its own budget questions; a
|
|
73
|
+
* half-support that silently dropped the image would be worse than a named
|
|
74
|
+
* refusal (`bridge_unsupported_content`).
|
|
75
|
+
*/
|
|
76
|
+
export declare function contentToText(content: unknown): {
|
|
77
|
+
ok: true;
|
|
78
|
+
text: string;
|
|
79
|
+
} | {
|
|
80
|
+
ok: false;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Clip to `maxCallerToolResultBytes` and SAY SO.
|
|
84
|
+
*
|
|
85
|
+
* The footer is not decoration: `ToolExecutor` already clips model-visible output
|
|
86
|
+
* at its own ceiling, so a silent second clip here would look like the tool
|
|
87
|
+
* returned less than it did, and the model would draw conclusions from a truncated
|
|
88
|
+
* table without being told it was truncated.
|
|
89
|
+
*/
|
|
90
|
+
export declare function clipCallerToolText(text: string): string;
|
|
91
|
+
/** Build the result a `tool_result` frame becomes, or a named refusal. */
|
|
92
|
+
export declare function callerToolResult(content: unknown, isError: boolean): ToolResult;
|
|
93
|
+
/** Thrown only so `server.ts` can map it to one error code in one place. */
|
|
94
|
+
export declare class UnsupportedContentError extends Error {
|
|
95
|
+
constructor();
|
|
96
|
+
}
|