@aragon-agent/cli 0.6.39 → 0.6.42
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 +109 -0
- package/README.md +126 -20
- package/dist/agent/controller.d.ts +139 -14
- package/dist/agent/controller.js +299 -40
- 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 +29 -1
- package/dist/agent/reducer.js +34 -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 +33 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +21 -5
- package/dist/commands/builtins.js +28 -185
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +30 -0
- package/dist/commands/subagent-command.js +39 -0
- package/dist/commands/team-command.d.ts +49 -0
- package/dist/commands/team-command.js +259 -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/load.js +50 -3
- package/dist/config/model-kits.d.ts +2 -1
- package/dist/config/schema.d.ts +82 -6
- package/dist/config/schema.js +107 -9
- package/dist/config/store.js +10 -4
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- 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/dispatch-gate.d.ts +86 -0
- package/dist/team/dispatch-gate.js +75 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/forced-mode.d.ts +116 -0
- package/dist/team/forced-mode.js +156 -0
- package/dist/team/forced-prompt.d.ts +48 -0
- package/dist/team/forced-prompt.js +84 -0
- package/dist/team/limits.d.ts +61 -1
- package/dist/team/limits.js +69 -1
- package/dist/team/model.d.ts +5 -5
- package/dist/team/model.js +5 -5
- 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/task-tool.js +11 -7
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/ui/App.js +130 -11
- package/dist/ui/StatusBar.d.ts +15 -0
- package/dist/ui/StatusBar.js +2 -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/layout/status-layout.d.ts +27 -0
- package/dist/ui/layout/status-layout.js +27 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -4
- package/dist/ui/overlays/SettingsScreen.d.ts +14 -0
- package/dist/ui/overlays/SettingsScreen.js +39 -5
- package/dist/ui/overlays/TeamFanoutPage.d.ts +51 -0
- package/dist/ui/overlays/TeamFanoutPage.js +90 -0
- package/dist/ui/overlays/kit-settings-state.js +3 -2
- package/dist/ui/overlays/model-profile-state.d.ts +20 -2
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +106 -0
- package/dist/ui/overlays/team-fanout-state.js +155 -0
- package/package.json +81 -81
package/dist/agent/reducer.js
CHANGED
|
@@ -1134,12 +1134,11 @@ export function viewReducer(state, action) {
|
|
|
1134
1134
|
case 'fastTier':
|
|
1135
1135
|
return { ...state, fast: action.snapshot };
|
|
1136
1136
|
// --- Context compaction (context-auto-compaction §5.5) ----------------
|
|
1137
|
-
//
|
|
1138
|
-
//
|
|
1139
|
-
//
|
|
1140
|
-
//
|
|
1141
|
-
//
|
|
1142
|
-
// fired; `/compact status` and the diagnostic log are where the attempts are.
|
|
1137
|
+
// THE TRANSCRIPT NOW CARRIES ONE CARD PER ATTEMPT (context-compaction-never-
|
|
1138
|
+
// fires C). Before this change a declined `pressure` checkpoint dispatched
|
|
1139
|
+
// NOTHING, which is what made "95 % and nothing happens" unreportable: the
|
|
1140
|
+
// feature was trying, failing and saying nothing. The flood the old rule
|
|
1141
|
+
// feared is bounded by `maxPerRun` / `stuckLimit` in `compaction/limits.ts`.
|
|
1143
1142
|
case 'compactionStart': {
|
|
1144
1143
|
const { id, seq } = nextId(state);
|
|
1145
1144
|
let entries = state.entries;
|
|
@@ -1189,6 +1188,7 @@ export function viewReducer(state, action) {
|
|
|
1189
1188
|
tokensAfter: 0,
|
|
1190
1189
|
model: action.model,
|
|
1191
1190
|
startedAt: Date.now(),
|
|
1191
|
+
phase: 'planning',
|
|
1192
1192
|
live: true,
|
|
1193
1193
|
};
|
|
1194
1194
|
return {
|
|
@@ -1217,6 +1217,16 @@ export function viewReducer(state, action) {
|
|
|
1217
1217
|
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1218
1218
|
...(record.decision ? { decision: record.decision } : {}),
|
|
1219
1219
|
...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
|
|
1220
|
+
...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
|
|
1221
|
+
...(record.retainedTurns !== undefined
|
|
1222
|
+
? { retainedTurns: record.retainedTurns } : {}),
|
|
1223
|
+
...(record.retainedMessages !== undefined
|
|
1224
|
+
? { retainedMessages: record.retainedMessages } : {}),
|
|
1225
|
+
...(record.retainedMessages !== undefined
|
|
1226
|
+
? { retainedMessages: record.retainedMessages } : {}),
|
|
1227
|
+
...(record.keepRecentTurnsUsed !== undefined
|
|
1228
|
+
? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
|
|
1229
|
+
...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
|
|
1220
1230
|
model: record.model,
|
|
1221
1231
|
durationMs: record.durationMs,
|
|
1222
1232
|
// SETTLED. `Transcript`'s boundary is monotonic, so a card that
|
|
@@ -1253,12 +1263,30 @@ export function viewReducer(state, action) {
|
|
|
1253
1263
|
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1254
1264
|
...(record.decision ? { decision: record.decision } : {}),
|
|
1255
1265
|
...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
|
|
1266
|
+
...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
|
|
1267
|
+
...(record.retainedTurns !== undefined ? { retainedTurns: record.retainedTurns } : {}),
|
|
1268
|
+
...(record.keepRecentTurnsUsed !== undefined
|
|
1269
|
+
? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
|
|
1270
|
+
...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
|
|
1256
1271
|
model: record.model,
|
|
1257
1272
|
durationMs: record.durationMs,
|
|
1258
1273
|
live: false,
|
|
1259
1274
|
};
|
|
1260
1275
|
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
1261
1276
|
}
|
|
1277
|
+
case 'compactionPhase': {
|
|
1278
|
+
// THE LIVE CARD'S STAGE, AND NOTHING ELSE. It must not open a card (a phase
|
|
1279
|
+
// can arrive for an entry that a fast settle already closed) and must not
|
|
1280
|
+
// resurrect a settled one: `live` is left untouched, so a late phase on a
|
|
1281
|
+
// settled card changes nothing the renderer shows.
|
|
1282
|
+
if (!state.compactionEntryId)
|
|
1283
|
+
return state;
|
|
1284
|
+
const phase = action.phase;
|
|
1285
|
+
return {
|
|
1286
|
+
...state,
|
|
1287
|
+
entries: mapEntry(state.entries, state.compactionEntryId, (e) => e.kind === 'compaction' && e.live ? { ...e, phase } : e),
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1262
1290
|
case 'compactionUsage':
|
|
1263
1291
|
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
|
|
1264
1292
|
// particular NOT `context`: that gauge shows the LEAD's context
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<skill_triage>` system-prompt block (skill-triage-and-unbounded-ask §4.2.3).
|
|
3
|
+
*
|
|
4
|
+
* WHY A PROTOCOL BLOCK AT ALL. The capability this feature fixes already existed:
|
|
5
|
+
* the Level 1 catalog is in every prompt, `skill(name)` loads a body, and
|
|
6
|
+
* `README.md` has claimed since the Skills feature landed that "the model decides
|
|
7
|
+
* on its own whether a task matches a skill". What was missing is any instruction
|
|
8
|
+
* that the decision has to HAPPEN, before the work rather than after it. The
|
|
9
|
+
* catalog's own opening paragraph is a STATEMENT ("if a task matches one, call the
|
|
10
|
+
* skill tool"), not a step in an order of operations, and a statement the model
|
|
11
|
+
* has read forty times is the one it skims.
|
|
12
|
+
*
|
|
13
|
+
* WHY IT IS THIS SHORT. The protocol is paid for on EVERY LLM request: the system
|
|
14
|
+
* prompt is re-sent each time and nothing in this repository uses provider-side
|
|
15
|
+
* prefix caching. An earlier draft carried a 1.9 KB version whose second half
|
|
16
|
+
* repeated the disclosure ladder that `<available_skills>` already states three
|
|
17
|
+
* lines above it. Repeating a rule is not emphasis, it is noise — and noise about
|
|
18
|
+
* a rule teaches the model that these blocks are skimmable. Measured size of the
|
|
19
|
+
* block below is 1273 bytes; `skill-triage-prompt.test.ts` asserts the ceiling of
|
|
20
|
+
* 1500 so an edit has 227 bytes of room, not unlimited scope.
|
|
21
|
+
*
|
|
22
|
+
* ASCII ONLY — `agent/` is inside the glyph scanner's scope.
|
|
23
|
+
*/
|
|
24
|
+
import type { SkillCandidate } from '../skills/triage.js';
|
|
25
|
+
/** Bump when the wording changes, so a transcript can be dated. */
|
|
26
|
+
export declare const SKILL_TRIAGE_BLOCK_VERSION = "v1-2026-10-26";
|
|
27
|
+
export interface SkillTriageBlockOptions {
|
|
28
|
+
/** This turn's candidates, best first. Empty is normal and renders no data block. */
|
|
29
|
+
candidates: SkillCandidate[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Protocol first, candidates second — in that order, always.
|
|
33
|
+
*
|
|
34
|
+
* The order is the meaning: the protocol says what to do, the data block says with
|
|
35
|
+
* what. Reversing them would put a list of suggestions above the rules for reading
|
|
36
|
+
* it, which is how a suggestion turns into an instruction.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildSkillTriageBlock(options: SkillTriageBlockOptions): string;
|
|
39
|
+
/** Byte size of the protocol half, for the budget assertion in the tests. */
|
|
40
|
+
export declare function skillTriageProtocolBytes(): number;
|
|
41
|
+
/** The ceiling the protocol half must stay under, in bytes. */
|
|
42
|
+
export declare const SKILL_TRIAGE_PROTOCOL_MAX_BYTES: number;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<skill_triage>` system-prompt block (skill-triage-and-unbounded-ask §4.2.3).
|
|
3
|
+
*
|
|
4
|
+
* WHY A PROTOCOL BLOCK AT ALL. The capability this feature fixes already existed:
|
|
5
|
+
* the Level 1 catalog is in every prompt, `skill(name)` loads a body, and
|
|
6
|
+
* `README.md` has claimed since the Skills feature landed that "the model decides
|
|
7
|
+
* on its own whether a task matches a skill". What was missing is any instruction
|
|
8
|
+
* that the decision has to HAPPEN, before the work rather than after it. The
|
|
9
|
+
* catalog's own opening paragraph is a STATEMENT ("if a task matches one, call the
|
|
10
|
+
* skill tool"), not a step in an order of operations, and a statement the model
|
|
11
|
+
* has read forty times is the one it skims.
|
|
12
|
+
*
|
|
13
|
+
* WHY IT IS THIS SHORT. The protocol is paid for on EVERY LLM request: the system
|
|
14
|
+
* prompt is re-sent each time and nothing in this repository uses provider-side
|
|
15
|
+
* prefix caching. An earlier draft carried a 1.9 KB version whose second half
|
|
16
|
+
* repeated the disclosure ladder that `<available_skills>` already states three
|
|
17
|
+
* lines above it. Repeating a rule is not emphasis, it is noise — and noise about
|
|
18
|
+
* a rule teaches the model that these blocks are skimmable. Measured size of the
|
|
19
|
+
* block below is 1273 bytes; `skill-triage-prompt.test.ts` asserts the ceiling of
|
|
20
|
+
* 1500 so an edit has 227 bytes of room, not unlimited scope.
|
|
21
|
+
*
|
|
22
|
+
* ASCII ONLY — `agent/` is inside the glyph scanner's scope.
|
|
23
|
+
*/
|
|
24
|
+
import { renderSkillCandidates, SKILL_TRIAGE_LIMITS } from '../skills/triage.js';
|
|
25
|
+
/** Bump when the wording changes, so a transcript can be dated. */
|
|
26
|
+
export const SKILL_TRIAGE_BLOCK_VERSION = 'v1-2026-10-26';
|
|
27
|
+
/**
|
|
28
|
+
* The protocol text. Kept as a literal array so a diff of the prompt is a diff of
|
|
29
|
+
* the source, and so the grader/test can see line boundaries.
|
|
30
|
+
*/
|
|
31
|
+
const PROTOCOL_LINES = [
|
|
32
|
+
'<skill_triage>',
|
|
33
|
+
`Version: ${SKILL_TRIAGE_BLOCK_VERSION}`,
|
|
34
|
+
'',
|
|
35
|
+
'Triage every task that touches the workspace: AFTER the project guidance and the',
|
|
36
|
+
'directory listing, BEFORE you plan or change anything. A user-typed /<skill-name>',
|
|
37
|
+
'is an override, not the only way in.',
|
|
38
|
+
'',
|
|
39
|
+
'1. Read <available_skills> above, and <skill_candidates> when present. A candidate',
|
|
40
|
+
' is a computed guess about your message, not a verdict - check its one-line',
|
|
41
|
+
' description before trusting it.',
|
|
42
|
+
'2. If the catalog says some skills were not shown, or nothing listed plainly',
|
|
43
|
+
' covers the task, call skill_find(query) once before concluding there is no',
|
|
44
|
+
' skill.',
|
|
45
|
+
'3. If a skill covers the task, call skill(name="...") BEFORE the first source read',
|
|
46
|
+
' that would change code, then follow it. Say why in one line, not a paragraph.',
|
|
47
|
+
'4. If nothing covers the task, say so in one clause and continue. Do not load a',
|
|
48
|
+
' skill to look diligent, and never load several "to be safe".',
|
|
49
|
+
'',
|
|
50
|
+
'A loaded skill is reference material, not permission to ignore the user, the',
|
|
51
|
+
'plan-mode rules, or the tools you actually have. If a skill narrows your tool set,',
|
|
52
|
+
'that is its allowed-tools declaration working: report it, do not work around it.',
|
|
53
|
+
'Never install or author a skill because a task looks like it needs one; that',
|
|
54
|
+
'decision belongs to the user.',
|
|
55
|
+
'</skill_triage>',
|
|
56
|
+
];
|
|
57
|
+
const PROTOCOL = PROTOCOL_LINES.join('\n');
|
|
58
|
+
/**
|
|
59
|
+
* Protocol first, candidates second — in that order, always.
|
|
60
|
+
*
|
|
61
|
+
* The order is the meaning: the protocol says what to do, the data block says with
|
|
62
|
+
* what. Reversing them would put a list of suggestions above the rules for reading
|
|
63
|
+
* it, which is how a suggestion turns into an instruction.
|
|
64
|
+
*/
|
|
65
|
+
export function buildSkillTriageBlock(options) {
|
|
66
|
+
const candidates = renderSkillCandidates(options.candidates);
|
|
67
|
+
return candidates.length > 0 ? `${PROTOCOL}\n${candidates}` : PROTOCOL;
|
|
68
|
+
}
|
|
69
|
+
/** Byte size of the protocol half, for the budget assertion in the tests. */
|
|
70
|
+
export function skillTriageProtocolBytes() {
|
|
71
|
+
return new TextEncoder().encode(PROTOCOL).length;
|
|
72
|
+
}
|
|
73
|
+
/** The ceiling the protocol half must stay under, in bytes. */
|
|
74
|
+
export const SKILL_TRIAGE_PROTOCOL_MAX_BYTES = SKILL_TRIAGE_LIMITS.maxProtocolBytes;
|
|
@@ -34,8 +34,20 @@ export interface SystemPromptParams {
|
|
|
34
34
|
* plan as its final message instead of calling tools it does not have.
|
|
35
35
|
*/
|
|
36
36
|
planInteractive?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The rendered `<skill_triage>` block plus (when there are candidates) the
|
|
39
|
+
* `<skill_candidates>` block, or `''`.
|
|
40
|
+
*
|
|
41
|
+
* Spliced conditionally exactly as `skillsBlock` and the plan block are, which
|
|
42
|
+
* is what keeps invariant I-T1 true: `skills.triage: false`, a zero-skill
|
|
43
|
+
* install and `--no-skills` all produce the pre-feature prompt BYTE FOR BYTE.
|
|
44
|
+
*
|
|
45
|
+
* POSITION IS THE SEMANTICS. It goes immediately AFTER `skillsBlock`, because
|
|
46
|
+
* its first instruction refers to `<available_skills> above`, and a block
|
|
47
|
+
* separated from what it points at reads as a dangling reference. It also goes
|
|
48
|
+
* BEFORE the plan block, whose first step defers to it.
|
|
49
|
+
*/
|
|
50
|
+
skillTriageBlock?: string;
|
|
39
51
|
/**
|
|
40
52
|
* The rendered `<team_mode>` block for a LEAD with team mode on, or `''`.
|
|
41
53
|
*
|
|
@@ -44,6 +56,25 @@ export interface SystemPromptParams {
|
|
|
44
56
|
* produce the pre-team prompt BYTE FOR BYTE.
|
|
45
57
|
*/
|
|
46
58
|
teamBlock?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The rendered `<forced_team_mode>` block while the session is ARMED, or `''`
|
|
61
|
+
* (team-command-forced-dispatch §3.6 / I-2).
|
|
62
|
+
*
|
|
63
|
+
* A SEPARATE PARAMETER FROM `teamBlock`, not a sentence appended to it: that
|
|
64
|
+
* block says the lead MAY dispatch, this one says it MUST, and merging them
|
|
65
|
+
* would make the obligation inseparable from the permission - so an ordinary
|
|
66
|
+
* team session would start inheriting the forced tone, and switching `/team
|
|
67
|
+
* off` could only be done by rebuilding the team block itself.
|
|
68
|
+
*
|
|
69
|
+
* SPLICED AFTER `teamBlock` (it is the tightening of it) and BEFORE
|
|
70
|
+
* `subagentBlock` (a child must never read it: children have no `task` tool, so
|
|
71
|
+
* the block would order a call that cannot succeed - the same reason
|
|
72
|
+
* `system-prompt.ts:68` gives for keeping the two role blocks apart).
|
|
73
|
+
*
|
|
74
|
+
* Absent or `''` keeps the prompt BYTE-IDENTICAL to the pre-feature output,
|
|
75
|
+
* which is what I-2 asserts for every session that never types `/team`.
|
|
76
|
+
*/
|
|
77
|
+
forcedTeamBlock?: string;
|
|
47
78
|
/**
|
|
48
79
|
* The rendered `<subagent_role>` block for a CHILD, or `''`.
|
|
49
80
|
*
|
|
@@ -33,12 +33,13 @@ export function buildSystemPrompt(params) {
|
|
|
33
33
|
const skillsBlock = params.skillsBlock ?? '';
|
|
34
34
|
const hasSkills = skillsBlock.length > 0;
|
|
35
35
|
const planBlock = params.agentMode === 'plan'
|
|
36
|
-
? buildPlanModeBlock({
|
|
37
|
-
interactive: params.planInteractive !== false,
|
|
38
|
-
maxAskRounds: params.planMaxAskRounds ?? 4,
|
|
39
|
-
})
|
|
36
|
+
? buildPlanModeBlock({ interactive: params.planInteractive !== false })
|
|
40
37
|
: '';
|
|
38
|
+
// `''` when the caller has no candidates or the feature is off — which is what
|
|
39
|
+
// keeps this block's absence byte-identical to the pre-feature prompt (I-T1).
|
|
40
|
+
const skillTriageBlock = params.skillTriageBlock ?? '';
|
|
41
41
|
const teamBlock = params.teamBlock ?? '';
|
|
42
|
+
const forcedTeamBlock = params.forcedTeamBlock ?? '';
|
|
42
43
|
const subagentBlock = params.subagentBlock ?? '';
|
|
43
44
|
const todoBlock = params.todoBlock ?? '';
|
|
44
45
|
const fastBlock = params.fastBlock ?? '';
|
|
@@ -61,8 +62,15 @@ export function buildSystemPrompt(params) {
|
|
|
61
62
|
'',
|
|
62
63
|
buildProjectGuidanceBlock(),
|
|
63
64
|
...(hasSkills ? ['', skillsBlock] : []),
|
|
65
|
+
// AFTER <available_skills> (it refers to it as "above") and BEFORE <plan_mode>
|
|
66
|
+
// (whose step 1 defers to it). Conditional like every other optional block.
|
|
67
|
+
...(skillTriageBlock ? ['', skillTriageBlock] : []),
|
|
64
68
|
...(planBlock ? ['', planBlock] : []),
|
|
65
69
|
...(teamBlock ? ['', teamBlock] : []),
|
|
70
|
+
// IMMEDIATELY AFTER `<team_mode>`: "can" then "must". BEFORE the subagent
|
|
71
|
+
// block, because a child reading an order to dispatch would be ordered to use
|
|
72
|
+
// a tool it does not have (§3.6).
|
|
73
|
+
...(forcedTeamBlock ? ['', forcedTeamBlock] : []),
|
|
66
74
|
...(subagentBlock ? ['', subagentBlock] : []),
|
|
67
75
|
...(todoBlock ? ['', todoBlock] : []),
|
|
68
76
|
...(fastBlock ? ['', fastBlock] : []),
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS,
|
|
|
29
29
|
import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
|
|
30
30
|
import { getLogger } from './logging/logger.js';
|
|
31
31
|
import { runLogsCommand } from './logging/cli-commands.js';
|
|
32
|
-
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel,
|
|
32
|
+
import { ADAPTER_PROVIDERS, clampDensity, clampTheme, clampThinkingLevel, clampHumanTimeout, clampSkillsIntegrity, clampSkillsToolPolicy, clampContextWindow, clampMaxRenderInterval, clampScrollMode, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAdapterProvider, isAutoToken, parseMaxTokensInput, RETIRED_CONFIG_KEYS, DEFAULT_MAX_RENDER_INTERVAL_MS, DEFAULT_MAX_TOKENS, DEFAULT_PLAN_HUMAN_TIMEOUT_MS, DEFAULT_SCROLL_MODE, DEFAULT_SCROLL_RESUME_MS, DEFAULT_SKILLS_CONFIG, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from './config/schema.js';
|
|
33
33
|
import { makeGetApiKey } from './config/load.js';
|
|
34
34
|
import { AgentController, DENY_ALL_APPROVAL } from './agent/controller.js';
|
|
35
35
|
import { runSkillsCommand } from './skills/cli-commands.js';
|
|
@@ -1559,7 +1559,6 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1559
1559
|
'scrollResumeMs',
|
|
1560
1560
|
'historyEnabled',
|
|
1561
1561
|
'planModeDefault',
|
|
1562
|
-
'planModeMaxAskRounds',
|
|
1563
1562
|
'planModeHumanTimeoutMs',
|
|
1564
1563
|
// Dotted keys route into the nested `skills` object; see the switch below.
|
|
1565
1564
|
'skills.enabled',
|
|
@@ -1569,6 +1568,7 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1569
1568
|
'skills.integrity',
|
|
1570
1569
|
'skills.usageTracking',
|
|
1571
1570
|
'skills.toolPolicy',
|
|
1571
|
+
'skills.triage',
|
|
1572
1572
|
// `log.*` keys are handled by `applyLogConfigSet` below, NOT by the switch:
|
|
1573
1573
|
// that switch is already past this package's complexity ceiling.
|
|
1574
1574
|
...LOG_CONFIG_SET_KEYS,
|
|
@@ -1660,6 +1660,18 @@ function runConfigSet(key, value) {
|
|
|
1660
1660
|
}
|
|
1661
1661
|
}
|
|
1662
1662
|
function runConfigSetValue(key, value) {
|
|
1663
|
+
// RETIRED KEYS ARE REFUSED BY NAME, BEFORE THE UNKNOWN-KEY CHECK. Falling
|
|
1664
|
+
// through to `Unknown config key` would be technically true and practically
|
|
1665
|
+
// useless: a script that has run `config set planModeMaxAskRounds 5` for months
|
|
1666
|
+
// needs to be told what happened to it, not that it never existed. Exit 2 for
|
|
1667
|
+
// the same reason a bad value is 2 — the caller's argument is wrong, the file
|
|
1668
|
+
// is not.
|
|
1669
|
+
const retired = RETIRED_CONFIG_KEYS[key];
|
|
1670
|
+
if (retired !== undefined) {
|
|
1671
|
+
process.stderr.write(`"${key}" was removed: ${retired}\n`);
|
|
1672
|
+
process.exitCode = 2;
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1663
1675
|
if (!CONFIG_SET_KEYS.has(key)) {
|
|
1664
1676
|
process.stderr.write(`Unknown config key "${key}". Known keys: ${[...CONFIG_SET_KEYS].join(', ')}.\n`);
|
|
1665
1677
|
process.exitCode = 2;
|
|
@@ -1852,9 +1864,6 @@ function runConfigSetValue(key, value) {
|
|
|
1852
1864
|
case 'planModeDefault':
|
|
1853
1865
|
patch.planModeDefault = value === 'true' || value === '1';
|
|
1854
1866
|
break;
|
|
1855
|
-
case 'planModeMaxAskRounds':
|
|
1856
|
-
patch.planModeMaxAskRounds = clampAskRounds(value, DEFAULT_PLAN_MAX_ASK_ROUNDS);
|
|
1857
|
-
break;
|
|
1858
1867
|
case 'planModeHumanTimeoutMs':
|
|
1859
1868
|
patch.planModeHumanTimeoutMs = clampHumanTimeout(value, DEFAULT_PLAN_HUMAN_TIMEOUT_MS);
|
|
1860
1869
|
break;
|
|
@@ -1893,6 +1902,13 @@ function runConfigSetValue(key, value) {
|
|
|
1893
1902
|
toolPolicy: clampSkillsToolPolicy(value, DEFAULT_SKILLS_CONFIG.toolPolicy),
|
|
1894
1903
|
};
|
|
1895
1904
|
break;
|
|
1905
|
+
// The triage master switch. `false` restores the pre-feature prompt BYTE FOR
|
|
1906
|
+
// BYTE (invariant I-T1), which is the reason it is a key rather than a flag.
|
|
1907
|
+
case 'skills.triage':
|
|
1908
|
+
patch.skills = {
|
|
1909
|
+
triage: value === 'true' || value === '1',
|
|
1910
|
+
};
|
|
1911
|
+
break;
|
|
1896
1912
|
}
|
|
1897
1913
|
updatePersistedConfig(patch);
|
|
1898
1914
|
process.stdout.write(`Set ${key} = ${value}\n`);
|
|
@@ -21,12 +21,15 @@ import { applyRestoredSession } from '../session/startup-session.js';
|
|
|
21
21
|
import { runSessionCommand } from './session-command.js';
|
|
22
22
|
import { runBranchCommand } from './branch-command.js';
|
|
23
23
|
import { getLogger } from '../logging/logger.js';
|
|
24
|
-
import { resolveFastTier } from '../fast/resolve.js';
|
|
25
|
-
import { subagentModelSummary } from '../ui/overlays/model-profile-state.js';
|
|
26
24
|
import { registerQueueCommand } from './queue-command.js';
|
|
27
25
|
import { registerPromptCommands } from '../prompts/command.js';
|
|
28
26
|
import { registerKitCommand } from './kit-command.js';
|
|
29
27
|
import { registerAgentsCommand } from './agents-command.js';
|
|
28
|
+
import { registerSubagentCommand } from './subagent-command.js';
|
|
29
|
+
// `/team` (team-command-forced-dispatch §3.10). The BODY is `commands/team-command.ts`
|
|
30
|
+
// and this pair of lines is the whole registration; the `team` entry that used to
|
|
31
|
+
// live in `COMMANDS` above was a CONFIG command and now lives in `Settings > Team`.
|
|
32
|
+
import { registerTeamCommand } from './team-command.js';
|
|
30
33
|
// THE DRAWER AND THE HEADER LABEL (tui-shortcut-drawer-and-task-summary §4.7):
|
|
31
34
|
// two single-purpose commands in their own modules, registered from here. Both are
|
|
32
35
|
// UNCONDITIONAL, even with `taskSummary.enabled=false`, because they are the only
|
|
@@ -107,66 +110,6 @@ function formatUpdateStatus(port, override) {
|
|
|
107
110
|
lines.push(` next ${next ? new Date(next).toLocaleTimeString() : 'not scheduled'}`);
|
|
108
111
|
return lines.join('\n');
|
|
109
112
|
}
|
|
110
|
-
/**
|
|
111
|
-
* `/team model [main|fast|<profile-name>]` (subagent-model §3.6 / 5.2).
|
|
112
|
-
*
|
|
113
|
-
* The user-owned switch's command-line surface. `main` and `fast` are verbs
|
|
114
|
-
* compared case-insensitively; a NAME is matched EXACTLY, case and all,
|
|
115
|
-
* against the library - the same identity the picker shows - and a miss lists
|
|
116
|
-
* the names that would work rather than failing silently. The summary reuses
|
|
117
|
-
* the exact wording the settings root row renders, so the two surfaces can
|
|
118
|
-
* never describe the same setting differently.
|
|
119
|
-
*/
|
|
120
|
-
function runTeamModelCommand(ctx, subject) {
|
|
121
|
-
const controller = ctx.controller;
|
|
122
|
-
const config = controller.getConfig();
|
|
123
|
-
const tier = resolveFastTier(config, (id, role) => controller.hasApiKey(id, role));
|
|
124
|
-
const summary = subagentModelSummary({
|
|
125
|
-
mode: config.team.model,
|
|
126
|
-
modelProfileId: config.team.modelProfileId,
|
|
127
|
-
mainConnection: `${config.provider}:${config.model}`,
|
|
128
|
-
fastConnection: tier.ok ? `${tier.ref.providerId}:${tier.ref.modelId}` : null,
|
|
129
|
-
profiles: config.modelProfiles,
|
|
130
|
-
hasKey: (id) => controller.hasApiKey(id),
|
|
131
|
-
});
|
|
132
|
-
if (!subject) {
|
|
133
|
-
// The status readout: mode + the connection actually in effect + the
|
|
134
|
-
// disclosed reason when the request had to fall back (D-4).
|
|
135
|
-
const resolution = controller.subagentModelResolution();
|
|
136
|
-
const note = resolution.fallbackReason
|
|
137
|
-
? ` Unavailable (${resolution.fallbackReason}); children run on the main model.`
|
|
138
|
-
: resolution.effective === 'main'
|
|
139
|
-
? ''
|
|
140
|
-
: ` Children run on ${resolution.ref.providerId}:${resolution.ref.modelId}.`;
|
|
141
|
-
ctx.notify('info', `Subagent model: ${summary}.${note}`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (subject.toLowerCase() === 'main' || subject.toLowerCase() === 'fast') {
|
|
145
|
-
const mode = subject.toLowerCase();
|
|
146
|
-
const applied = controller.setTeamConfig({ model: mode });
|
|
147
|
-
ctx.persistConfig({ team: { model: applied.model } });
|
|
148
|
-
if (mode === 'fast' && !tier.ok) {
|
|
149
|
-
ctx.notify('warn', 'The fast tier is not available now; subagents will fall back to the main model.');
|
|
150
|
-
}
|
|
151
|
-
ctx.toast('success', `Subagent model: ${mode === 'main' ? 'same as main' : 'fast tier'}.`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const entries = config.modelProfiles?.entries ?? [];
|
|
155
|
-
const matches = entries.filter((entry) => entry.name === subject);
|
|
156
|
-
if (matches.length === 0) {
|
|
157
|
-
const names = entries.slice(0, 8).map((entry) => entry.name);
|
|
158
|
-
ctx.notify('warn', `No profile named "${subject}".`
|
|
159
|
-
+ (names.length ? ` Available: ${names.join(', ')}` : ' Create one in /settings first.'));
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const entry = matches[0];
|
|
163
|
-
const disambiguation = matches.length > 1 ? ` (#${entry.id.slice(0, 8)})` : '';
|
|
164
|
-
const applied = controller.setTeamConfig({ model: 'profile', modelProfileId: entry.id });
|
|
165
|
-
const teamPatch = { model: applied.model,
|
|
166
|
-
modelProfileId: applied.modelProfileId };
|
|
167
|
-
ctx.persistConfig({ team: teamPatch });
|
|
168
|
-
ctx.toast('success', `Subagent model: ${entry.name}${disambiguation} (${entry.provider}:${entry.model}).`);
|
|
169
|
-
}
|
|
170
113
|
/** Rows `/todo status` prints before it says how many it did not. */
|
|
171
114
|
const ACCEPT_STATUS_ROWS = 8;
|
|
172
115
|
/**
|
|
@@ -412,8 +355,10 @@ const COMMANDS = [
|
|
|
412
355
|
if (arg === 'status') {
|
|
413
356
|
const s = ctx.controller.getPlanStatus();
|
|
414
357
|
const pending = s.pending ? ` (pending ${MODE_LABEL[s.pending]} after this run)` : '';
|
|
415
|
-
|
|
416
|
-
|
|
358
|
+
// NO ROUND COUNT. The old line ended with `Ask rounds used: 3/4.`, which
|
|
359
|
+
// told the user their agent was rationing questions - a limit that no
|
|
360
|
+
// longer exists. Nothing replaced it: "0 of unlimited" is noise.
|
|
361
|
+
ctx.notify('info', `Mode: ${MODE_LABEL[s.effective]}${pending}.`);
|
|
417
362
|
return;
|
|
418
363
|
}
|
|
419
364
|
if (arg.length > 0 && arg !== 'on' && arg !== 'off') {
|
|
@@ -425,126 +370,6 @@ const COMMANDS = [
|
|
|
425
370
|
ctx.applyAgentMode(target);
|
|
426
371
|
},
|
|
427
372
|
},
|
|
428
|
-
{
|
|
429
|
-
name: 'team',
|
|
430
|
-
description: 'Team subagents: status | on | off | max <n> | overseer on|off | model [main|fast|<profile-name>]',
|
|
431
|
-
/**
|
|
432
|
-
* `/team on` CANNOT turn team mode on in a session that started without it,
|
|
433
|
-
* and this command must not pretend otherwise (§4.5 / D-17 / P0-2).
|
|
434
|
-
*
|
|
435
|
-
* `controller.ts` builds the tool array ONCE and its own comment forbids
|
|
436
|
-
* rebuilding it: `Agent.setTools()` mutates the live `ToolRegistry`, and
|
|
437
|
-
* `submit_plan` flips the session mode from INSIDE a tool execution, so a
|
|
438
|
-
* rebuild at that moment would mutate the registry mid-iteration. But
|
|
439
|
-
* `rebuildSystemPrompt()` IS a live path — so the naive reading of `/team on`
|
|
440
|
-
* (flip a flag, re-splice `<team_mode>`) produces a session where the model
|
|
441
|
-
* is told it has a `task` tool that was never registered. Every call comes
|
|
442
|
-
* back "unknown tool", and the model has no way to discover why.
|
|
443
|
-
*
|
|
444
|
-
* So the answer splits by what was decided at construction, and the honest
|
|
445
|
-
* branch — persist it, say the session is unchanged — is the whole point.
|
|
446
|
-
*/
|
|
447
|
-
run: (ctx) => {
|
|
448
|
-
const arg = ctx.args.trim().toLowerCase();
|
|
449
|
-
const [verb, value] = arg.split(/\s+/, 2);
|
|
450
|
-
const controller = ctx.controller;
|
|
451
|
-
// RAW TOKENS, KEPT BESIDE THE LOWERCASED PAIR (V-2): `model` matches a
|
|
452
|
-
// profile NAME case-sensitively - like the picker and the library -
|
|
453
|
-
// and the whole-args toLowerCase() above would have made
|
|
454
|
-
// `/team model MyProfile` silently unmatchable. Only the VERB token is
|
|
455
|
-
// compared through the lowercased pair.
|
|
456
|
-
const rawTokens = ctx.args.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
457
|
-
// Refused mid-dispatch for the reason `/reload` already records: half a
|
|
458
|
-
// dispatch under one ceiling and half under another produces a result
|
|
459
|
-
// nothing can afterwards explain.
|
|
460
|
-
if (verb && verb !== 'status' && controller.isTeamBusy()) {
|
|
461
|
-
ctx.notify('warn', 'Cannot change team settings mid-dispatch.');
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
if (!verb || verb === 'status') {
|
|
465
|
-
const cfg = controller.getTeamConfig();
|
|
466
|
-
const config = controller.getConfig();
|
|
467
|
-
const state = !controller.isTeamRegistered()
|
|
468
|
-
? 'off for this session (started with --no-team)'
|
|
469
|
-
: controller.isTeamEnabled()
|
|
470
|
-
? 'on'
|
|
471
|
-
: 'off';
|
|
472
|
-
const snapshot = controller.getTeamSnapshot();
|
|
473
|
-
const roster = snapshot
|
|
474
|
-
? `\nRunning: ${snapshot.runs.map((r) => `${r.label} (${r.phase})`).join(', ')}`
|
|
475
|
-
: '';
|
|
476
|
-
const tier = resolveFastTier(controller.getConfig(), (id, role) => controller.hasApiKey(id, role));
|
|
477
|
-
const subagent = subagentModelSummary({
|
|
478
|
-
mode: cfg.model,
|
|
479
|
-
modelProfileId: cfg.modelProfileId,
|
|
480
|
-
mainConnection: `${config.provider}:${config.model}`,
|
|
481
|
-
fastConnection: tier.ok ? `${tier.ref.providerId}:${tier.ref.modelId}` : null,
|
|
482
|
-
profiles: config.modelProfiles,
|
|
483
|
-
hasKey: (id) => controller.hasApiKey(id),
|
|
484
|
-
});
|
|
485
|
-
ctx.notify('info', `Team mode: ${state}. Max subagents: ${cfg.maxSubagents}, ` +
|
|
486
|
-
`concurrent: ${cfg.maxConcurrent}, supervisor: ${cfg.overseer ? 'on' : 'off'}` +
|
|
487
|
-
`${cfg.overseer ? ' (never kills; checks the fast tier each tick)' : ' (legacy hard timeouts)'}.` +
|
|
488
|
-
`\nSubagent model: ${subagent}` +
|
|
489
|
-
`${roster}`);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
if (verb === 'on' || verb === 'off') {
|
|
493
|
-
const enabled = verb === 'on';
|
|
494
|
-
// A PARTIAL `team` section, safe only because `updatePersistedConfig`
|
|
495
|
-
// deep-merges it — the same contract `/skills disable` relies on.
|
|
496
|
-
ctx.persistConfig({ team: { enabled } });
|
|
497
|
-
if (!controller.isTeamRegistered()) {
|
|
498
|
-
// The bottom-right cell of §4.5's table. It is honest, it is one
|
|
499
|
-
// sentence, and it keeps AC-11 intact: a `--no-team` session never
|
|
500
|
-
// grows a wrapper or an entry, whatever the user types afterwards.
|
|
501
|
-
ctx.notify('info', enabled
|
|
502
|
-
? 'Team mode is off for this session (started with --no-team). Saved for next launch.'
|
|
503
|
-
: 'Team mode is already off for this session. Saved for next launch.');
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
controller.setTeamEnabled(enabled);
|
|
507
|
-
ctx.toast('success', `Team mode ${enabled ? 'on' : 'off'}.`);
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
if (verb === 'max') {
|
|
511
|
-
const n = Number.parseInt(value ?? '', 10);
|
|
512
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
513
|
-
ctx.notify('warn', 'Usage: /team max <n>');
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
// Clamped by `setTeamConfig` and again on the persist path, so a value
|
|
517
|
-
// above the requirement's ceiling can reach neither the runtime nor the
|
|
518
|
-
// file (R-g).
|
|
519
|
-
const applied = controller.setTeamConfig({ maxSubagents: n });
|
|
520
|
-
ctx.persistConfig({
|
|
521
|
-
team: { maxSubagents: applied.maxSubagents },
|
|
522
|
-
});
|
|
523
|
-
ctx.toast('success', `Max subagents set to ${applied.maxSubagents}.`);
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
if (verb === 'model') {
|
|
527
|
-
runTeamModelCommand(ctx, rawTokens[1]);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
if (verb === 'overseer') {
|
|
531
|
-
if (value !== 'on' && value !== 'off') {
|
|
532
|
-
ctx.notify('warn', 'Usage: /team overseer on|off');
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
// Read LIVE per dispatch by the runtime, so no rebuild is needed - the
|
|
536
|
-
// same reason this command does not touch the tool array (its own header
|
|
537
|
-
// comment). Clamped by `setTeamConfig` and again on the persist path.
|
|
538
|
-
const applied = controller.setTeamConfig({ overseer: value === 'on' });
|
|
539
|
-
ctx.persistConfig({
|
|
540
|
-
team: { overseer: applied.overseer },
|
|
541
|
-
});
|
|
542
|
-
ctx.toast('success', `Dispatch supervisor ${applied.overseer ? 'on' : 'off'}.`);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
ctx.notify('warn', `Unknown argument "${verb}" - use /team [status|on|off|max <n>|overseer on|off|model main|fast|<profile-name>].`);
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
373
|
{
|
|
549
374
|
name: 'fast',
|
|
550
375
|
description: 'Fast model tier: status | on | off | model <id> | provider <id> | same | ' +
|
|
@@ -1508,6 +1333,19 @@ export function registerBuiltinCommands(registry) {
|
|
|
1508
1333
|
// The read-only subagent board (tui-subagent-panel §6.4). Same shape again: the
|
|
1509
1334
|
// body is `commands/agents-command.ts` and the registration is this one line.
|
|
1510
1335
|
registerAgentsCommand(registry);
|
|
1336
|
+
// `/subagent` (subagent-fanout 3.5). REGISTERED UNCONDITIONALLY, like `/prompts`
|
|
1337
|
+
// and for the same reason: a `--no-team` session must still be able to ask the
|
|
1338
|
+
// command why it cannot force anything (I-7 / AC-15) - so registering it may
|
|
1339
|
+
// not read config, build a connection or send a request.
|
|
1340
|
+
// It is now a DEPRECATED ALIAS of `/team` (team-command-forced-dispatch D-12):
|
|
1341
|
+
// same implementation, one extra toast, no second grammar.
|
|
1342
|
+
registerSubagentCommand(registry);
|
|
1343
|
+
// `/team` (team-command-forced-dispatch §3.10). Registered HERE rather than from
|
|
1344
|
+
// `COMMANDS` because the entry that used to sit in that array was the config
|
|
1345
|
+
// command (`status | on | off | max n | overseer | model`) - those four verbs
|
|
1346
|
+
// now live in the settings screen, and this command is the ACT: arm forced mode
|
|
1347
|
+
// and start work. Unconditional, for the reason `/subagent` above records.
|
|
1348
|
+
registerTeamCommand(registry);
|
|
1511
1349
|
// The keyboard drawer (`Ctrl+G` / `/keys`) and the task label (`/task`). Two
|
|
1512
1350
|
// registrations, no new mechanism: `/keys` sets the panel's pre-filter and opens
|
|
1513
1351
|
// the same overlay `Ctrl+G` opens, and `/task` reports what the header cell says.
|
|
@@ -1522,7 +1360,12 @@ export function registerBuiltinCommands(registry) {
|
|
|
1522
1360
|
* `registry.get(name)` instead, because that is the only check that stays
|
|
1523
1361
|
* correct when commands are registered dynamically (§7.1 / P1-2).
|
|
1524
1362
|
*/
|
|
1525
|
-
export const BUILTIN_COMMAND_NAMES = ['queue', 'prompts', 'agents', '
|
|
1363
|
+
export const BUILTIN_COMMAND_NAMES = ['queue', 'prompts', 'agents', 'subagent',
|
|
1364
|
+
// `team` is listed here because it is no longer in `COMMANDS` (it moved to
|
|
1365
|
+
// `commands/team-command.ts`), and this array is the documentation/tests surface
|
|
1366
|
+
// for every built-in name - the `flatMap` below only sees `COMMANDS`.
|
|
1367
|
+
'team',
|
|
1368
|
+
'keys', 'task',
|
|
1526
1369
|
...COMMANDS.flatMap((c) => [
|
|
1527
1370
|
c.name,
|
|
1528
1371
|
...(c.aliases ?? []),
|