@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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask the child's output streams whether anything is still arriving (W1,
|
|
3
|
+
* defence 1).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS AT ALL, AND THE RELEASE-GATE FAILURE THAT PROVED IT.
|
|
6
|
+
*
|
|
7
|
+
* `bash` settles on `'close'`, which fires only once every stdio stream is
|
|
8
|
+
* closed - and a detached grandchild that inherited the pipe keeps it open after
|
|
9
|
+
* the shell is long gone, so the promise had a path that never settled. The
|
|
10
|
+
* drain window (`PROC_LIMITS.drainMs`) closes that: a quarter of a second after
|
|
11
|
+
* `'exit'`, settle anyway and stamp
|
|
12
|
+
*
|
|
13
|
+
* [note] the command exited but a background child is still holding its
|
|
14
|
+
* output stream.
|
|
15
|
+
*
|
|
16
|
+
* The first build decided that sentence by TIMER EXPIRY - whatever had not
|
|
17
|
+
* settled when the window closed was called a held pipe. That is a claim about
|
|
18
|
+
* STATE inferred from a clock, and under load the two disagree. The release gate
|
|
19
|
+
* caught it on `tool-output-store.test.ts` AC-30 ("`bash` returns the same bytes
|
|
20
|
+
* with and without a recorder"): the second call of `node -e
|
|
21
|
+
* "process.stdout.write(...)"` returned the note for a child that had printed
|
|
22
|
+
* two lines and exited immediately. The pipe was closed microseconds after
|
|
23
|
+
* `'exit'`; the event loop was busy enough (four workers, 400 files, ~200 s of
|
|
24
|
+
* transforms behind it) that the timers phase came round before libuv delivered
|
|
25
|
+
* the close notification, so a 250 ms timer won a race it was never meant to
|
|
26
|
+
* win - and the model was told a background child was holding its output stream
|
|
27
|
+
* when nothing was.
|
|
28
|
+
*
|
|
29
|
+
* Hence: READ THE STREAMS, DO NOT GUESS FROM THE CLOCK. A `Readable` has ended
|
|
30
|
+
* once it has emitted `'end'` (`readableEnded`) or been destroyed. While either
|
|
31
|
+
* child descriptor is still open the sentence above is literally true; the
|
|
32
|
+
* moment both have ended it is false no matter what a timer says. Callers use
|
|
33
|
+
* this to decide the WORDING (note or not) and, together with the `'end'`
|
|
34
|
+
* events, to settle the ordinary path without waiting for `'close'` at all.
|
|
35
|
+
*
|
|
36
|
+
* ASCII ONLY: `src/proc/**` is inside the glyph scanner's scope.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Whether one child descriptor can still deliver bytes.
|
|
40
|
+
*
|
|
41
|
+
* A stream that has been destroyed is finished even when `readableEnded` is
|
|
42
|
+
* still false - that is the state a killed child's pipes end up in.
|
|
43
|
+
*/
|
|
44
|
+
export function isOutputStreamOpen(stream) {
|
|
45
|
+
if (!stream)
|
|
46
|
+
return false;
|
|
47
|
+
if (stream.destroyed)
|
|
48
|
+
return false;
|
|
49
|
+
return stream.readableEnded !== true;
|
|
50
|
+
}
|
|
51
|
+
/** Whether EITHER child descriptor can still deliver bytes. */
|
|
52
|
+
export function hasOpenOutputStreams(streams) {
|
|
53
|
+
return isOutputStreamOpen(streams.stdout) || isOutputStreamOpen(streams.stderr);
|
|
54
|
+
}
|
package/dist/skills/service.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { SkillRegistry, type SkillFileRef, type SkillHost, type SkillPlatform, t
|
|
|
9
9
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
10
10
|
import type { SkillsConfig, SkillsRuntimeOptions, SkillsToolPolicyMode } from '../config/schema.js';
|
|
11
11
|
import { type ConfirmRequest } from '../tools/index.js';
|
|
12
|
+
import { type SkillRoutingReport } from './triage.js';
|
|
12
13
|
/**
|
|
13
14
|
* Capability-probing approval gate (D17 / §8.3.1).
|
|
14
15
|
*
|
|
@@ -229,6 +230,46 @@ export declare class SkillService {
|
|
|
229
230
|
/** Warn once about `--skill <name>` values that match nothing (§7.5). */
|
|
230
231
|
reportUnknownForcedSkills(): void;
|
|
231
232
|
setDisabled(name: string, disabled: boolean): void;
|
|
233
|
+
/**
|
|
234
|
+
* Route one task against the installed skills — PURE COMPUTATION (I-T2).
|
|
235
|
+
*
|
|
236
|
+
* Reads in-memory records only: no file, no body, no network, no model call. The
|
|
237
|
+
* whole "which skills could apply here" question therefore costs zero context
|
|
238
|
+
* bytes, which is what makes the disclosure ladder honest — Level 2 is paid for
|
|
239
|
+
* only when the model actually opens a skill.
|
|
240
|
+
*
|
|
241
|
+
* Returns `null` when triage is switched off, so every caller has one shape to
|
|
242
|
+
* check rather than an empty report that looks like "no match".
|
|
243
|
+
*
|
|
244
|
+
* Writes NO usage counter. An automatic proposal is not a user's choice, which
|
|
245
|
+
* is the same reason `loadBody({ countUse: false })` exists for always-on
|
|
246
|
+
* skills: counting it would pin an auto-suggested skill to the top of the
|
|
247
|
+
* ranking forever and destroy the signal the ranking is there to carry.
|
|
248
|
+
*/
|
|
249
|
+
triage(task: string): SkillRoutingReport | null;
|
|
250
|
+
/**
|
|
251
|
+
* `/skills route <text>` — the same computation, WITHOUT the on/off gate.
|
|
252
|
+
*
|
|
253
|
+
* The gate exists to keep the block out of the prompt; it must not make the
|
|
254
|
+
* explanation unreachable, because "why did nothing match" is the question a user
|
|
255
|
+
* asks precisely when they suspect the switch is off.
|
|
256
|
+
*/
|
|
257
|
+
route(task: string): SkillRoutingReport;
|
|
258
|
+
/**
|
|
259
|
+
* `/skills triage on|off` — persist the switch AND rebuild the prompt now.
|
|
260
|
+
*
|
|
261
|
+
* `updateConfig` deliberately only replaces this object's config and calls
|
|
262
|
+
* `persist`; it does NOT call `deps.onChange`. Every other mutator supplies that
|
|
263
|
+
* call itself (`setDisabled` above, the directory-trust path below), and `onChange`
|
|
264
|
+
* is the sole route to `controller.refreshSkills()` → `rebuildSystemPrompt()`.
|
|
265
|
+
* Without it the setting would be written to disk and the running session would
|
|
266
|
+
* keep the old prompt until the next launch — which reads as a broken switch.
|
|
267
|
+
*
|
|
268
|
+
* The early return is not an optimization: `updateConfig` persists, so flipping a
|
|
269
|
+
* switch to the value it already has would produce a pointless disk write and a
|
|
270
|
+
* pointless prompt rebuild on every keystroke.
|
|
271
|
+
*/
|
|
272
|
+
setTriageEnabled(enabled: boolean): void;
|
|
232
273
|
getConfig(): SkillsConfig;
|
|
233
274
|
/** Local use counters backing `--sort=recent`; empty when tracking is off. */
|
|
234
275
|
getUsage(): SkillUsageMap;
|
package/dist/skills/service.js
CHANGED
|
@@ -14,6 +14,7 @@ import { currentPlatform } from './node-host.js';
|
|
|
14
14
|
import { MANIFEST_FILENAME, readManifest, sha256Buffer, sha256File } from './manifest.js';
|
|
15
15
|
import { isDirectory, normalizeTrustPath, resolveSkillRoots, getStagingDir, } from './paths.js';
|
|
16
16
|
import { USAGE_MAX_ENTRIES, loadUsage, pruneUsage, recordUse } from './usage.js';
|
|
17
|
+
import { buildRoutingReport } from './triage.js';
|
|
17
18
|
const ENTRY_FILE = 'SKILL.md';
|
|
18
19
|
/**
|
|
19
20
|
* Re-exported from `manifest.ts` rather than spelled out again. The two copies
|
|
@@ -615,6 +616,67 @@ export class SkillService {
|
|
|
615
616
|
this.updateConfig({ disabled: [...next] });
|
|
616
617
|
this.deps.onChange?.();
|
|
617
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Route one task against the installed skills — PURE COMPUTATION (I-T2).
|
|
621
|
+
*
|
|
622
|
+
* Reads in-memory records only: no file, no body, no network, no model call. The
|
|
623
|
+
* whole "which skills could apply here" question therefore costs zero context
|
|
624
|
+
* bytes, which is what makes the disclosure ladder honest — Level 2 is paid for
|
|
625
|
+
* only when the model actually opens a skill.
|
|
626
|
+
*
|
|
627
|
+
* Returns `null` when triage is switched off, so every caller has one shape to
|
|
628
|
+
* check rather than an empty report that looks like "no match".
|
|
629
|
+
*
|
|
630
|
+
* Writes NO usage counter. An automatic proposal is not a user's choice, which
|
|
631
|
+
* is the same reason `loadBody({ countUse: false })` exists for always-on
|
|
632
|
+
* skills: counting it would pin an auto-suggested skill to the top of the
|
|
633
|
+
* ranking forever and destroy the signal the ranking is there to carry.
|
|
634
|
+
*/
|
|
635
|
+
triage(task) {
|
|
636
|
+
if (!this.config.enabled || !this.config.triage)
|
|
637
|
+
return null;
|
|
638
|
+
return buildRoutingReport({
|
|
639
|
+
task,
|
|
640
|
+
records: this.registry.list(),
|
|
641
|
+
activeNames: this.registry.activeNames,
|
|
642
|
+
now: Date.now(),
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* `/skills route <text>` — the same computation, WITHOUT the on/off gate.
|
|
647
|
+
*
|
|
648
|
+
* The gate exists to keep the block out of the prompt; it must not make the
|
|
649
|
+
* explanation unreachable, because "why did nothing match" is the question a user
|
|
650
|
+
* asks precisely when they suspect the switch is off.
|
|
651
|
+
*/
|
|
652
|
+
route(task) {
|
|
653
|
+
return buildRoutingReport({
|
|
654
|
+
task,
|
|
655
|
+
records: this.registry.list(),
|
|
656
|
+
activeNames: this.registry.activeNames,
|
|
657
|
+
now: Date.now(),
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* `/skills triage on|off` — persist the switch AND rebuild the prompt now.
|
|
662
|
+
*
|
|
663
|
+
* `updateConfig` deliberately only replaces this object's config and calls
|
|
664
|
+
* `persist`; it does NOT call `deps.onChange`. Every other mutator supplies that
|
|
665
|
+
* call itself (`setDisabled` above, the directory-trust path below), and `onChange`
|
|
666
|
+
* is the sole route to `controller.refreshSkills()` → `rebuildSystemPrompt()`.
|
|
667
|
+
* Without it the setting would be written to disk and the running session would
|
|
668
|
+
* keep the old prompt until the next launch — which reads as a broken switch.
|
|
669
|
+
*
|
|
670
|
+
* The early return is not an optimization: `updateConfig` persists, so flipping a
|
|
671
|
+
* switch to the value it already has would produce a pointless disk write and a
|
|
672
|
+
* pointless prompt rebuild on every keystroke.
|
|
673
|
+
*/
|
|
674
|
+
setTriageEnabled(enabled) {
|
|
675
|
+
if (this.config.triage === enabled)
|
|
676
|
+
return;
|
|
677
|
+
this.updateConfig({ triage: enabled });
|
|
678
|
+
this.deps.onChange?.();
|
|
679
|
+
}
|
|
618
680
|
getConfig() {
|
|
619
681
|
return this.config;
|
|
620
682
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic skill routing for the pre-work triage step
|
|
3
|
+
* (skill-triage-and-unbounded-ask §4.2.2).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS IS NOT AN LLM CALL (D-11). The question "which installed skill, if any,
|
|
6
|
+
* covers this task?" is answered against data the process already holds: the
|
|
7
|
+
* `SkillRecord` set in `SkillRegistry`, with a name, a one-line description and a
|
|
8
|
+
* keyword list. A model call would add a round trip, latency and — the decisive
|
|
9
|
+
* point — untestability, because a router that is itself a model can only be
|
|
10
|
+
* asserted on statistically. This module is a pure function of its inputs, so the
|
|
11
|
+
* whole feature has unit tests instead of anecdotes.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT IS ALLOWED TO TOUCH. Records only. No filesystem, no `SKILL.md` read,
|
|
14
|
+
* no network, no clock (invariant I-T2, asserted by a source-scanning test). That
|
|
15
|
+
* is what makes the disclosure ladder real: "which skills exist" costs zero
|
|
16
|
+
* context bytes and zero IO, and the expensive Level 2 body is paid for only when
|
|
17
|
+
* the model actually calls `skill(name)`.
|
|
18
|
+
*
|
|
19
|
+
* BOTH SCRIPTS ARE HANDLED. The user writes tasks in Chinese and English, and
|
|
20
|
+
* skill descriptions are usually English. Space-separated tokens alone score zero
|
|
21
|
+
* on CJK text, so a run of Han/Kana/Hangul characters is tokenized into bigrams
|
|
22
|
+
* (plus the whole run when it is short enough for a phrase match to be likely).
|
|
23
|
+
*
|
|
24
|
+
* ASCII ONLY IN LITERALS: `skills/` is outside the glyph scanner's scope today,
|
|
25
|
+
* but new text keeps the same rule rather than assuming a scanner will never
|
|
26
|
+
* cover it.
|
|
27
|
+
*/
|
|
28
|
+
import type { SkillRecord, SkillScope } from '@aragon-agent/core';
|
|
29
|
+
export interface SkillTriageLimits {
|
|
30
|
+
/** Candidates presented per turn. R-1: more than three is a reading list. */
|
|
31
|
+
maxCandidates: number;
|
|
32
|
+
/** One keyword hit is enough to be worth showing. */
|
|
33
|
+
minScore: number;
|
|
34
|
+
/** The score at which "you were told and did not load it" is worth a notice. */
|
|
35
|
+
confidentScore: number;
|
|
36
|
+
/**
|
|
37
|
+
* Byte ceiling for `<skill_candidates>`. 900, NOT 700: the fixed overhead is
|
|
38
|
+
* about 114 B, so 700 leaves ~586 B for three lines that cost up to 249 B each.
|
|
39
|
+
* A ceiling that silently drops the third candidate on ordinary input is not a
|
|
40
|
+
* ceiling, it is a slow leak — see the arithmetic table in `triage.test.ts`.
|
|
41
|
+
*/
|
|
42
|
+
maxBlockBytes: number;
|
|
43
|
+
/** Byte ceiling for the `<skill_triage>` protocol block, asserted elsewhere. */
|
|
44
|
+
maxProtocolBytes: number;
|
|
45
|
+
/** Task text is truncated to this before tokenizing. */
|
|
46
|
+
maxTaskChars: number;
|
|
47
|
+
/** Tokens kept after tokenizing; bounds the scoring loop on a long task. */
|
|
48
|
+
maxTokens: number;
|
|
49
|
+
/** Latin tokens shorter than this are noise ("a", "of", "to"). */
|
|
50
|
+
latinTokenMinChars: number;
|
|
51
|
+
/** CJK runs shorter than this are noise; a single ideograph means nothing. */
|
|
52
|
+
cjkMinRunChars: number;
|
|
53
|
+
/** Bigrams taken per run. A 2,000-char run cannot be allowed to flood the set. */
|
|
54
|
+
maxCjkGramsPerRun: number;
|
|
55
|
+
/**
|
|
56
|
+
* Longest CJK run that ALSO contributes a whole-run token.
|
|
57
|
+
*
|
|
58
|
+
* Above this the whole-run token is dropped on purpose: a 2,000-character
|
|
59
|
+
* "token" cannot be a substring of any haystack, so it can only occupy one of
|
|
60
|
+
* `maxTokens` slots that a real token needed.
|
|
61
|
+
*/
|
|
62
|
+
maxWholeRunChars: number;
|
|
63
|
+
/** Skill names are unbounded upstream (a kebab directory can be long). */
|
|
64
|
+
maxNameChars: number;
|
|
65
|
+
/** One line of description per candidate. */
|
|
66
|
+
descLineChars: number;
|
|
67
|
+
/** Each match-reason tag. */
|
|
68
|
+
reasonChars: number;
|
|
69
|
+
}
|
|
70
|
+
export declare const SKILL_TRIAGE_LIMITS: SkillTriageLimits;
|
|
71
|
+
export type MatchReason = 'name' | 'keyword' | 'description';
|
|
72
|
+
export interface SkillScore {
|
|
73
|
+
score: number;
|
|
74
|
+
reasons: MatchReason[];
|
|
75
|
+
mentioned: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface SkillCandidate {
|
|
78
|
+
name: string;
|
|
79
|
+
scope: SkillScope;
|
|
80
|
+
/** Already truncated to `descLineChars`; the renderer does not re-cut it. */
|
|
81
|
+
description: string;
|
|
82
|
+
/** 0..44 — see `TRIAGE_WEIGHTS` / `TRIAGE_CAPS`. */
|
|
83
|
+
score: number;
|
|
84
|
+
/** At most three, in the order they were first hit. */
|
|
85
|
+
reasons: MatchReason[];
|
|
86
|
+
}
|
|
87
|
+
export interface SkillRoutingReport {
|
|
88
|
+
/** The task text AFTER truncation to `maxTaskChars`. */
|
|
89
|
+
task: string;
|
|
90
|
+
/** How many tokens the task produced — the explanation for a miss. */
|
|
91
|
+
tokenCount: number;
|
|
92
|
+
/** How many records were eligible to be scored. */
|
|
93
|
+
eligible: number;
|
|
94
|
+
candidates: SkillCandidate[];
|
|
95
|
+
/**
|
|
96
|
+
* The best-scoring skill that did NOT make the list, or `null` when nothing
|
|
97
|
+
* scored at all.
|
|
98
|
+
*
|
|
99
|
+
* It exists for exactly one caller: `/skills route`'s "no match" output. "No
|
|
100
|
+
* skill matched" on its own is a dead end, and the threshold is the one number a
|
|
101
|
+
* user may need to argue with. Naming the runner-up and its score is what turns
|
|
102
|
+
* that dead end into "the threshold is slightly too high" or "nothing is close".
|
|
103
|
+
*/
|
|
104
|
+
topUnqualified: {
|
|
105
|
+
name: string;
|
|
106
|
+
score: number;
|
|
107
|
+
} | null;
|
|
108
|
+
decision: 'none' | 'suggest' | 'confident';
|
|
109
|
+
/**
|
|
110
|
+
* Timestamp, for display and logging only. It never participates in ordering or
|
|
111
|
+
* scoring, which is what keeps this module a pure function (I-T2).
|
|
112
|
+
*/
|
|
113
|
+
at: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Task text to a token set, Latin words and CJK bigrams mixed together.
|
|
117
|
+
*
|
|
118
|
+
* DETERMINISTIC BY CONSTRUCTION: no random sampling, no clock, no iteration over a
|
|
119
|
+
* `Set` whose insertion order depends on anything but the input. AC-T3 asserts
|
|
120
|
+
* that two calls on the same input are `deepEqual`.
|
|
121
|
+
*/
|
|
122
|
+
export declare function tokenizeTask(text: string, limits?: SkillTriageLimits): string[];
|
|
123
|
+
/**
|
|
124
|
+
* Score one record against the task (44 is the ceiling: 12 + 5*2 + 4*4 + 2*3).
|
|
125
|
+
*
|
|
126
|
+
* THE THREE HAYSTACKS ARE SEPARATE on purpose. A token found in `keywords` is a
|
|
127
|
+
* deliberate author signal and scores 4; the same token found only in the prose
|
|
128
|
+
* description scores 2. Merging them into one string would make every keyword hit
|
|
129
|
+
* look like a description hit as well, inflating the score on exactly the records
|
|
130
|
+
* whose authors were least specific.
|
|
131
|
+
*
|
|
132
|
+
* `record.frontmatter.keywords`, never `record.keywords`: `SkillRecord` has no
|
|
133
|
+
* top-level keyword field, and both keywords and `activation` hang off
|
|
134
|
+
* `frontmatter`.
|
|
135
|
+
*/
|
|
136
|
+
export declare function scoreSkill(record: SkillRecord, tokens: string[], taskText: string): SkillScore;
|
|
137
|
+
export interface SelectSkillCandidatesInput {
|
|
138
|
+
task: string;
|
|
139
|
+
records: SkillRecord[];
|
|
140
|
+
/** Skills already loaded this turn — never proposed again (I-T4). */
|
|
141
|
+
activeNames: readonly string[];
|
|
142
|
+
limits?: Partial<SkillTriageLimits>;
|
|
143
|
+
/**
|
|
144
|
+
* Reference timestamp for the report's `at` field. Absent means `0`, so the
|
|
145
|
+
* function has no clock of its own (I-T2).
|
|
146
|
+
*/
|
|
147
|
+
now?: number;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The candidate list alone — `buildRoutingReport` minus the report.
|
|
151
|
+
*
|
|
152
|
+
* ONE PIPELINE, TWO VIEWS. This used to repeat the tokenize / rank / filter / slice
|
|
153
|
+
* sequence, which meant the candidate order and the report's `topUnqualified` were
|
|
154
|
+
* produced by two pieces of code that had to be kept in agreement by hand: exactly
|
|
155
|
+
* the drift `rankEligible`'s single comparator exists to prevent. The report owns
|
|
156
|
+
* the arithmetic; this is the projection of it for a caller who does not want
|
|
157
|
+
* `at` / `eligible` / `topUnqualified`.
|
|
158
|
+
*/
|
|
159
|
+
export declare function selectSkillCandidates(input: SelectSkillCandidatesInput): SkillCandidate[];
|
|
160
|
+
/**
|
|
161
|
+
* The router's whole decision, as one pure call — and the ONE place the arithmetic
|
|
162
|
+
* lives (`selectSkillCandidates` is a projection of it).
|
|
163
|
+
*
|
|
164
|
+
* THE FILTER IS THE SAME ONE `catalogRecords` AND `skill_find` USE (I-T4): not
|
|
165
|
+
* disabled, not invalid, not `activation: manual`, and not already active. It is
|
|
166
|
+
* re-implemented here rather than imported because the report needs the
|
|
167
|
+
* candidates' scores, and `catalogRecords` returns records. `triage.test.ts` pins
|
|
168
|
+
* this filter against `catalogRecords` so the two cannot drift apart silently.
|
|
169
|
+
*
|
|
170
|
+
* ORDER IS SCOPE FIRST, then name. That is NOT the order the user sees in
|
|
171
|
+
* `<available_skills>`, where `usageTracking` re-ranks by recent use
|
|
172
|
+
* (`service.ts:catalogBlock`). Intentional: the catalog answers "what have I used
|
|
173
|
+
* lately", the candidate list answers "what should I read first".
|
|
174
|
+
*
|
|
175
|
+
* `at` is the only non-derived field and it is display-only, so a test can pass a
|
|
176
|
+
* fixed value and compare two reports with `deepEqual`.
|
|
177
|
+
*/
|
|
178
|
+
export declare function buildRoutingReport(input: SelectSkillCandidatesInput): SkillRoutingReport;
|
|
179
|
+
/**
|
|
180
|
+
* The `<skill_candidates>` block, or `''` when there is nothing to propose.
|
|
181
|
+
*
|
|
182
|
+
* `''` is load-bearing: it is what keeps `buildSystemPrompt` byte-identical to the
|
|
183
|
+
* pre-feature text when triage finds nothing, so an empty result cannot leave an
|
|
184
|
+
* empty shell behind (I-T1).
|
|
185
|
+
*
|
|
186
|
+
* BUDGET DISCIPLINE, copied from `renderSkillCatalog`: reserve the worst-case
|
|
187
|
+
* overflow line UP FRONT, add lines while they fit, and on overflow drop whole
|
|
188
|
+
* lines rather than truncating text or the closing tag. A dropped candidate is
|
|
189
|
+
* visible as `(+N more not shown)`; a truncated one reads as a corrupt skill name.
|
|
190
|
+
*/
|
|
191
|
+
export declare function renderSkillCandidates(candidates: SkillCandidate[], opts?: {
|
|
192
|
+
maxBytes?: number;
|
|
193
|
+
limits?: Partial<SkillTriageLimits>;
|
|
194
|
+
}): string;
|