@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/cli.js
CHANGED
|
@@ -25,11 +25,11 @@ import { formatMigrationNotice, migrateLegacyState } from './config/migrate-lega
|
|
|
25
25
|
import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
|
|
26
26
|
import { migrateStateOutOfConfig } from './config/migrate-state-out-of-config.js';
|
|
27
27
|
import { runHistoryCommand } from './config/history-commands.js';
|
|
28
|
-
import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, JOBS_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyCompactionConfigSet, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyJobsConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
28
|
+
import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, JOBS_CONFIG_SET_KEYS, TASK_SUMMARY_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyCompactionConfigSet, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyJobsConfigSet, applyTaskSummaryConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
|
|
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';
|
|
@@ -276,15 +276,19 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
276
276
|
version: VERSION,
|
|
277
277
|
todoPanelCapable,
|
|
278
278
|
confirm: (req) => confirmBridge.handler ? confirmBridge.handler(req) : Promise.resolve(true),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
279
|
+
// THE TWO CAN NEVER BOTH BE PRESENT: `interactive: false` is the only path
|
|
280
|
+
// that supplies a bridge gate, and the interactive branch is the only one that
|
|
281
|
+
// reads the overlay bridge. `??` keeps `-p`'s deny-all default byte-identical.
|
|
282
|
+
approval: opts.approval ??
|
|
283
|
+
(opts.interactive
|
|
284
|
+
? {
|
|
285
|
+
// Read live on every call: the App's effect cleanup nulls the handler
|
|
286
|
+
// on unmount, and a cached `true` would keep the gate open after the
|
|
287
|
+
// human channel is gone.
|
|
288
|
+
canPrompt: () => confirmBridge.handler !== null,
|
|
289
|
+
request: (req) => confirmBridge.handler ? confirmBridge.handler(req) : Promise.resolve(false),
|
|
290
|
+
}
|
|
291
|
+
: DENY_ALL_APPROVAL),
|
|
288
292
|
notify: (level, text) => {
|
|
289
293
|
if (!opts.interactive)
|
|
290
294
|
process.stderr.write(`[skills] ${level}: ${text}\n`);
|
|
@@ -294,17 +298,36 @@ function makeController(flags, opts = { interactive: true }) {
|
|
|
294
298
|
// render a question wizard or a plan card. Headless gets the fail-closed
|
|
295
299
|
// gate, whose STATIC `neverPrompts` is also what stops `createPlanTools`
|
|
296
300
|
// registering two tools the model could never use there.
|
|
297
|
-
humanInput: opts.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
301
|
+
humanInput: opts.humanInput ??
|
|
302
|
+
(opts.interactive
|
|
303
|
+
? {
|
|
304
|
+
// Live probe, never cached - same contract as `approval` above.
|
|
305
|
+
canPrompt: () => humanInputBridge.handler !== null,
|
|
306
|
+
request: (req, signal) => humanInputBridge.handler
|
|
307
|
+
? humanInputBridge.handler(req, signal)
|
|
308
|
+
: Promise.resolve(null),
|
|
309
|
+
}
|
|
310
|
+
: DENY_ALL_HUMAN_INPUT),
|
|
306
311
|
...(opts.permission ? { permission: opts.permission } : {}),
|
|
307
312
|
...(opts.appendSystemPrompt ? { appendSystemPrompt: opts.appendSystemPrompt } : {}),
|
|
313
|
+
// SPREAD, never `toolGate: opts.toolGate`: with no gate the deps bag has no
|
|
314
|
+
// such key, `createBuiltinTools` never enters its mapping branch, and the tool
|
|
315
|
+
// array stays provably identical outside bridge mode (I-1).
|
|
316
|
+
...(opts.toolGate ? { toolGate: opts.toolGate } : {}),
|
|
317
|
+
...(opts.toolGateForChild ? { toolGateForChild: opts.toolGateForChild } : {}),
|
|
318
|
+
...(opts.decisionBudgetMs !== undefined ? { decisionBudgetMs: opts.decisionBudgetMs } : {}),
|
|
319
|
+
// THE R3 PAIR, AND THIS LINE IS THE DIFFERENCE BETWEEN A WORKING FEATURE AND A DEAD
|
|
320
|
+
// ONE (headless-request-scope-and-typed-result section 2.6 rows 11/17). This factory
|
|
321
|
+
// takes `deps` as a bag and FORWARDS ONLY WHAT IS NAMED HERE, so a new dep that is
|
|
322
|
+
// not spread below is dropped in silence: every unit test that stubs the factory
|
|
323
|
+
// keeps passing while the real binary never registers the tool. That is the D-12
|
|
324
|
+
// defect this project has already paid for once, which is why both keys are spread
|
|
325
|
+
// conditionally and adjacent to each other.
|
|
326
|
+
...(opts.resultTool ? { resultTool: opts.resultTool } : {}),
|
|
327
|
+
...(opts.structuredResultBlock
|
|
328
|
+
? { structuredResultBlock: opts.structuredResultBlock }
|
|
329
|
+
: {}),
|
|
330
|
+
...(opts.hookRunner ? { hookRunner: opts.hookRunner } : {}),
|
|
308
331
|
});
|
|
309
332
|
return { controller, confirmBridge, humanInputBridge };
|
|
310
333
|
}
|
|
@@ -322,15 +345,34 @@ function makeExecController(flags, deps) {
|
|
|
322
345
|
const { controller } = makeController(flags, { interactive: false, ...deps });
|
|
323
346
|
void controller.refreshModelMetadata();
|
|
324
347
|
const logger = getLogger();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
348
|
+
// PROCESS-LEVEL WORK HAPPENS ONCE, PER-CONTROLLER WORK HAPPENS EVERY TIME
|
|
349
|
+
// (headless-threads-and-lifecycle-hooks section 2.3.4, review P1-8).
|
|
350
|
+
//
|
|
351
|
+
// The four statements in this branch attach to a PROCESS-WIDE singleton - the logger's
|
|
352
|
+
// handler list, the log file, the same untrusted skill directories - so running them per
|
|
353
|
+
// thread would emit N `run_exec` lines and N `[log]` handlers for one process.
|
|
354
|
+
//
|
|
355
|
+
// `attachAgentEvents` / `attachTeamEvents` ARE NOT IN THIS BRANCH and must never be:
|
|
356
|
+
// each binds to THIS controller and returns its own disposer, so skipping them would
|
|
357
|
+
// mean seven of eight tenants' agent events never reach the log at all. That is an
|
|
358
|
+
// observability regression, not an optimisation - the opposite of what the first draft of
|
|
359
|
+
// the design said. The disposers go back to the caller (the thread runtime) and are
|
|
360
|
+
// invoked when the thread closes (I-T6).
|
|
361
|
+
// `processLevelMounts` IS EXPLICIT RATHER THAN DERIVED FROM `threadId` (review P1-8):
|
|
362
|
+
// the default thread carries `threadId: 'main'` like every other thread, so a guard on
|
|
363
|
+
// "has a thread id" would skip these four statements for ALL of them and leave a
|
|
364
|
+
// thread-mode run with no `run_exec` line and no `onFailure` handler at all.
|
|
365
|
+
if (deps.processLevelMounts !== false) {
|
|
366
|
+
logger.info('cli', 'run_exec', {});
|
|
367
|
+
logger.onFailure((reason) => process.stderr.write(`[log] ${reason}\n`));
|
|
368
|
+
for (const dir of controller.getSkillService().untrustedDirs()) {
|
|
369
|
+
// STDERR, NEVER STDOUT. In `json` / `stream-json` mode stdout carries
|
|
370
|
+
// nothing but the schema, and a notice on it would corrupt every consumer.
|
|
371
|
+
process.stderr.write(`[skills] project skills in ${dir} skipped (untrusted). Run: aragon skills trust ${dir}\n`);
|
|
372
|
+
}
|
|
333
373
|
}
|
|
374
|
+
const disposers = [attachAgentEvents(logger, controller), attachTeamEvents(logger, controller)];
|
|
375
|
+
deps.onAttach?.(disposers);
|
|
334
376
|
return controller;
|
|
335
377
|
}
|
|
336
378
|
/**
|
|
@@ -1517,7 +1559,6 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1517
1559
|
'scrollResumeMs',
|
|
1518
1560
|
'historyEnabled',
|
|
1519
1561
|
'planModeDefault',
|
|
1520
|
-
'planModeMaxAskRounds',
|
|
1521
1562
|
'planModeHumanTimeoutMs',
|
|
1522
1563
|
// Dotted keys route into the nested `skills` object; see the switch below.
|
|
1523
1564
|
'skills.enabled',
|
|
@@ -1527,6 +1568,7 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1527
1568
|
'skills.integrity',
|
|
1528
1569
|
'skills.usageTracking',
|
|
1529
1570
|
'skills.toolPolicy',
|
|
1571
|
+
'skills.triage',
|
|
1530
1572
|
// `log.*` keys are handled by `applyLogConfigSet` below, NOT by the switch:
|
|
1531
1573
|
// that switch is already past this package's complexity ceiling.
|
|
1532
1574
|
...LOG_CONFIG_SET_KEYS,
|
|
@@ -1551,6 +1593,12 @@ const CONFIG_SET_KEYS = new Set([
|
|
|
1551
1593
|
// own assertion: the static guard in `config.test.ts` matches LITERAL keys in
|
|
1552
1594
|
// this file, so a spread set is invisible to it (RV-P2-8).
|
|
1553
1595
|
...JOBS_CONFIG_SET_KEYS,
|
|
1596
|
+
// `taskSummary.*` keys are handled by `applyTaskSummaryConfigSet`, same reasoning
|
|
1597
|
+
// again (tui-shortcut-drawer-and-task-summary P0-3). WITHOUT THIS LINE the keys
|
|
1598
|
+
// fall through to `Unknown config key`, and WITHOUT the matching case below the
|
|
1599
|
+
// membership set accepts a write that never happens - the two halves the static
|
|
1600
|
+
// guard in `config.test.ts` exists to pair up.
|
|
1601
|
+
...TASK_SUMMARY_CONFIG_SET_KEYS,
|
|
1554
1602
|
]);
|
|
1555
1603
|
/**
|
|
1556
1604
|
* `aragon config set maxTokens <n|auto|default>`.
|
|
@@ -1612,6 +1660,18 @@ function runConfigSet(key, value) {
|
|
|
1612
1660
|
}
|
|
1613
1661
|
}
|
|
1614
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
|
+
}
|
|
1615
1675
|
if (!CONFIG_SET_KEYS.has(key)) {
|
|
1616
1676
|
process.stderr.write(`Unknown config key "${key}". Known keys: ${[...CONFIG_SET_KEYS].join(', ')}.\n`);
|
|
1617
1677
|
process.exitCode = 2;
|
|
@@ -1701,6 +1761,16 @@ function runConfigSetValue(key, value) {
|
|
|
1701
1761
|
process.stdout.write(`Set ${key} = ${String(merged.jobs[field])}\n`);
|
|
1702
1762
|
return;
|
|
1703
1763
|
}
|
|
1764
|
+
const taskSummaryPatch = applyTaskSummaryConfigSet(key, value);
|
|
1765
|
+
if (taskSummaryPatch) {
|
|
1766
|
+
// Echoed from the STORED value, like every section above: `config set
|
|
1767
|
+
// taskSummary.enabled banana` prints `false`, which is what the read path will
|
|
1768
|
+
// report and therefore the only honest answer.
|
|
1769
|
+
const merged = updatePersistedConfig(taskSummaryPatch);
|
|
1770
|
+
const field = key.slice('taskSummary.'.length);
|
|
1771
|
+
process.stdout.write(`Set ${key} = ${String(merged.taskSummary[field])}\n`);
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1704
1774
|
const patch = {};
|
|
1705
1775
|
switch (key) {
|
|
1706
1776
|
case 'provider':
|
|
@@ -1794,9 +1864,6 @@ function runConfigSetValue(key, value) {
|
|
|
1794
1864
|
case 'planModeDefault':
|
|
1795
1865
|
patch.planModeDefault = value === 'true' || value === '1';
|
|
1796
1866
|
break;
|
|
1797
|
-
case 'planModeMaxAskRounds':
|
|
1798
|
-
patch.planModeMaxAskRounds = clampAskRounds(value, DEFAULT_PLAN_MAX_ASK_ROUNDS);
|
|
1799
|
-
break;
|
|
1800
1867
|
case 'planModeHumanTimeoutMs':
|
|
1801
1868
|
patch.planModeHumanTimeoutMs = clampHumanTimeout(value, DEFAULT_PLAN_HUMAN_TIMEOUT_MS);
|
|
1802
1869
|
break;
|
|
@@ -1835,6 +1902,13 @@ function runConfigSetValue(key, value) {
|
|
|
1835
1902
|
toolPolicy: clampSkillsToolPolicy(value, DEFAULT_SKILLS_CONFIG.toolPolicy),
|
|
1836
1903
|
};
|
|
1837
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;
|
|
1838
1912
|
}
|
|
1839
1913
|
updatePersistedConfig(patch);
|
|
1840
1914
|
process.stdout.write(`Set ${key} = ${value}\n`);
|
|
@@ -27,6 +27,13 @@ import { registerQueueCommand } from './queue-command.js';
|
|
|
27
27
|
import { registerPromptCommands } from '../prompts/command.js';
|
|
28
28
|
import { registerKitCommand } from './kit-command.js';
|
|
29
29
|
import { registerAgentsCommand } from './agents-command.js';
|
|
30
|
+
import { registerSubagentCommand } from './subagent-command.js';
|
|
31
|
+
// THE DRAWER AND THE HEADER LABEL (tui-shortcut-drawer-and-task-summary §4.7):
|
|
32
|
+
// two single-purpose commands in their own modules, registered from here. Both are
|
|
33
|
+
// UNCONDITIONAL, even with `taskSummary.enabled=false`, because they are the only
|
|
34
|
+
// surfaces that can explain the switch.
|
|
35
|
+
import { keysCommand } from './keys-command.js';
|
|
36
|
+
import { taskCommand } from './task-command.js';
|
|
30
37
|
import { perfCommand } from './perf.js';
|
|
31
38
|
import { runCompactCommand } from '../compaction/command.js';
|
|
32
39
|
import { runClearCommand } from './clear-command.js';
|
|
@@ -406,8 +413,10 @@ const COMMANDS = [
|
|
|
406
413
|
if (arg === 'status') {
|
|
407
414
|
const s = ctx.controller.getPlanStatus();
|
|
408
415
|
const pending = s.pending ? ` (pending ${MODE_LABEL[s.pending]} after this run)` : '';
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
// NO ROUND COUNT. The old line ended with `Ask rounds used: 3/4.`, which
|
|
417
|
+
// told the user their agent was rationing questions - a limit that no
|
|
418
|
+
// longer exists. Nothing replaced it: "0 of unlimited" is noise.
|
|
419
|
+
ctx.notify('info', `Mode: ${MODE_LABEL[s.effective]}${pending}.`);
|
|
411
420
|
return;
|
|
412
421
|
}
|
|
413
422
|
if (arg.length > 0 && arg !== 'on' && arg !== 'off') {
|
|
@@ -1502,6 +1511,16 @@ export function registerBuiltinCommands(registry) {
|
|
|
1502
1511
|
// The read-only subagent board (tui-subagent-panel §6.4). Same shape again: the
|
|
1503
1512
|
// body is `commands/agents-command.ts` and the registration is this one line.
|
|
1504
1513
|
registerAgentsCommand(registry);
|
|
1514
|
+
// `/subagent` (subagent-fanout 3.5). REGISTERED UNCONDITIONALLY, like `/prompts`
|
|
1515
|
+
// and for the same reason: a `--no-team` session must still be able to ask the
|
|
1516
|
+
// command why it cannot force anything (I-7 / AC-15) - so registering it may
|
|
1517
|
+
// not read config, build a connection or send a request.
|
|
1518
|
+
registerSubagentCommand(registry);
|
|
1519
|
+
// The keyboard drawer (`Ctrl+G` / `/keys`) and the task label (`/task`). Two
|
|
1520
|
+
// registrations, no new mechanism: `/keys` sets the panel's pre-filter and opens
|
|
1521
|
+
// the same overlay `Ctrl+G` opens, and `/task` reports what the header cell says.
|
|
1522
|
+
registry.register(keysCommand);
|
|
1523
|
+
registry.register(taskCommand);
|
|
1505
1524
|
}
|
|
1506
1525
|
/**
|
|
1507
1526
|
* Every built-in command name INCLUDING aliases (`quit`), for documentation and
|
|
@@ -1511,7 +1530,9 @@ export function registerBuiltinCommands(registry) {
|
|
|
1511
1530
|
* `registry.get(name)` instead, because that is the only check that stays
|
|
1512
1531
|
* correct when commands are registered dynamically (§7.1 / P1-2).
|
|
1513
1532
|
*/
|
|
1514
|
-
export const BUILTIN_COMMAND_NAMES = ['queue', 'prompts', 'agents',
|
|
1533
|
+
export const BUILTIN_COMMAND_NAMES = ['queue', 'prompts', 'agents', 'subagent',
|
|
1534
|
+
'keys', 'task',
|
|
1535
|
+
...COMMANDS.flatMap((c) => [
|
|
1515
1536
|
c.name,
|
|
1516
1537
|
...(c.aliases ?? []),
|
|
1517
1538
|
])].sort();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/keys [query]` — open the keyboard drawer (tui-shortcut-drawer-and-task-summary
|
|
3
|
+
* §4.7).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/commands/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* THE COMMAND IS THE SECOND ENTRANCE TO THE SAME ROOM, not a second room: it sets
|
|
8
|
+
* the pre-filter and opens `overlay: 'shortcuts'`, exactly as `Ctrl+G` does. That
|
|
9
|
+
* pairing (`/agents` <-> `Ctrl+B`, `/help` <-> `?`) is the established one, and it
|
|
10
|
+
* exists because a key is unreachable in a script, a paste and a screen reader.
|
|
11
|
+
*
|
|
12
|
+
* NOTE ON NAMES: `CommandRegistry.register` overwrites unconditionally while the
|
|
13
|
+
* skill loader only claims a bare `/<name>` when nothing else has it, so a user with
|
|
14
|
+
* a skill called `keys` now reaches it as `/skill:keys`. That is registered in
|
|
15
|
+
* `CHANGELOG.md`; `/keys` deliberately has NO alias, because an alias would multiply
|
|
16
|
+
* the same shadowing.
|
|
17
|
+
*/
|
|
18
|
+
import type { SlashCommand } from './registry.js';
|
|
19
|
+
/** The pure part: what to open and with what filter. Separated so it is testable. */
|
|
20
|
+
export declare function parseKeysArgs(args: string): {
|
|
21
|
+
query: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const keysCommand: SlashCommand;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/keys [query]` — open the keyboard drawer (tui-shortcut-drawer-and-task-summary
|
|
3
|
+
* §4.7).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/commands/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* THE COMMAND IS THE SECOND ENTRANCE TO THE SAME ROOM, not a second room: it sets
|
|
8
|
+
* the pre-filter and opens `overlay: 'shortcuts'`, exactly as `Ctrl+G` does. That
|
|
9
|
+
* pairing (`/agents` <-> `Ctrl+B`, `/help` <-> `?`) is the established one, and it
|
|
10
|
+
* exists because a key is unreachable in a script, a paste and a screen reader.
|
|
11
|
+
*
|
|
12
|
+
* NOTE ON NAMES: `CommandRegistry.register` overwrites unconditionally while the
|
|
13
|
+
* skill loader only claims a bare `/<name>` when nothing else has it, so a user with
|
|
14
|
+
* a skill called `keys` now reaches it as `/skill:keys`. That is registered in
|
|
15
|
+
* `CHANGELOG.md`; `/keys` deliberately has NO alias, because an alias would multiply
|
|
16
|
+
* the same shadowing.
|
|
17
|
+
*/
|
|
18
|
+
/** The pure part: what to open and with what filter. Separated so it is testable. */
|
|
19
|
+
export function parseKeysArgs(args) {
|
|
20
|
+
// ONE TOKEN, TRIMMED: the filter matches `id` / `keys` / `keyword` / aliases, and
|
|
21
|
+
// a multi-word query would only ever match a phrase none of them contain.
|
|
22
|
+
return { query: args.trim() };
|
|
23
|
+
}
|
|
24
|
+
export const keysCommand = {
|
|
25
|
+
name: 'keys',
|
|
26
|
+
description: 'List every keyboard shortcut (opens the drawer)',
|
|
27
|
+
run: (ctx) => {
|
|
28
|
+
const { query } = parseKeysArgs(ctx.args);
|
|
29
|
+
// WITHOUT THE PORT THERE IS NO PANEL TO OPEN, and saying so is better than
|
|
30
|
+
// opening nothing: this is the one command whose failure mode would otherwise be
|
|
31
|
+
// a keystroke that appears to do nothing at all.
|
|
32
|
+
if (!ctx.taskSummary) {
|
|
33
|
+
ctx.notify('warn', 'The shortcut drawer is not available in this build.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
ctx.taskSummary.openPanel(query);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -175,6 +175,45 @@ export interface CommandContext {
|
|
|
175
175
|
* needs the SHAPE of the port, never a value at run time.
|
|
176
176
|
*/
|
|
177
177
|
prompts?: PromptCommandPort;
|
|
178
|
+
/**
|
|
179
|
+
* The task label's three capabilities (`/keys`, `/task`; tui-shortcut-drawer-and-
|
|
180
|
+
* task-summary §6.4).
|
|
181
|
+
*
|
|
182
|
+
* OPTIONAL, and this is the sixth time this file records the same rule: every
|
|
183
|
+
* fixture in this package builds `CommandContext` by literal, so a REQUIRED field
|
|
184
|
+
* would fail to compile every one of them for a feature none of them is about.
|
|
185
|
+
* Absent means the build has no label machinery, and both commands say so instead
|
|
186
|
+
* of crashing - which matters most for `/task`, whose entire job is to explain why
|
|
187
|
+
* there is no label.
|
|
188
|
+
*
|
|
189
|
+
* TYPE-ONLY import, for the reason the `branch` note above records.
|
|
190
|
+
*/
|
|
191
|
+
taskSummary?: TaskSummaryCommandPort;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* What `/keys` and `/task` may do.
|
|
195
|
+
*
|
|
196
|
+
* THREE METHODS, NOT ONE: opening the drawer, reading the label and forcing a
|
|
197
|
+
* regeneration are three different user intentions, and `/task refresh` must not be
|
|
198
|
+
* expressible as "open the panel and press r".
|
|
199
|
+
*/
|
|
200
|
+
export interface TaskSummaryCommandPort {
|
|
201
|
+
/** Open the drawer, optionally pre-filtered (`/keys <query>`). */
|
|
202
|
+
openPanel(query?: string): void;
|
|
203
|
+
/** The current label, its provenance and why it is a fallback. */
|
|
204
|
+
read(): {
|
|
205
|
+
view: {
|
|
206
|
+
text: string;
|
|
207
|
+
tone: 'model' | 'fallback';
|
|
208
|
+
reason: string | null;
|
|
209
|
+
by: string;
|
|
210
|
+
at: number;
|
|
211
|
+
} | null;
|
|
212
|
+
pending: boolean;
|
|
213
|
+
enabled: boolean;
|
|
214
|
+
};
|
|
215
|
+
/** Regenerate from the last user message. Returns the anchor it used, or `''`. */
|
|
216
|
+
refresh(): string;
|
|
178
217
|
}
|
|
179
218
|
/** What `/mouse` can see and do. Read at command time, never captured. */
|
|
180
219
|
export interface MouseCommandPort {
|
package/dist/commands/skills.js
CHANGED
|
@@ -12,6 +12,7 @@ import { checkManifest } from '../skills/manifest.js';
|
|
|
12
12
|
import { updateAllSkills, updateSkill, isHardFailure } from '../skills/updater.js';
|
|
13
13
|
import { currentPlatform } from '../skills/node-host.js';
|
|
14
14
|
import { listUsage, resetUsage, getUsagePath } from '../skills/usage.js';
|
|
15
|
+
import { SKILL_TRIAGE_LIMITS } from '../skills/triage.js';
|
|
15
16
|
import { HOST_TOOL_NAMES, SKILL_TOOL_FLOOR } from '../tools/index.js';
|
|
16
17
|
import { SKILLS_TOOL_POLICY_MODES, isSkillsToolPolicyMode } from '../config/schema.js';
|
|
17
18
|
import { pickGlyphs } from '../ui/glyphs.js';
|
|
@@ -92,12 +93,96 @@ async function runSkills(ctx, service, version) {
|
|
|
92
93
|
return unloadFrames(ctx, service);
|
|
93
94
|
case 'usage':
|
|
94
95
|
return usageFromCommand(ctx, service, argument);
|
|
96
|
+
case 'triage':
|
|
97
|
+
return triageFromCommand(ctx, service, argument);
|
|
98
|
+
case 'route':
|
|
99
|
+
return routeFromCommand(ctx, service, argument);
|
|
95
100
|
default:
|
|
96
101
|
ctx.notify('warn', `Unknown subcommand "/skills ${sub}". Try: list, info, install, update, remove, enable, ` +
|
|
97
|
-
'disable, reload, create, trust, untrust, policy, unload, usage.');
|
|
102
|
+
'disable, reload, create, trust, untrust, policy, unload, usage, triage, route.');
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
// ---------------------------------------------------------------------------
|
|
106
|
+
// /skills triage · route (skill-triage-and-unbounded-ask §4.2.8)
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
/**
|
|
109
|
+
* `/skills triage [on|off]`.
|
|
110
|
+
*
|
|
111
|
+
* The report names the switch AND the last turn's result, because "on" alone does
|
|
112
|
+
* not answer the question a user actually has, which is "is it finding anything?".
|
|
113
|
+
* `lastSkillRouting` comes from the controller — it is per-turn session state, not
|
|
114
|
+
* skill-service state, and the service must not start owning a copy of it.
|
|
115
|
+
*/
|
|
116
|
+
function triageFromCommand(ctx, service, argument) {
|
|
117
|
+
const requested = argument.trim().toLowerCase();
|
|
118
|
+
if (requested.length === 0) {
|
|
119
|
+
const on = service.getConfig().triage === true;
|
|
120
|
+
const last = ctx.controller.getSkillRouting();
|
|
121
|
+
const summary = last === null
|
|
122
|
+
? 'No turn has been triaged yet.'
|
|
123
|
+
: last.candidates.length === 0
|
|
124
|
+
? `Last turn: no candidate (${last.eligible} eligible skill(s) scored).`
|
|
125
|
+
: `Last turn: ${last.candidates.length} candidate(s), best ${last.candidates[0].name} (${last.candidates[0].score}).`;
|
|
126
|
+
ctx.notify('info', `Skill triage: ${on ? 'on' : 'off'}. ${summary}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (requested !== 'on' && requested !== 'off') {
|
|
130
|
+
ctx.notify('warn', `Unknown argument "${argument}" - use /skills triage [on|off].`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const enabled = requested === 'on';
|
|
134
|
+
// `setTriageEnabled` persists AND fires `onChange`, which rebuilds the prompt in
|
|
135
|
+
// this session. Without that second half the switch would look applied and take
|
|
136
|
+
// effect only after a restart (AC-T23).
|
|
137
|
+
service.setTriageEnabled(enabled);
|
|
138
|
+
// A toast, not a transcript notice: flipping a switch is an acknowledgement, and
|
|
139
|
+
// `toast` is this package's channel for exactly that (see `toggleSkill`). The
|
|
140
|
+
// level still differs by direction, so "off" does not read as a win.
|
|
141
|
+
ctx.toast(enabled ? 'success' : 'info', `Skill triage: ${enabled ? 'on' : 'off'}. ` +
|
|
142
|
+
(enabled
|
|
143
|
+
? 'Each new task gets a short candidate list.'
|
|
144
|
+
: 'The system prompt no longer carries the triage block.'));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `/skills route [text]`.
|
|
148
|
+
*
|
|
149
|
+
* Two modes, one output shape. With text it routes that text; without text it
|
|
150
|
+
* replays the previous turn's real routing, which is the case a user is most often
|
|
151
|
+
* in — they just watched the model ignore a suggestion. The "never triaged" text
|
|
152
|
+
* names the form that works instead of leaving them to guess.
|
|
153
|
+
*/
|
|
154
|
+
function routeFromCommand(ctx, service, argument) {
|
|
155
|
+
const task = argument.trim();
|
|
156
|
+
if (task.length === 0) {
|
|
157
|
+
const last = ctx.controller.getSkillRouting();
|
|
158
|
+
if (last === null) {
|
|
159
|
+
ctx.notify('info', 'No turn has been triaged yet. Run /skills route <text> to try one.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
ctx.notify('info', renderRoutingReport(last));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// A fresh computation, NOT the cached one: `/skills route <text>` is how a user
|
|
166
|
+
// tests whether a phrasing would have matched.
|
|
167
|
+
ctx.notify('info', renderRoutingReport(service.route(task)));
|
|
168
|
+
}
|
|
169
|
+
/** One `info` notification, one line per candidate, ASCII only (`commands/`). */
|
|
170
|
+
function renderRoutingReport(report) {
|
|
171
|
+
if (report.candidates.length === 0) {
|
|
172
|
+
const close = report.topUnqualified === null
|
|
173
|
+
? ''
|
|
174
|
+
: ` Closest was ${report.topUnqualified.name} (${report.topUnqualified.score}); ` +
|
|
175
|
+
`the threshold is ${SKILL_TRIAGE_LIMITS.minScore}.`;
|
|
176
|
+
return (`No skill matched "${report.task}". The catalog has ${report.eligible} eligible skills.` +
|
|
177
|
+
close);
|
|
178
|
+
}
|
|
179
|
+
const lines = report.candidates.map((candidate) => {
|
|
180
|
+
const reasons = candidate.reasons.length > 0 ? candidate.reasons.join('+') : 'none';
|
|
181
|
+
return `${candidate.name} (${candidate.scope}) score ${candidate.score} [${reasons}]`;
|
|
182
|
+
});
|
|
183
|
+
return [`Routed "${report.task}" (${report.tokenCount} token(s)):`, ...lines].join('\n');
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
101
186
|
// /skills policy · unload · usage (§5.5 / §9)
|
|
102
187
|
// ---------------------------------------------------------------------------
|
|
103
188
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/subagent` — force the next turn to fan out into a parallel team
|
|
3
|
+
* (subagent-fanout 3.5 / 5.2).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/commands/**` is inside `glyphs.test.ts::inScope`.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THIS COMMAND IS AND IS NOT (D-8 / D-9 / D-10). It does not dispatch: it
|
|
8
|
+
* parses the user's intent, refuses honestly when forcing cannot work, prints
|
|
9
|
+
* ONE line saying what will happen next, and submits a normal user turn. The
|
|
10
|
+
* lead then calls `task` on the one dispatch path that already exists, so Esc,
|
|
11
|
+
* the watchdog pause, the report, spend accounting and the concurrency lock all
|
|
12
|
+
* keep working without a second implementation. The consequence the user must
|
|
13
|
+
* be told about is that forcing is INSTRUCTION-LEVEL: the notice says "the next
|
|
14
|
+
* turn dispatches them", never "N subagents started".
|
|
15
|
+
*
|
|
16
|
+
* THE GUARD ORDER IS A CONTRACT (R-P1-10 / R-P1-11): status -> capability ->
|
|
17
|
+
* parse. A `--no-team` session typing `/subagent -n x` is told the session has
|
|
18
|
+
* no team capability, because a syntax hint would send them to fix the one thing
|
|
19
|
+
* that is not wrong.
|
|
20
|
+
*/
|
|
21
|
+
import type { CommandContext, CommandRegistry } from './registry.js';
|
|
22
|
+
/** The command body; `registerSubagentCommand` is the only registration site. */
|
|
23
|
+
export declare function runSubagentCommand(ctx: CommandContext): void;
|
|
24
|
+
/** Registered from `builtins.ts` with one line, next to every other subsystem. */
|
|
25
|
+
export declare function registerSubagentCommand(registry: CommandRegistry): void;
|