@zq-silk/yui 0.0.0

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 (95) hide show
  1. package/ARCHITECTURE.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +211 -0
  4. package/dist/agent/adapterCatalog.js +10 -0
  5. package/dist/agent/agent.js +89 -0
  6. package/dist/agent/agentRegistry.js +10 -0
  7. package/dist/agent/argumentPolicy.js +80 -0
  8. package/dist/brief/taskBrief.js +37 -0
  9. package/dist/cli/commandCatalog.js +647 -0
  10. package/dist/cli/completion.js +111 -0
  11. package/dist/cli/completionWizard.js +143 -0
  12. package/dist/cli/dynamicCompletion.js +48 -0
  13. package/dist/cli/helpRenderer.js +32 -0
  14. package/dist/cli/interactionCandidates.js +139 -0
  15. package/dist/cli/interactionPolicy.js +389 -0
  16. package/dist/cli/interactiveSelection.js +185 -0
  17. package/dist/cli/invocationRouter.js +51 -0
  18. package/dist/cli/roleOptionCatalog.js +67 -0
  19. package/dist/cli/roleWizard.js +546 -0
  20. package/dist/cli/selectionPorts.js +1 -0
  21. package/dist/cli/updateCommand.js +22 -0
  22. package/dist/cli.js +402 -0
  23. package/dist/commands/agentCommands.js +196 -0
  24. package/dist/commands/globalRoleCommands.js +367 -0
  25. package/dist/commands/jobCommands.js +100 -0
  26. package/dist/commands/operatorCommands.js +38 -0
  27. package/dist/commands/repositoryCommands.js +86 -0
  28. package/dist/commands/roleConfiguration.js +201 -0
  29. package/dist/commands/taskCommands.js +1344 -0
  30. package/dist/commands/taskContextCommand.js +215 -0
  31. package/dist/commands/taskInputCommands.js +423 -0
  32. package/dist/commands/taskRoleRuntimeStatus.js +152 -0
  33. package/dist/completion/completionInstaller.js +168 -0
  34. package/dist/completion/completionPort.js +1 -0
  35. package/dist/completion/completionState.js +137 -0
  36. package/dist/completion/completionWizard.js +125 -0
  37. package/dist/completion/fileCompletionManager.js +51 -0
  38. package/dist/config/yuiConfig.js +17 -0
  39. package/dist/context/dispatchContext.js +74 -0
  40. package/dist/controller/clientRuntime.js +215 -0
  41. package/dist/controller/controller.js +158 -0
  42. package/dist/controller/controllerMain.js +37 -0
  43. package/dist/controller/fileSchedulerStoreAdapter.js +322 -0
  44. package/dist/controller/runtime.js +31 -0
  45. package/dist/controller/sessionNotify.js +136 -0
  46. package/dist/core/controllerClient.js +127 -0
  47. package/dist/core/controllerServer.js +269 -0
  48. package/dist/core/protocol.js +169 -0
  49. package/dist/decision/decision.js +42 -0
  50. package/dist/doctor/doctor.js +229 -0
  51. package/dist/errors/cliError.js +38 -0
  52. package/dist/event/taskEvent.js +44 -0
  53. package/dist/executor/agentAdapter.js +338 -0
  54. package/dist/executor/agentExecutor.js +144 -0
  55. package/dist/executor/executorRegistry.js +101 -0
  56. package/dist/executor/fileRoleLaunchPlanner.js +156 -0
  57. package/dist/executor/launchPlan.js +16 -0
  58. package/dist/input/inputRequest.js +326 -0
  59. package/dist/message/message.js +69 -0
  60. package/dist/milestone/milestone.js +27 -0
  61. package/dist/operator/operatorContext.js +66 -0
  62. package/dist/output/rolePresentation.js +82 -0
  63. package/dist/output/table.js +77 -0
  64. package/dist/output/terminal.js +198 -0
  65. package/dist/repository/gitWorkspace.js +210 -0
  66. package/dist/repository/repository.js +55 -0
  67. package/dist/repository/taskWorkspacePreparer.js +256 -0
  68. package/dist/role/role.js +246 -0
  69. package/dist/role/systemRoles.js +20 -0
  70. package/dist/run/agentRun.js +102 -0
  71. package/dist/scheduler/activeRoleRunDelivery.js +94 -0
  72. package/dist/scheduler/archivedTaskRuntime.js +12 -0
  73. package/dist/scheduler/leaderFailure.js +18 -0
  74. package/dist/scheduler/leaderWakeupProcessor.js +143 -0
  75. package/dist/scheduler/operatorInputNotificationProcessor.js +85 -0
  76. package/dist/scheduler/operatorNotification.js +17 -0
  77. package/dist/scheduler/pendingWakeup.js +33 -0
  78. package/dist/scheduler/ports.js +1 -0
  79. package/dist/scheduler/roleRunLiveness.js +41 -0
  80. package/dist/scheduler/wakeupQueue.js +13 -0
  81. package/dist/setup/setupCommand.js +317 -0
  82. package/dist/storage/durableFile.js +38 -0
  83. package/dist/storage/storageSchema.js +259 -0
  84. package/dist/storage/taskStore.js +1032 -0
  85. package/dist/task/task.js +216 -0
  86. package/dist/tmux/commandExecutor.js +69 -0
  87. package/dist/tmux/terminalHandoff.js +17 -0
  88. package/dist/tmux/tmuxManager.js +408 -0
  89. package/dist/workItem/workItem.js +45 -0
  90. package/dist/worktree/roleWorkspace.js +62 -0
  91. package/i18n/README.zh-CN.md +205 -0
  92. package/package.json +47 -0
  93. package/skills/yui-leader/SKILL.md +72 -0
  94. package/skills/yui-operator/SKILL.md +57 -0
  95. package/skills/yui-worker/SKILL.md +31 -0
@@ -0,0 +1,67 @@
1
+ const BOTH_ADAPTERS = Object.freeze(["codex", "claude"]);
2
+ const CODEX_ONLY = Object.freeze(["codex"]);
3
+ const CLAUDE_ONLY = Object.freeze(["claude"]);
4
+ export const ROLE_AGENT_OPTION_SPECS = Object.freeze([
5
+ roleAgentOption("--model", "model", "model", false, BOTH_ADAPTERS),
6
+ roleAgentOption("--effort", "effort", "effort", false, BOTH_ADAPTERS),
7
+ roleAgentOption("--sandbox", "permission.sandbox", "mode", false, CODEX_ONLY),
8
+ roleAgentOption("--approval", "permission.approval", "policy", false, CODEX_ONLY),
9
+ roleAgentOption("--permission-mode", "permission.mode", "mode", false, CLAUDE_ONLY),
10
+ roleAgentOption("--allowed-tool", "permission.allowedTools", "tool", true, CLAUDE_ONLY),
11
+ roleAgentOption("--disallowed-tool", "permission.disallowedTools", "tool", true, CLAUDE_ONLY),
12
+ roleAgentOption("--search", "search", "true", false, CODEX_ONLY, { staticValues: ["true"] }),
13
+ roleAgentOption("--profile", "profile", "profile", false, CODEX_ONLY),
14
+ roleAgentOption("--add-dir", "additionalDirectories", "path", true, BOTH_ADAPTERS, { fileValue: true }),
15
+ roleAgentOption("--settings", "settingsFile", "file", false, CLAUDE_ONLY, { fileValue: true }),
16
+ roleAgentOption("--setting-source", "settingsSources", "source", true, CLAUDE_ONLY),
17
+ roleAgentOption("--raw-arg", "advanced.rawArgs", "arg", true, BOTH_ADAPTERS, { allowOptionLikeValue: true })
18
+ ]);
19
+ export const ROLE_AGENT_INHERIT_OPTION = "--inherit";
20
+ export const ROLE_EXPECT_UPDATED_AT_OPTION = "--expect-updated-at";
21
+ export const ROLE_AGENT_INHERITABLE_FIELDS = Object.freeze(ROLE_AGENT_OPTION_SPECS.map(({ fieldKey }) => fieldKey));
22
+ export const ROLE_PROFILE_INHERITABLE_FIELDS = Object.freeze(["systemPrompt"]);
23
+ export const ROLE_INHERITABLE_FIELDS = Object.freeze([
24
+ ...ROLE_AGENT_INHERITABLE_FIELDS,
25
+ ...ROLE_PROFILE_INHERITABLE_FIELDS
26
+ ]);
27
+ export const ROLE_AGENT_SCRIPTED_OPTIONS = Object.freeze([
28
+ ...ROLE_AGENT_OPTION_SPECS.map(({ option }) => option),
29
+ ROLE_AGENT_INHERIT_OPTION
30
+ ]);
31
+ export const ROLE_AGENT_FILE_OPTIONS = Object.freeze(ROLE_AGENT_OPTION_SPECS.filter(({ fileValue }) => fileValue === true).map(({ option }) => option));
32
+ export function roleAgentOptionSpecsForAdapter(adapterId) {
33
+ return ROLE_AGENT_OPTION_SPECS.filter(({ adapters }) => adapters.includes(adapterId));
34
+ }
35
+ export function roleAgentOptionUsage() {
36
+ return [
37
+ ...ROLE_AGENT_OPTION_SPECS.map(({ option, valueLabel, repeatable }) => `[${option} <${valueLabel}>${repeatable ? " ..." : ""}]`),
38
+ `[${ROLE_AGENT_INHERIT_OPTION} <field-key> ...]`
39
+ ].join(" ");
40
+ }
41
+ function roleAgentOption(option, fieldKey, valueLabel, repeatable, adapters, extra = {}) {
42
+ return Object.freeze({ option, fieldKey, valueLabel, arity: "value", repeatable, adapters, ...extra });
43
+ }
44
+ export const FIRST_CLASS_AGENT_OPTIONS = Object.freeze([
45
+ Object.freeze({ id: "codex", label: "Codex" }),
46
+ Object.freeze({ id: "claude", label: "Claude" })
47
+ ]);
48
+ export function orderRoleOptions(roles) {
49
+ return [...roles].sort((left, right) => {
50
+ const rank = roleRank(left) - roleRank(right);
51
+ if (rank !== 0)
52
+ return rank;
53
+ return roleName(left).localeCompare(roleName(right));
54
+ });
55
+ }
56
+ function roleRank(role) {
57
+ const kind = role.kind?.toLowerCase();
58
+ const name = roleName(role).toLowerCase();
59
+ if (kind === "operator" || name === "operator")
60
+ return 0;
61
+ if (kind === "leader" || name === "leader")
62
+ return 1;
63
+ return 2;
64
+ }
65
+ function roleName(role) {
66
+ return role.name ?? role.id ?? "";
67
+ }
@@ -0,0 +1,546 @@
1
+ import { renderTable } from "../output/table.js";
2
+ const TEXT_COLUMN = { header: "Selection", minWidth: 12, maxWidth: 34 };
3
+ export async function resolveRoleWizardArguments(commandArgs, ports, io) {
4
+ const args = [...commandArgs];
5
+ if (!io.interactive || io.json)
6
+ return { kind: "unchanged", args };
7
+ if (isGlobalRoleAdd(args)) {
8
+ return addGlobalRole(args, ports, io);
9
+ }
10
+ if (isTaskRoleAdd(args)) {
11
+ return addTaskRole(args, ports, io);
12
+ }
13
+ if (isGlobalRoleUpdate(args)) {
14
+ return updateGlobalRole(args, ports, io);
15
+ }
16
+ if (isTaskRoleUpdate(args)) {
17
+ return updateTaskRole(args, ports, io);
18
+ }
19
+ return { kind: "unchanged", args };
20
+ }
21
+ function isGlobalRoleAdd(args) {
22
+ return args[0] === "role" && args[1] === "add";
23
+ }
24
+ function isTaskRoleAdd(args) {
25
+ return args[0] === "task" && args[1] === "role" && args[2] === "add";
26
+ }
27
+ // Explicit update options always win. This keeps scripts and deliberate
28
+ // non-interactive invocations out of the wizard even when run from a TTY.
29
+ function isGlobalRoleUpdate(args) {
30
+ return args.length === 3 && args[0] === "role" && args[1] === "update"
31
+ && isValue(args[2]);
32
+ }
33
+ function isTaskRoleUpdate(args) {
34
+ return args.length === 5 && args[0] === "task" && args[1] === "role"
35
+ && args[2] === "update" && isValue(args[3]) && isValue(args[4]);
36
+ }
37
+ async function addGlobalRole(args, ports, io) {
38
+ const named = await ensureRoleName(args, 2, io);
39
+ if (named === undefined)
40
+ return { kind: "cancelled", args };
41
+ const explicitSettings = hasExplicitAddSettings(named, 3);
42
+ const agents = await loadAgentSelection(ports);
43
+ const explicitAgent = optionValue(named, "--agent");
44
+ const selected = explicitAgent ?? await selectConfiguredAgent(ports, io, "Create Global Role from Agent", agents);
45
+ if (selected === undefined)
46
+ return { kind: "cancelled", args: named };
47
+ const withAgent = explicitAgent === undefined
48
+ ? [...named, "--agent", selected]
49
+ : named;
50
+ if (explicitSettings)
51
+ return { kind: "resolved", args: withAgent };
52
+ const agent = agents.agents.find((candidate) => candidate.id === selected);
53
+ return agent === undefined
54
+ ? { kind: "resolved", args: withAgent }
55
+ : configureRoleCreation(withAgent, agent, ports, io, false);
56
+ }
57
+ async function addTaskRole(args, ports, io) {
58
+ const withTask = await ensureTask(args, ports, io);
59
+ if (withTask === undefined)
60
+ return { kind: "cancelled", args };
61
+ const named = await ensureRoleName(withTask, 4, io);
62
+ if (named === undefined)
63
+ return { kind: "cancelled", args: withTask };
64
+ if (hasExplicitAddSettings(named, 5))
65
+ return { kind: "resolved", args: named };
66
+ const roleName = named[4] ?? "";
67
+ const [globalRoleValue, agents] = await Promise.all([
68
+ Promise.resolve(ports.call("role.show", { name: roleName })),
69
+ loadAgentSelection(ports)
70
+ ]);
71
+ const globalRole = asRole(globalRoleValue);
72
+ const explicitAgent = optionValue(named, "--agent");
73
+ if (globalRole !== undefined && explicitAgent === undefined) {
74
+ const source = await choose("Create Task Role from", [
75
+ {
76
+ value: "copy",
77
+ cells: ["Copy existing Global Role", `Active Agent: ${globalRole.activeAgentId}`]
78
+ },
79
+ {
80
+ value: "agent",
81
+ cells: ["Create from Agent", `Default Agent: ${agents.defaultAgent ?? "none"}`]
82
+ }
83
+ ], [TEXT_COLUMN, { header: "Details", minWidth: 18, maxWidth: 42 }], io, "copy", "source");
84
+ if (source === undefined)
85
+ return { kind: "cancelled", args: named };
86
+ if (source === "copy")
87
+ return { kind: "resolved", args: named };
88
+ }
89
+ const selected = explicitAgent ?? await selectConfiguredAgent(ports, io, "Create Task Role from Agent", agents);
90
+ if (selected === undefined)
91
+ return { kind: "cancelled", args: named };
92
+ const withAgent = explicitAgent === undefined
93
+ ? [...named, "--agent", selected]
94
+ : named;
95
+ const agent = agents.agents.find((candidate) => candidate.id === selected);
96
+ return agent === undefined
97
+ ? { kind: "resolved", args: withAgent }
98
+ : configureRoleCreation(withAgent, agent, ports, io, true);
99
+ }
100
+ async function ensureTask(args, ports, io) {
101
+ if (isValue(args[3]))
102
+ return args;
103
+ const value = await ports.call("task.list", {});
104
+ const tasks = Array.isArray(value) ? value.flatMap((candidate) => {
105
+ const input = entity(candidate);
106
+ const id = stringField(input, "id");
107
+ if (id === undefined)
108
+ return [];
109
+ return [{
110
+ value: id,
111
+ cells: [id, stringField(input, "title") ?? "", stringField(input, "status") ?? ""]
112
+ }];
113
+ }) : [];
114
+ const selected = await choose("Select Task", tasks, [
115
+ { header: "Task", minWidth: 5, maxWidth: 24 },
116
+ { header: "Title", minWidth: 8, maxWidth: 42 },
117
+ { header: "Status", minWidth: 6, maxWidth: 12 }
118
+ ], io, tasks[0]?.value, "Task");
119
+ if (selected === undefined)
120
+ return undefined;
121
+ const resolved = [...args];
122
+ resolved.splice(3, 0, selected);
123
+ return resolved;
124
+ }
125
+ async function ensureRoleName(args, index, io) {
126
+ if (isValue(args[index]))
127
+ return args;
128
+ const name = (await io.question("Role name: "))?.trim();
129
+ if (name === undefined || name.length === 0)
130
+ return undefined;
131
+ const resolved = [...args];
132
+ resolved.splice(index, 0, name);
133
+ return resolved;
134
+ }
135
+ async function configureRoleCreation(args, agent, ports, io, taskRole) {
136
+ const mode = await choose("Create Role", [
137
+ { value: "defaults", cells: ["Create with CLI defaults", "No Agent overrides"] },
138
+ { value: "configure", cells: ["Configure Role", "Set one Role or Agent field"] }
139
+ ], [TEXT_COLUMN, { header: "Result", minWidth: 18, maxWidth: 40 }], io, "defaults", "creation mode");
140
+ if (mode === undefined)
141
+ return { kind: "cancelled", args };
142
+ if (mode === "defaults")
143
+ return { kind: "resolved", args };
144
+ const section = await settingsSection("Configure Role", io, taskRole);
145
+ if (section === undefined)
146
+ return { kind: "cancelled", args };
147
+ return section === "role"
148
+ ? configureNewRoleField(args, agent, ports, io, taskRole)
149
+ : configureNewAgentField(args, agent, io);
150
+ }
151
+ async function configureNewRoleField(args, agent, ports, io, taskRole) {
152
+ const field = await choose("Role settings", [
153
+ { value: "description", cells: ["Description"] },
154
+ { value: "responsibilities", cells: ["Responsibilities"] },
155
+ { value: "constraints", cells: ["Constraints"] },
156
+ { value: "expected-output", cells: ["Expected output"] },
157
+ { value: "system-prompt", cells: ["System prompt"] },
158
+ { value: "active-agent", cells: ["Active Agent"] },
159
+ ...(taskRole ? [] : [{ value: "workspace", cells: ["Workspace"] }])
160
+ ], [TEXT_COLUMN], io, undefined, "field");
161
+ if (field === undefined)
162
+ return { kind: "cancelled", args };
163
+ if (field === "active-agent") {
164
+ const selected = await selectConfiguredAgent(ports, io, "Select Active Agent");
165
+ return selected === undefined
166
+ ? { kind: "cancelled", args }
167
+ : { kind: "resolved", args: replaceOptionValue(args, "--agent", selected) };
168
+ }
169
+ if (field === "workspace") {
170
+ const workspace = (await io.question("Workspace: "))?.trim();
171
+ return workspace === undefined || workspace.length === 0
172
+ ? { kind: "cancelled", args }
173
+ : { kind: "resolved", args: [...args, "--workspace", workspace] };
174
+ }
175
+ void agent;
176
+ return setProfileField(args, field, io);
177
+ }
178
+ async function configureNewAgentField(args, agent, io) {
179
+ const fields = agentFields({
180
+ agentId: agent.id,
181
+ adapterId: agent.adapterId,
182
+ config: { adapterId: agent.adapterId }
183
+ });
184
+ const field = await choose(`Agent settings: ${agent.id}`, fields.map((candidate) => ({ value: candidate.value, cells: [candidate.label, "CLI default"] })), [TEXT_COLUMN, { header: "Current", minWidth: 12, maxWidth: 24 }], io, undefined, "field");
185
+ const selected = fields.find((candidate) => candidate.value === field);
186
+ if (selected === undefined)
187
+ return { kind: "cancelled", args };
188
+ const value = await promptAgentFieldValue(selected, io);
189
+ return value === undefined
190
+ ? { kind: "cancelled", args }
191
+ : { kind: "resolved", args: [...args, selected.set, value] };
192
+ }
193
+ async function updateGlobalRole(args, ports, io) {
194
+ const role = asRole(await ports.call("role.show", { name: args[2] }));
195
+ if (role === undefined)
196
+ return { kind: "unchanged", args };
197
+ return updateRole(args, role, ports, io, false);
198
+ }
199
+ async function updateTaskRole(args, ports, io) {
200
+ const role = asRole(await ports.call("task.role.show", {
201
+ taskId: args[3],
202
+ roleName: args[4]
203
+ }));
204
+ if (role === undefined)
205
+ return { kind: "unchanged", args };
206
+ return updateRole(args, role, ports, io, true);
207
+ }
208
+ async function updateRole(args, role, ports, io, taskRole) {
209
+ const section = await settingsSection(`Update Role: ${role.name}`, io, taskRole);
210
+ if (section === undefined)
211
+ return { kind: "cancelled", args };
212
+ return section === "role"
213
+ ? updateRoleSettings(args, role, ports, io, taskRole)
214
+ : updateAgentSettings(args, role, io);
215
+ }
216
+ async function settingsSection(title, io, taskRole) {
217
+ return choose(title, [
218
+ {
219
+ value: "role",
220
+ cells: ["Role settings", taskRole ? "Profile, Active Agent" : "Profile, Active Agent, workspace"]
221
+ },
222
+ { value: "agent", cells: ["Agent settings", "Model, effort, permissions"] }
223
+ ], [TEXT_COLUMN, { header: "Includes", minWidth: 18, maxWidth: 46 }], io, "role", "settings");
224
+ }
225
+ async function updateRoleSettings(args, role, ports, io, taskRole) {
226
+ const fields = [
227
+ profileChoice("description", "Description", role.description),
228
+ profileChoice("responsibilities", "Responsibilities", role.responsibilities),
229
+ profileChoice("constraints", "Constraints", role.constraints),
230
+ profileChoice("expected-output", "Expected output", role.expectedOutput),
231
+ profileChoice("system-prompt", "System prompt", role.systemPrompt),
232
+ { value: "active-agent", cells: ["Active Agent", role.activeAgentId] },
233
+ ...(taskRole ? [] : [{ value: "workspace", cells: ["Workspace", roleDisplay(role.workspace)] }])
234
+ ];
235
+ const field = await choose("Role settings", fields, [TEXT_COLUMN, { header: "Current", minWidth: 12, maxWidth: 52 }], io, undefined, "field");
236
+ if (field === undefined)
237
+ return { kind: "cancelled", args };
238
+ if (field === "active-agent") {
239
+ const target = await selectActiveAgent(role, ports, io);
240
+ if (target === undefined)
241
+ return { kind: "cancelled", args };
242
+ return taskRole
243
+ ? { kind: "resolved", args: ["task", "role", "bind", args[3] ?? "", args[4] ?? "", target] }
244
+ : { kind: "resolved", args: ["role", "bind", args[2] ?? "", target] };
245
+ }
246
+ if (field === "workspace") {
247
+ const value = (await io.question(`Workspace [${roleDisplay(role.workspace)}]: `))?.trim();
248
+ return value === undefined || value.length === 0
249
+ ? { kind: "cancelled", args }
250
+ : { kind: "resolved", args: [...args, "--workspace", value] };
251
+ }
252
+ return updateProfileField(args, field, io);
253
+ }
254
+ async function updateProfileField(args, field, io) {
255
+ const action = await choose(`Update ${profileLabel(field)}`, [
256
+ { value: "keep", cells: ["Keep current"] },
257
+ { value: "clear", cells: ["Clear"] },
258
+ { value: "set", cells: ["Set value"] }
259
+ ], [TEXT_COLUMN], io, "keep", "action");
260
+ if (action === undefined || action === "keep")
261
+ return { kind: "cancelled", args };
262
+ const options = profileOptions(field);
263
+ if (action === "clear") {
264
+ return { kind: "resolved", args: [...args, options.clear] };
265
+ }
266
+ return setProfileField(args, field, io);
267
+ }
268
+ async function setProfileField(args, field, io) {
269
+ const options = profileOptions(field);
270
+ const hint = options.repeatable ? " (comma-separated)" : "";
271
+ const value = (await io.question(`${profileLabel(field)}${hint}: `))?.trim();
272
+ if (value === undefined || value.length === 0)
273
+ return { kind: "cancelled", args };
274
+ const values = options.repeatable
275
+ ? value.split(",").map((part) => part.trim()).filter(Boolean)
276
+ : [value];
277
+ if (values.length === 0)
278
+ return { kind: "cancelled", args };
279
+ return {
280
+ kind: "resolved",
281
+ args: [...args, ...values.flatMap((entry) => [options.set, entry])]
282
+ };
283
+ }
284
+ async function updateAgentSettings(args, role, io) {
285
+ const bindings = Object.values(role.agentBindings);
286
+ const selectedId = await choose("Select Role Agent binding", bindings.map((binding) => ({
287
+ value: binding.agentId,
288
+ cells: [binding.agentId, binding.adapterId, binding.agentId === role.activeAgentId ? "active" : "bound"]
289
+ })), [
290
+ { header: "Agent", minWidth: 5, maxWidth: 24 },
291
+ { header: "Adapter", minWidth: 7, maxWidth: 12 },
292
+ { header: "State", minWidth: 6, maxWidth: 8 }
293
+ ], io, role.activeAgentId, "binding");
294
+ if (selectedId === undefined)
295
+ return { kind: "cancelled", args };
296
+ const binding = role.agentBindings[selectedId];
297
+ if (binding === undefined)
298
+ return { kind: "cancelled", args };
299
+ const fields = agentFields(binding);
300
+ const field = await choose(`Agent settings: ${binding.agentId}`, [
301
+ ...fields.map((candidate) => ({
302
+ value: candidate.value,
303
+ cells: [candidate.label, candidate.current]
304
+ })),
305
+ { value: "clear-all", cells: ["Clear all overrides", "Use CLI defaults"] }
306
+ ], [TEXT_COLUMN, { header: "Current", minWidth: 12, maxWidth: 36 }], io, undefined, "field");
307
+ if (field === undefined)
308
+ return { kind: "cancelled", args };
309
+ if (field === "clear-all") {
310
+ return {
311
+ kind: "resolved",
312
+ args: [...args, "--agent", binding.agentId, "--clear-agent-config"]
313
+ };
314
+ }
315
+ const selectedField = fields.find((candidate) => candidate.value === field);
316
+ if (selectedField === undefined)
317
+ return { kind: "cancelled", args };
318
+ const action = await choose(`Update ${selectedField.label}`, [
319
+ { value: "keep", cells: ["Keep current", selectedField.current] },
320
+ { value: "clear", cells: ["Use CLI default", "Remove override"] },
321
+ { value: "set", cells: ["Set value", "Override CLI default"] }
322
+ ], [TEXT_COLUMN, { header: "Result", minWidth: 14, maxWidth: 36 }], io, "keep", "action");
323
+ if (action === undefined || action === "keep")
324
+ return { kind: "cancelled", args };
325
+ if (action === "clear") {
326
+ return {
327
+ kind: "resolved",
328
+ args: [...args, "--agent", binding.agentId, selectedField.clear]
329
+ };
330
+ }
331
+ const value = await promptAgentFieldValue(selectedField, io);
332
+ if (value === undefined || value.length === 0)
333
+ return { kind: "cancelled", args };
334
+ return {
335
+ kind: "resolved",
336
+ args: [...args, "--agent", binding.agentId, selectedField.set, value]
337
+ };
338
+ }
339
+ function agentFields(binding) {
340
+ const config = binding.config;
341
+ const permission = entity(config.permission) ?? {};
342
+ return [
343
+ agentField("model", "Model", config.model, "--model", "--clear-model"),
344
+ agentField("effort", "Effort", config.effort, "--effort", "--clear-effort"),
345
+ ...(binding.adapterId === "codex" ? [
346
+ agentField("sandbox", "Sandbox", permission.sandbox, "--sandbox", "--clear-sandbox", [
347
+ "read-only", "workspace-write", "danger-full-access"
348
+ ]),
349
+ agentField("approval", "Approval", permission.approval, "--approval", "--clear-approval", [
350
+ "untrusted", "on-request", "never"
351
+ ]),
352
+ agentField("search", "Web search", config.search, "--search", "--clear-search", [
353
+ "true"
354
+ ])
355
+ ] : []),
356
+ ...(binding.adapterId === "claude" ? [
357
+ agentField("permission-mode", "Permission mode", permission.mode, "--permission-mode", "--clear-permission-mode")
358
+ ] : [])
359
+ ];
360
+ }
361
+ function agentField(value, label, current, set, clear, choices) {
362
+ return { value, label, current: display(current), set, clear, ...(choices === undefined ? {} : { choices }) };
363
+ }
364
+ async function promptAgentFieldValue(field, io) {
365
+ return field.choices === undefined
366
+ ? (await io.question(`${field.label}: `))?.trim()
367
+ : choose(`Set ${field.label}`, field.choices.map((choice) => ({ value: choice, cells: [choice] })), [TEXT_COLUMN], io, field.choices[0], "value");
368
+ }
369
+ async function selectActiveAgent(role, ports, io) {
370
+ const configured = await configuredAgents(ports);
371
+ const byId = new Map(configured.map((agent) => [agent.id, agent]));
372
+ for (const binding of Object.values(role.agentBindings)) {
373
+ if (!byId.has(binding.agentId)) {
374
+ byId.set(binding.agentId, { id: binding.agentId, adapterId: binding.adapterId });
375
+ }
376
+ }
377
+ return choose("Select Active Agent", [...byId.values()].map((agent) => ({
378
+ value: agent.id,
379
+ cells: [
380
+ agent.id,
381
+ agent.adapterId,
382
+ agent.id === role.activeAgentId ? "active" : role.agentBindings[agent.id] === undefined ? "new" : "bound"
383
+ ]
384
+ })), [
385
+ { header: "Agent", minWidth: 5, maxWidth: 24 },
386
+ { header: "Adapter", minWidth: 7, maxWidth: 12 },
387
+ { header: "State", minWidth: 5, maxWidth: 8 }
388
+ ], io, role.activeAgentId, "Agent");
389
+ }
390
+ async function selectConfiguredAgent(ports, io, title, loaded) {
391
+ const selection = loaded ?? await loadAgentSelection(ports);
392
+ return choose(title, selection.agents.map((agent) => ({
393
+ value: agent.id,
394
+ cells: [agent.id, agent.adapterId, agent.id === selection.defaultAgent ? "yes" : ""]
395
+ })), [
396
+ { header: "Agent", minWidth: 5, maxWidth: 24 },
397
+ { header: "Adapter", minWidth: 7, maxWidth: 12 },
398
+ { header: "Default", minWidth: 7, maxWidth: 7 }
399
+ ], io, selection.defaultAgent, "Agent");
400
+ }
401
+ async function loadAgentSelection(ports) {
402
+ const [agents, config] = await Promise.all([
403
+ configuredAgents(ports),
404
+ ports.call("config.get", {})
405
+ ]);
406
+ const configuredDefault = stringField(entity(config), "defaultAgent");
407
+ const defaultAgent = agents.some((agent) => agent.id === configuredDefault)
408
+ ? configuredDefault
409
+ : agents[0]?.id;
410
+ return {
411
+ agents,
412
+ ...(defaultAgent === undefined ? {} : { defaultAgent })
413
+ };
414
+ }
415
+ async function configuredAgents(ports) {
416
+ const value = await ports.call("agent.list", {});
417
+ if (!Array.isArray(value))
418
+ return [];
419
+ return value.flatMap((candidate) => {
420
+ const input = entity(candidate);
421
+ const id = stringField(input, "id");
422
+ const adapterId = stringField(input, "adapterId");
423
+ return id === undefined || adapterId === undefined ? [] : [{ id, adapterId }];
424
+ });
425
+ }
426
+ async function choose(title, choices, columns, io, defaultValue, label) {
427
+ if (choices.length === 0) {
428
+ io.write(`○ No ${label}s are available.\n`);
429
+ return undefined;
430
+ }
431
+ io.write(`${renderTable(title, [{ header: "#", minWidth: 1, maxWidth: 4 }, ...columns], choices.map((choice, index) => [String(index + 1), ...choice.cells]), io.width)}\n\n`);
432
+ const answer = (await io.question(`Choose ${label} [1-${choices.length}/value, q]: `))?.trim();
433
+ if (answer === undefined || answer.toLowerCase() === "q" || answer.toLowerCase() === "quit") {
434
+ return undefined;
435
+ }
436
+ if (answer.length === 0) {
437
+ return choices.find((choice) => choice.value === defaultValue)?.value
438
+ ?? choices[0]?.value;
439
+ }
440
+ const numeric = Number(answer);
441
+ if (Number.isSafeInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
442
+ return choices[numeric - 1]?.value;
443
+ }
444
+ return choices.find((choice) => choice.value === answer)?.value;
445
+ }
446
+ function asRole(value) {
447
+ const input = entity(value);
448
+ const name = stringField(input, "name");
449
+ const activeAgentId = stringField(input, "activeAgentId");
450
+ const rawBindings = entity(input?.agentBindings);
451
+ if (input === undefined || name === undefined || activeAgentId === undefined || rawBindings === undefined) {
452
+ return undefined;
453
+ }
454
+ const agentBindings = Object.fromEntries(Object.entries(rawBindings).flatMap(([id, raw]) => {
455
+ const binding = entity(raw);
456
+ const agentId = stringField(binding, "agentId");
457
+ const adapterId = stringField(binding, "adapterId");
458
+ const config = entity(binding?.config);
459
+ return agentId === undefined || adapterId === undefined || config === undefined
460
+ ? []
461
+ : [[id, { agentId, adapterId, config }]];
462
+ }));
463
+ return {
464
+ name,
465
+ activeAgentId,
466
+ agentBindings,
467
+ ...(stringField(input, "workspace") === undefined ? {} : { workspace: stringField(input, "workspace") }),
468
+ ...(stringField(input, "description") === undefined ? {} : { description: stringField(input, "description") }),
469
+ ...(stringArray(input.responsibilities) === undefined ? {} : { responsibilities: stringArray(input.responsibilities) }),
470
+ ...(stringArray(input.constraints) === undefined ? {} : { constraints: stringArray(input.constraints) }),
471
+ ...(stringField(input, "expectedOutput") === undefined ? {} : { expectedOutput: stringField(input, "expectedOutput") }),
472
+ ...(stringField(input, "systemPrompt") === undefined ? {} : { systemPrompt: stringField(input, "systemPrompt") })
473
+ };
474
+ }
475
+ function profileChoice(value, label, current) {
476
+ return { value, cells: [label, roleDisplay(current)] };
477
+ }
478
+ function profileLabel(field) {
479
+ return {
480
+ description: "Description",
481
+ responsibilities: "Responsibilities",
482
+ constraints: "Constraints",
483
+ "expected-output": "Expected output",
484
+ "system-prompt": "System prompt"
485
+ }[field] ?? field;
486
+ }
487
+ function profileOptions(field) {
488
+ switch (field) {
489
+ case "description": return { set: "--description", clear: "--clear-description", repeatable: false };
490
+ case "responsibilities": return { set: "--responsibility", clear: "--clear-responsibilities", repeatable: true };
491
+ case "constraints": return { set: "--constraint", clear: "--clear-constraints", repeatable: true };
492
+ case "expected-output": return { set: "--expected-output", clear: "--clear-expected-output", repeatable: false };
493
+ case "system-prompt": return { set: "--system-prompt", clear: "--clear-system-prompt", repeatable: false };
494
+ default: throw new Error(`Unsupported Role profile field: ${field}.`);
495
+ }
496
+ }
497
+ function entity(value) {
498
+ return typeof value === "object" && value !== null && !Array.isArray(value)
499
+ ? value
500
+ : undefined;
501
+ }
502
+ function stringField(value, field) {
503
+ const candidate = value?.[field];
504
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
505
+ }
506
+ function stringArray(value) {
507
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string")
508
+ ? [...value]
509
+ : undefined;
510
+ }
511
+ function display(value) {
512
+ if (Array.isArray(value))
513
+ return value.length === 0 ? "—" : value.join(", ");
514
+ if (typeof value === "string")
515
+ return value.length === 0 ? "—" : value;
516
+ if (typeof value === "number" || typeof value === "boolean")
517
+ return String(value);
518
+ return "CLI default";
519
+ }
520
+ function roleDisplay(value) {
521
+ if (Array.isArray(value))
522
+ return value.length === 0 ? "—" : value.join(", ");
523
+ if (typeof value === "string")
524
+ return value.length === 0 ? "—" : value;
525
+ if (typeof value === "number" || typeof value === "boolean")
526
+ return String(value);
527
+ return "—";
528
+ }
529
+ function isValue(value) {
530
+ return value !== undefined && value.length > 0 && !value.startsWith("--");
531
+ }
532
+ function hasExplicitAddSettings(args, optionStart) {
533
+ return args.slice(optionStart).some((value) => value.startsWith("--") && value !== "--agent");
534
+ }
535
+ function optionValue(args, option) {
536
+ const index = args.indexOf(option);
537
+ return index < 0 || !isValue(args[index + 1]) ? undefined : args[index + 1];
538
+ }
539
+ function replaceOptionValue(args, option, value) {
540
+ const replaced = [...args];
541
+ const index = replaced.indexOf(option);
542
+ if (index < 0)
543
+ return [...replaced, option, value];
544
+ replaced[index + 1] = value;
545
+ return replaced;
546
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { runtimeError } from "../errors/cliError.js";
3
+ /**
4
+ * Update the published global package. This intentionally remains an exact,
5
+ * shell-free npm invocation. In particular, yui-dev updates the published
6
+ * Yui installation; it never rewrites the current checkout.
7
+ */
8
+ export function runUpdateCommand(spawn = spawnSync) {
9
+ const result = spawn("npm", ["install", "--global", "@zq-silk/yui@latest"], {
10
+ cwd: process.cwd(),
11
+ env: process.env,
12
+ shell: false,
13
+ stdio: "inherit"
14
+ });
15
+ if (result.error !== undefined) {
16
+ throw runtimeError(`Failed to start npm: ${result.error.message}`);
17
+ }
18
+ if (result.status === null) {
19
+ throw runtimeError(`npm update terminated${result.signal === null ? "" : ` by ${result.signal}`}.`);
20
+ }
21
+ return result.status;
22
+ }