@zq-silk/yui 0.7.0 → 0.8.1

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.
Files changed (49) hide show
  1. package/README.md +42 -23
  2. package/dist/cli/commandCatalog.js +234 -122
  3. package/dist/cli/completion.js +3 -3
  4. package/dist/cli/helpRenderer.js +3 -0
  5. package/dist/cli/interactionPolicy.js +44 -23
  6. package/dist/cli/interactiveSelection.js +1 -1
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/roleWizard.js +8 -8
  9. package/dist/cli.js +116 -72
  10. package/dist/commands/agentCommands.js +5 -5
  11. package/dist/commands/configCommands.js +351 -104
  12. package/dist/commands/configOverview.js +60 -0
  13. package/dist/commands/deliveryGuardPreflight.js +2 -2
  14. package/dist/commands/globalRoleCommands.js +9 -9
  15. package/dist/commands/profileCommands.js +8 -8
  16. package/dist/commands/resourcesCommands.js +6 -5
  17. package/dist/commands/taskCommands.js +3 -6
  18. package/dist/commands/taskRoleRuntimeStatus.js +3 -1
  19. package/dist/commands/telemetryCommands.js +11 -6
  20. package/dist/config/configCatalog.js +42 -0
  21. package/dist/config/yuiConfig.js +80 -35
  22. package/dist/context/sessionBootstrapManifest.js +1 -1
  23. package/dist/controller/clientRuntime.js +0 -2
  24. package/dist/controller/controller.js +21 -9
  25. package/dist/controller/fileSchedulerStoreAdapter.js +20 -9
  26. package/dist/controller/runtime.js +32 -18
  27. package/dist/coordination/workMailbox.js +25 -22
  28. package/dist/doctor/doctor.js +2 -2
  29. package/dist/resources/autoResourceGc.js +3 -1
  30. package/dist/review/reviewConfig.js +0 -2
  31. package/dist/run/providerRetry.js +29 -16
  32. package/dist/run/providerRetryConfig.js +5 -3
  33. package/dist/runtime/launchDiagnostics.js +1 -1
  34. package/dist/scheduler/roleRunStall.js +12 -9
  35. package/dist/setup/setupCommand.js +153 -492
  36. package/dist/storage/compatibleTaskStore.js +9 -5
  37. package/dist/storage/migration/productionRegistry.js +58 -0
  38. package/dist/storage/sqliteStore.js +9 -2
  39. package/dist/storage/taskStore.js +21 -2
  40. package/dist/telemetry/sqliteTelemetryStore.js +9 -1
  41. package/dist/telemetry/telemetryConfig.js +1 -18
  42. package/dist/telemetry/telemetryStore.js +2 -2
  43. package/dist/telemetry/telemetryWiring.js +6 -5
  44. package/dist/web/webSnapshot.js +5 -3
  45. package/i18n/README.zh-CN.md +37 -32
  46. package/package.json +1 -1
  47. package/skills/yui-leader/SKILL.md +12 -5
  48. package/skills/yui-operator/SKILL.md +44 -6
  49. package/skills/yui-runtime/SKILL.md +1 -1
@@ -1,3 +1,4 @@
1
+ import { findCommandNode } from "./commandCatalog.js";
1
2
  const taskTarget = (command, argumentIndex = 2, statuses) => ({
2
3
  commandPath: ["task", command],
3
4
  selectors: [{
@@ -47,7 +48,7 @@ export const INTERACTION_POLICIES = Object.freeze([
47
48
  "--development": "value"
48
49
  }
49
50
  },
50
- ...["add", "update", "retire", "list", "show"].map((command) => ({
51
+ ...["add", "retire", "list", "show"].map((command) => ({
51
52
  commandPath: ["project", "knowledge", command],
52
53
  selectors: [{
53
54
  argumentIndex: 3,
@@ -57,26 +58,24 @@ export const INTERACTION_POLICIES = Object.freeze([
57
58
  }],
58
59
  ...(command === "add"
59
60
  ? { trailingOptions: { "--body": "value" } }
60
- : command === "update"
61
- ? { trailingOptions: { "--title": "value", "--body": "value" } }
62
- : command === "list"
63
- ? { trailingOptions: { "--all": "flag" } }
64
- : {})
61
+ : command === "list"
62
+ ? { trailingOptions: { "--all": "flag" } }
63
+ : {})
65
64
  })),
66
65
  ...["show", "capabilities", "update", "remove"].map((command) => ({
67
- commandPath: ["agent", command],
66
+ commandPath: ["config", "agent", command],
68
67
  selectors: [{
69
- argumentIndex: 2,
68
+ argumentIndex: 3,
70
69
  entity: "agent",
71
70
  provider: "configured-agents",
72
71
  actionTarget: true
73
72
  }],
74
73
  ...(command === "remove"
75
- ? { confirmation: { action: "Remove Agent", targetArgumentIndex: 2 } }
74
+ ? { confirmation: { action: "Remove Agent", targetArgumentIndex: 3 } }
76
75
  : {})
77
76
  })),
78
77
  {
79
- commandPath: ["role", "add"],
78
+ commandPath: ["config", "role", "add"],
80
79
  selectors: [{
81
80
  option: "--agent",
82
81
  requiredOption: true,
@@ -93,29 +92,31 @@ export const INTERACTION_POLICIES = Object.freeze([
93
92
  "--search": "value"
94
93
  }
95
94
  },
96
- ...["show", "update", "remove", "enter"].map((command) => ({
97
- commandPath: ["role", command],
95
+ ...["show", "update", "remove", "enter", "context"].map((command) => ({
96
+ commandPath: command === "enter" || command === "context"
97
+ ? ["session", command]
98
+ : ["config", "role", command],
98
99
  selectors: [{
99
- argumentIndex: 2,
100
+ argumentIndex: command === "enter" || command === "context" ? 2 : 3,
100
101
  entity: "global-role",
101
102
  provider: "global-roles",
102
103
  actionTarget: true
103
104
  }],
104
105
  ...(command === "remove"
105
- ? { confirmation: { action: "Remove Role", targetArgumentIndex: 2 } }
106
+ ? { confirmation: { action: "Remove Role", targetArgumentIndex: 3 } }
106
107
  : {})
107
108
  })),
108
109
  ...["bind", "unbind"].map((command) => ({
109
- commandPath: ["role", command],
110
+ commandPath: ["config", "role", command],
110
111
  selectors: [
111
- { argumentIndex: 2, entity: "global-role", provider: "global-roles", actionTarget: true },
112
- { argumentIndex: 3, entity: "agent", provider: "configured-agents", actionTarget: false }
112
+ { argumentIndex: 3, entity: "global-role", provider: "global-roles", actionTarget: true },
113
+ { argumentIndex: 4, entity: "agent", provider: "configured-agents", actionTarget: false }
113
114
  ]
114
115
  })),
115
116
  ...["record", "replace"].map((command) => ({
116
- commandPath: ["role", "session", command],
117
+ commandPath: ["session", command],
117
118
  selectors: [{
118
- argumentIndex: 3,
119
+ argumentIndex: 2,
119
120
  entity: "global-role",
120
121
  provider: "global-roles",
121
122
  actionTarget: true
@@ -125,7 +126,7 @@ export const INTERACTION_POLICIES = Object.freeze([
125
126
  : { "--native-id": "value" }
126
127
  })),
127
128
  {
128
- commandPath: ["profile", "add"],
129
+ commandPath: ["config", "profile", "add"],
129
130
  selectors: [],
130
131
  trailingOptions: {
131
132
  "--description": "value", "--instructions": "value",
@@ -133,9 +134,9 @@ export const INTERACTION_POLICIES = Object.freeze([
133
134
  }
134
135
  },
135
136
  ...["show", "update", "remove"].map((command) => ({
136
- commandPath: ["profile", command],
137
+ commandPath: ["config", "profile", command],
137
138
  selectors: [{
138
- argumentIndex: 2,
139
+ argumentIndex: 3,
139
140
  entity: "agent-profile",
140
141
  provider: "agent-profiles",
141
142
  actionTarget: true
@@ -151,7 +152,7 @@ export const INTERACTION_POLICIES = Object.freeze([
151
152
  }
152
153
  : {}),
153
154
  ...(command === "remove"
154
- ? { confirmation: { action: "Remove Agent Profile", targetArgumentIndex: 2 } }
155
+ ? { confirmation: { action: "Remove Agent Profile", targetArgumentIndex: 3 } }
155
156
  : {})
156
157
  })),
157
158
  taskTarget("show"),
@@ -630,10 +631,30 @@ export function validateInteractionPolicies(policies = INTERACTION_POLICIES) {
630
631
  if (paths.has(path))
631
632
  throw new Error(`Duplicate interaction policy: ${path}`);
632
633
  paths.add(path);
634
+ const node = findCommandNode(policy.commandPath);
635
+ if (node === undefined || node.hidden) {
636
+ throw new Error(`Interaction policy references an unknown command: ${path}`);
637
+ }
633
638
  for (const selector of policy.selectors) {
634
639
  if ((selector.argumentIndex === undefined) === (selector.option === undefined)) {
635
640
  throw new Error(`Interaction selector must declare exactly one slot: ${path}`);
636
641
  }
642
+ if (selector.argumentIndex !== undefined && (!Number.isInteger(selector.argumentIndex)
643
+ || selector.argumentIndex < node.path.length - 1)) {
644
+ throw new Error(`Interaction selector has an invalid argument index: ${path}`);
645
+ }
646
+ if (selector.option !== undefined && !node.options.includes(selector.option)) {
647
+ throw new Error(`Interaction selector references an unknown option: ${path} ${selector.option}`);
648
+ }
649
+ }
650
+ for (const option of Object.keys(policy.trailingOptions ?? {})) {
651
+ if (!node.options.includes(option)) {
652
+ throw new Error(`Interaction policy references an unknown trailing option: ${path} ${option}`);
653
+ }
654
+ }
655
+ if (policy.confirmation !== undefined && !policy.selectors.some((selector) => selector.argumentIndex === policy.confirmation?.targetArgumentIndex
656
+ && selector.actionTarget)) {
657
+ throw new Error(`Interaction confirmation has no action target selector: ${path}`);
637
658
  }
638
659
  }
639
660
  }
@@ -53,7 +53,7 @@ export async function resolveInteractiveArguments(args, node, ports, io) {
53
53
  }
54
54
  export function allowsInteractiveSelection(args, globalJson) {
55
55
  return !globalJson && !args.includes("--json")
56
- && !(args[0] === "completion" && args[1] === "candidates");
56
+ && !(args[0] === "config" && args[1] === "completion" && args[2] === "candidates");
57
57
  }
58
58
  function missingSlot(args, selector, node) {
59
59
  if (selector.argumentIndex !== undefined) {
@@ -31,7 +31,7 @@ function resolveExecutionPath(args) {
31
31
  break;
32
32
  const child = findChild(node, args[index] ?? "");
33
33
  const internalExecutable = child !== undefined && ((node === ROOT_COMMAND && child.name === "internal")
34
- || (node.path.join(" ") === "yui completion" && child.name === "candidates")
34
+ || (node.path.join(" ") === "yui config completion" && child.name === "candidates")
35
35
  || (node.path.join(" ") === "yui task run" && child.name === "checkpoint")
36
36
  || (node.path.join(" ") === "yui controller"
37
37
  && (child.name === "identity" || child.name === "live-identity")));
@@ -20,7 +20,7 @@ export async function resolveRoleWizardArguments(commandArgs, ports, io) {
20
20
  return { kind: "unchanged", args };
21
21
  }
22
22
  export async function resolveGlobalRoleAgentConfigurationArguments(roleName, agentId, ports, io) {
23
- const args = ["role", "update", roleName];
23
+ const args = ["config", "role", "update", roleName];
24
24
  if (!io.interactive || io.json)
25
25
  return { kind: "unchanged", args };
26
26
  const role = asRole(await ports.call("role.show", { name: roleName }));
@@ -31,7 +31,7 @@ export async function resolveGlobalRoleAgentConfigurationArguments(roleName, age
31
31
  return updateAgentBindingSettings(args, role, binding, ports, io);
32
32
  }
33
33
  function isGlobalRoleAdd(args) {
34
- return args[0] === "role" && args[1] === "add";
34
+ return args[0] === "config" && args[1] === "role" && args[2] === "add";
35
35
  }
36
36
  function isTaskRoleAdd(args) {
37
37
  return args[0] === "task" && args[1] === "role" && args[2] === "add";
@@ -39,18 +39,18 @@ function isTaskRoleAdd(args) {
39
39
  // Explicit update options always win. This keeps scripts and deliberate
40
40
  // non-interactive invocations out of the wizard even when run from a TTY.
41
41
  function isGlobalRoleUpdate(args) {
42
- return args.length === 3 && args[0] === "role" && args[1] === "update"
43
- && isValue(args[2]);
42
+ return args.length === 4 && args[0] === "config" && args[1] === "role"
43
+ && args[2] === "update" && isValue(args[3]);
44
44
  }
45
45
  function isTaskRoleUpdate(args) {
46
46
  return args.length === 5 && args[0] === "task" && args[1] === "role"
47
47
  && args[2] === "update" && isValue(args[3]) && isValue(args[4]);
48
48
  }
49
49
  async function addGlobalRole(args, ports, io) {
50
- const named = await ensureRoleName(args, 2, io);
50
+ const named = await ensureRoleName(args, 3, io);
51
51
  if (named === undefined)
52
52
  return { kind: "cancelled", args };
53
- const explicitSettings = hasExplicitAddSettings(named, 3);
53
+ const explicitSettings = hasExplicitAddSettings(named, 4);
54
54
  const agents = await loadAgentSelection(ports);
55
55
  const explicitAgent = optionValue(named, "--agent");
56
56
  const selected = explicitAgent ?? await selectConfiguredAgent(ports, io, "Create Global Role from Agent", agents);
@@ -236,7 +236,7 @@ async function configureNewAgentField(args, agent, ports, io) {
236
236
  : { kind: "resolved", args: [...args, selected.set, value] };
237
237
  }
238
238
  async function updateGlobalRole(args, ports, io) {
239
- const role = asRole(await ports.call("role.show", { name: args[2] }));
239
+ const role = asRole(await ports.call("role.show", { name: args[3] }));
240
240
  if (role === undefined)
241
241
  return { kind: "unchanged", args };
242
242
  return updateRole(args, role, ports, io, false);
@@ -286,7 +286,7 @@ async function updateRoleSettings(args, role, ports, io, taskRole) {
286
286
  return { kind: "cancelled", args };
287
287
  return taskRole
288
288
  ? { kind: "resolved", args: ["task", "role", "bind", args[3] ?? "", args[4] ?? "", target] }
289
- : { kind: "resolved", args: ["role", "bind", args[2] ?? "", target] };
289
+ : { kind: "resolved", args: ["config", "role", "bind", args[3] ?? "", target] };
290
290
  }
291
291
  if (field === "workspace") {
292
292
  const value = (await io.question(`Workspace [${roleDisplay(role.workspace)}]: `))?.trim();
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { createInterface } from "node:readline/promises";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { isDeepStrictEqual } from "node:util";
7
7
  import { renderCommandHelp } from "./cli/helpRenderer.js";
8
+ import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
8
9
  import { routeInvocation } from "./cli/invocationRouter.js";
9
10
  import { renderCompletion } from "./cli/completion.js";
10
11
  import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
@@ -16,12 +17,14 @@ import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
16
17
  import { runUpdateCommand } from "./cli/updateCommand.js";
17
18
  import { runUpgradeCommand } from "./cli/upgradeCommand.js";
18
19
  import { formatTimestamp } from "./output/timePresentation.js";
19
- import { resolveTmuxBin } from "./config/yuiConfig.js";
20
+ import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
20
21
  import { renderAgentConfigurationCatalog } from "./output/agentConfigurationPresentation.js";
21
22
  import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
22
23
  import { runAgentCommand } from "./commands/agentCommands.js";
23
24
  import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
24
25
  import { runConfigCommand } from "./commands/configCommands.js";
26
+ import { CONFIG_DOMAINS } from "./config/configCatalog.js";
27
+ import { runConfigOverview } from "./commands/configOverview.js";
25
28
  import { parseControllerCleanupOptions, parseControllerStatusOptions, parseControllerRuntimeSnapshot, renderControllerResourceStatus, renderRuntimeIdentitySection, summarizeDurablePhysicalMismatch, runInteractiveControllerCleanup } from "./commands/controllerCommands.js";
26
29
  import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
27
30
  import { parseSessionReconcileOptions, runSessionReconcileCommand } from "./commands/sessionCommands.js";
@@ -168,10 +171,25 @@ export async function main() {
168
171
  }
169
172
  throw usageError("Release usage: yui release install <source-dir> | list | activate [release-id].");
170
173
  }
171
- if (args[0] === "completion") {
174
+ if (args[0] === "config" && args[1] === "completion") {
172
175
  await completionCommand(home, invocation.node);
173
176
  return;
174
177
  }
178
+ if (args[0] === "config" && args[1] === "describe") {
179
+ const describeNode = findCommandNode(["config", "describe"]);
180
+ if (describeNode === undefined)
181
+ throw new Error("Config describe command is missing from the catalog.");
182
+ const domain = args[2];
183
+ const domains = describeNode.argumentValues[0] ?? [];
184
+ if (args.length > 3 || (domain !== undefined && !domains.includes(domain))) {
185
+ throw usageError(`Config describe usage: ${describeNode.usage.join(" | ")}.`);
186
+ }
187
+ const target = findCommandNode(domain === undefined ? ["config"] : ["config", domain]);
188
+ if (target === undefined)
189
+ throw new Error(`Config domain is missing from the catalog: ${domain}.`);
190
+ emit(renderCommandHelp(target, VERSION), false, describeCommandTree(target));
191
+ return;
192
+ }
175
193
  if (args[0] === "setup") {
176
194
  if (jsonOutput)
177
195
  throw usageError("Setup does not support --json.");
@@ -261,13 +279,13 @@ export async function main() {
261
279
  }
262
280
  throw usageError("Internal lifecycle callback usage is invalid.");
263
281
  }
264
- if (args[0] === "session") {
265
- if (args[1] !== "reconcile") {
266
- throw usageError("Session usage: yui session reconcile [--report] [--cleanup].");
267
- }
282
+ if (args[0] === "session" && args[1] === "reconcile") {
268
283
  const options = parseSessionReconcileOptions(args.slice(2));
269
284
  const store = openCompatibleFileTaskStore(home);
270
- const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), new NodeCommandExecutor(), { yuiHome: home });
285
+ const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), new NodeCommandExecutor(), {
286
+ yuiHome: home,
287
+ historyLimit: resolveTmuxHistoryLimit(store.getConfig().tmuxHistoryLimit)
288
+ });
271
289
  const reconciliation = new SessionOwnerReconciliation({
272
290
  home,
273
291
  store,
@@ -474,6 +492,7 @@ export async function main() {
474
492
  const executor = new NodeCommandExecutor();
475
493
  const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), executor, {
476
494
  yuiHome: home,
495
+ historyLimit: resolveTmuxHistoryLimit(store.getConfig().tmuxHistoryLimit),
477
496
  terminalInput: process.stdin,
478
497
  onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
479
498
  });
@@ -531,77 +550,102 @@ export async function main() {
531
550
  process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
532
551
  return;
533
552
  }
534
- if (resolved[0] === "agent") {
535
- const agentArgs = resolved.slice(1);
536
- if (agentArgs[0] === "capabilities") {
537
- if (agentArgs.length !== 2) {
538
- throw usageError("Agent capabilities usage: yui agent capabilities <agent-id>");
553
+ if (resolved[0] === "config") {
554
+ const domain = resolved[1];
555
+ const roleOptions = {
556
+ yuiHome: home,
557
+ env: process.env
558
+ };
559
+ if (domain === "show") {
560
+ const result = runConfigOverview(resolved.slice(2), store, process.env, resolveCliIdentity(process.env), roleOptions);
561
+ emit(result.output, false, result.data);
562
+ return;
563
+ }
564
+ if (CONFIG_DOMAINS.includes(domain ?? "")) {
565
+ const configDomain = domain;
566
+ const domainArgs = resolved.slice(2);
567
+ const result = runConfigCommand(configDomain, domainArgs, store);
568
+ if (domainArgs[0] === "set"
569
+ && domainArgs[1] === "reconciliation-interval-seconds") {
570
+ const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
571
+ emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
572
+ return;
539
573
  }
540
- const agent = store.getConfiguredAgent(agentArgs[1] ?? "");
541
- if (agent === null)
542
- throw agentNotFound(agentArgs[1] ?? "");
543
- const result = await catalogs.resolve({
544
- agent,
545
- cwd: store.getConfig().defaultWorkspace ?? process.cwd()
546
- });
547
- emit(renderAgentConfigurationCatalog(result), false, result);
574
+ emit(result.output, false, result.data);
548
575
  return;
549
576
  }
550
- const affectedAgentId = agentArgs[1];
551
- const previousAgent = typeof affectedAgentId === "string"
552
- ? store.getConfiguredAgent(affectedAgentId)
553
- : null;
554
- const output = runAgentCommand(agentArgs, store);
555
- if (agentArgs[0] === "add"
556
- || agentArgs[0] === "update"
557
- || agentArgs[0] === "remove") {
558
- const currentAgent = typeof affectedAgentId === "string"
577
+ if (domain === "agent") {
578
+ const agentArgs = resolved.slice(2);
579
+ if (agentArgs[0] === "capabilities") {
580
+ if (agentArgs.length !== 2) {
581
+ throw usageError("Agent capabilities usage: yui config agent capabilities <agent-id>");
582
+ }
583
+ const agent = store.getConfiguredAgent(agentArgs[1] ?? "");
584
+ if (agent === null)
585
+ throw agentNotFound(agentArgs[1] ?? "");
586
+ const result = await catalogs.resolve({
587
+ agent,
588
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd()
589
+ });
590
+ emit(renderAgentConfigurationCatalog(result), false, result);
591
+ return;
592
+ }
593
+ const affectedAgentId = agentArgs[1];
594
+ const previousAgent = typeof affectedAgentId === "string"
559
595
  ? store.getConfiguredAgent(affectedAgentId)
560
596
  : null;
561
- const capabilityNotice = currentAgent !== null
562
- && (agentArgs[0] === "add" || agentArgs[0] === "update")
563
- ? renderAgentConfigurationResolutionNotice(await catalogs.resolve({
564
- agent: currentAgent,
565
- cwd: store.getConfig().defaultWorkspace ?? process.cwd()
566
- }))
567
- : "";
568
- const scope = agentEnvironmentRefreshScope(previousAgent, currentAgent, store.listConfiguredAgents());
569
- const refresh = await refreshRunningFileTaskControllerEnvironment(home, store, process.env, scope);
570
- emit(withControllerRefreshWarning(`${output.trimEnd()}${capabilityNotice.length === 0 ? "\n" : `\n${capabilityNotice}`}`, refresh, "Agent environment"));
597
+ const output = runAgentCommand(agentArgs, store);
598
+ if (agentArgs[0] === "add"
599
+ || agentArgs[0] === "update"
600
+ || agentArgs[0] === "remove") {
601
+ const currentAgent = typeof affectedAgentId === "string"
602
+ ? store.getConfiguredAgent(affectedAgentId)
603
+ : null;
604
+ const capabilityNotice = currentAgent !== null
605
+ && (agentArgs[0] === "add" || agentArgs[0] === "update")
606
+ ? renderAgentConfigurationResolutionNotice(await catalogs.resolve({
607
+ agent: currentAgent,
608
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd()
609
+ }))
610
+ : "";
611
+ const scope = agentEnvironmentRefreshScope(previousAgent, currentAgent, store.listConfiguredAgents());
612
+ const refresh = await refreshRunningFileTaskControllerEnvironment(home, store, process.env, scope);
613
+ emit(withControllerRefreshWarning(`${output.trimEnd()}${capabilityNotice.length === 0 ? "\n" : `\n${capabilityNotice}`}`, refresh, "Agent environment"));
614
+ return;
615
+ }
616
+ emit(output);
571
617
  return;
572
618
  }
573
- emit(output);
574
- return;
575
- }
576
- if (resolved[0] === "config") {
577
- const configArgs = resolved.slice(1);
578
- const result = runConfigCommand(configArgs, store);
579
- if (configArgs[0] === "set"
580
- && configArgs[1] === "reconciliation-interval-seconds") {
581
- const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
582
- emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
619
+ if (domain === "profile") {
620
+ const result = runProfileCommand(resolved.slice(2), store);
621
+ emit(result.output, false, result.data);
583
622
  return;
584
623
  }
585
- emit(result.output, false, result.data);
586
- return;
624
+ if (domain === "role") {
625
+ const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
626
+ if (typeof result !== "string") {
627
+ throw new Error("Config Role commands cannot enter a runtime Session.");
628
+ }
629
+ emit(result);
630
+ return;
631
+ }
632
+ throw usageError(`Unknown configuration domain: ${domain ?? ""}.`);
587
633
  }
588
634
  if (resolved[0] === "project") {
589
635
  const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
590
636
  emit(result.output, false, result.data);
591
637
  return;
592
638
  }
593
- if (resolved[0] === "profile") {
594
- const result = runProfileCommand(resolved.slice(1), store);
595
- emit(result.output, false, result.data);
596
- return;
597
- }
598
- if (resolved[0] === "role") {
639
+ if (resolved[0] === "session") {
599
640
  const roleOptions = {
600
641
  yuiHome: home,
601
642
  env: process.env,
602
643
  jsonOutput
603
644
  };
604
- const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
645
+ const sessionArgs = resolved[1] === "enter" || resolved[1] === "context"
646
+ ? [resolved[1], ...resolved.slice(2)]
647
+ : ["session", resolved[1] ?? "", ...resolved.slice(2)];
648
+ const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
605
649
  if (typeof result === "string") {
606
650
  emit(result);
607
651
  return;
@@ -1659,7 +1703,7 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
1659
1703
  const config = store.getReviewConfig();
1660
1704
  if (!deltaRecheckEnabled(config)) {
1661
1705
  throw usageError("Delta-recheck is not enabled for this Project's review policy. "
1662
- + "Set `yui config set review --role <role> --trigger final --delta-recheck enabled` "
1706
+ + "Set `yui config workflow set review --role <role> --trigger final --delta-recheck enabled` "
1663
1707
  + "or request a full Review.");
1664
1708
  }
1665
1709
  const reviewConfig = config;
@@ -1809,7 +1853,7 @@ async function executeOperatorSessionControl(control, home, store, runtime, tmux
1809
1853
  process.stdout.write("Cancelled.\n");
1810
1854
  return;
1811
1855
  }
1812
- const updated = runGlobalRoleCommand(resolution.args.slice(1), store, { yuiHome: home, env: process.env });
1856
+ const updated = runGlobalRoleCommand(resolution.args.slice(2), store, { yuiHome: home, env: process.env });
1813
1857
  if (typeof updated !== "string") {
1814
1858
  throw new Error("Operator Agent configuration returned an invalid control result.");
1815
1859
  }
@@ -1865,11 +1909,11 @@ function renderControllerResult(method, value) {
1865
1909
  : "Controller was already stopped.";
1866
1910
  }
1867
1911
  async function completionCommand(home, node) {
1868
- if (args[1] === "candidates") {
1869
- const separator = args.indexOf("--");
1870
- const prefix = args[2];
1871
- if (prefix === undefined || separator !== 3) {
1872
- throw usageError("Completion candidates usage: yui completion candidates <prefix> -- <words...>");
1912
+ if (args[2] === "candidates") {
1913
+ const separator = args.indexOf("--", 4);
1914
+ const prefix = args[3];
1915
+ if (prefix === undefined || separator !== 4) {
1916
+ throw usageError("Completion candidates usage: yui config completion candidates <prefix> -- <words...>");
1873
1917
  }
1874
1918
  const candidates = await resolveCompletionCandidates({
1875
1919
  current: prefix,
@@ -1884,9 +1928,9 @@ async function completionCommand(home, node) {
1884
1928
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
1885
1929
  throw usageError("Completion configuration requires an interactive terminal.");
1886
1930
  }
1887
- const shell = completionShell(args[1]);
1888
- if (args.length > (shell === undefined ? 1 : 2)) {
1889
- throw usageError("Completion usage: yui completion [bash|zsh|fish]");
1931
+ const shell = completionShell(args[2]);
1932
+ if (args.length > (shell === undefined ? 2 : 3)) {
1933
+ throw usageError("Completion usage: yui config completion [bash|zsh|fish]");
1890
1934
  }
1891
1935
  const store = openCompatibleFileTaskStore(home);
1892
1936
  const ioHandle = terminalIo();
@@ -1921,7 +1965,7 @@ async function resolveTerminalArguments(commandArgs, node, store, catalogs) {
1921
1965
  // Global Role add owns its Agent choice so the configured default can be
1922
1966
  // shown explicitly. Other commands first resolve missing positional
1923
1967
  // targets through the generic selector, then enter the focused Role UI.
1924
- if ((operatorArgs[0] === "role" && operatorArgs[1] === "add")
1968
+ if ((operatorArgs[0] === "config" && operatorArgs[1] === "role" && operatorArgs[2] === "add")
1925
1969
  || (operatorArgs[0] === "task" && operatorArgs[1] === "role" && operatorArgs[2] === "add")) {
1926
1970
  const wizard = await resolveRoleWizardArguments(operatorArgs, ports, handle.io);
1927
1971
  if (wizard.kind === "cancelled")
@@ -1983,7 +2027,7 @@ async function preflightAgentConfigurationMutation(commandArgs, store, catalogs)
1983
2027
  });
1984
2028
  }
1985
2029
  function hasModelOrEffortMutation(args) {
1986
- const operation = args[0] === "role"
2030
+ const operation = (args[0] === "config" && args[1] === "role")
1987
2031
  || (args[0] === "task" && args[1] === "role");
1988
2032
  return operation && [
1989
2033
  "--model", "--effort", "--clear-model", "--clear-effort"
@@ -1993,8 +2037,8 @@ function configurationMutationAgentId(args, store) {
1993
2037
  const explicit = optionValue(args, "--agent");
1994
2038
  if (explicit !== undefined)
1995
2039
  return explicit;
1996
- if (args[0] === "role" && args[1] === "update") {
1997
- return store.getGlobalRole(args[2] ?? "")?.activeAgentId;
2040
+ if (args[0] === "config" && args[1] === "role" && args[2] === "update") {
2041
+ return store.getGlobalRole(args[3] ?? "")?.activeAgentId;
1998
2042
  }
1999
2043
  if (args[0] === "task" && args[1] === "role") {
2000
2044
  if (args[2] === "add")
@@ -15,7 +15,7 @@ export function runAgentCommand(args, store) {
15
15
  default:
16
16
  throw usageError(command === undefined
17
17
  ? "Agent command is required."
18
- : `Unknown command: agent ${command}`);
18
+ : `Unknown command: config agent ${command}`);
19
19
  }
20
20
  }
21
21
  function addAgent(args, store) {
@@ -38,12 +38,12 @@ function addAgent(args, store) {
38
38
  }
39
39
  const created = store.createConfiguredAgentIfAbsent(agent);
40
40
  if (created === null) {
41
- throw usageError(`Agent already exists: ${id}. Use yui agent update to change it.`);
41
+ throw usageError(`Agent already exists: ${id}. Use yui config agent update to change it.`);
42
42
  }
43
43
  return renderAgent(`Added agent ${id}`, created);
44
44
  }
45
45
  function listAgents(args, store) {
46
- assertNoArguments(args, "Agent list usage: yui agent list");
46
+ assertNoArguments(args, "Agent list usage: yui config agent list");
47
47
  const agents = store.listConfiguredAgents();
48
48
  if (agents.length === 0)
49
49
  return "No agents configured.\n";
@@ -62,7 +62,7 @@ function listAgents(args, store) {
62
62
  function showAgent(args, store) {
63
63
  const [rawId, ...rest] = args;
64
64
  const id = agentId(rawId);
65
- assertNoArguments(rest, "Agent show usage: yui agent show <agent-id>");
65
+ assertNoArguments(rest, "Agent show usage: yui config agent show <agent-id>");
66
66
  const agent = store.getConfiguredAgent(id);
67
67
  if (agent === null)
68
68
  throw agentNotFound(id);
@@ -137,7 +137,7 @@ function updateAgent(args, store) {
137
137
  function removeAgent(args, store) {
138
138
  const [rawId, ...rest] = args;
139
139
  const id = agentId(rawId);
140
- assertNoArguments(rest, "Agent remove usage: yui agent remove <agent-id>");
140
+ assertNoArguments(rest, "Agent remove usage: yui config agent remove <agent-id>");
141
141
  const removed = store.transaction((tx) => {
142
142
  if (tx.getConfiguredAgent(id) === null)
143
143
  return false;