@zq-silk/yui 2.3.0 → 2.4.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 (183) hide show
  1. package/README.md +15 -1
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +17 -8
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +25 -2
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +10 -1
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  23. package/dist/controller/jobControl.js +2 -0
  24. package/dist/controller/nativeHostInput.js +37 -0
  25. package/dist/controller/runtime.js +93 -2
  26. package/dist/controller/runtimeEventInbox.js +2 -2
  27. package/dist/controller/runtimeEventProcessor.js +3 -3
  28. package/dist/doctor/doctor.js +10 -0
  29. package/dist/execution/reviewMainRun.js +1 -0
  30. package/dist/execution/workItemMainRun.js +1 -0
  31. package/dist/executor/codexConfigConflict.js +6 -4
  32. package/dist/executor/effectiveLaunch.js +24 -1
  33. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  34. package/dist/integration/gitIntegrationService.js +7 -12
  35. package/dist/integration/integrationSourceApplication.js +4 -2
  36. package/dist/job/httpPreview.js +61 -0
  37. package/dist/job/jobRunner.js +12 -2
  38. package/dist/kernel/builtinCapabilities.js +3 -1
  39. package/dist/message/message.js +15 -1
  40. package/dist/output/rolePresentation.js +4 -1
  41. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  42. package/dist/repository/gitCompatibility.js +62 -0
  43. package/dist/repository/gitWorkspace.js +48 -71
  44. package/dist/repository/gitWorktreeInventory.js +121 -0
  45. package/dist/repository/project.js +74 -25
  46. package/dist/resources/resourceDiscovery.js +2 -2
  47. package/dist/resources/resourceGc.js +7 -4
  48. package/dist/resources/resourceRegistrar.js +2 -1
  49. package/dist/runtime/agentEndpoint.js +8 -0
  50. package/dist/runtime/agentHost.js +170 -3
  51. package/dist/runtime/codexAppServerRuntime.js +1 -1
  52. package/dist/runtime/codexNativeAccess.js +175 -0
  53. package/dist/runtime/controlledCodexTui.js +116 -0
  54. package/dist/runtime/publicReply.js +23 -0
  55. package/dist/runtime/runtimeObservation.js +48 -0
  56. package/dist/runtime/structuredProviderHost.js +16 -7
  57. package/dist/runtime/taskCostMetrics.js +133 -0
  58. package/dist/runtime/taskUsageMetrics.js +11 -2
  59. package/dist/runtime/taskUsageQuery.js +38 -0
  60. package/dist/scheduler/taskExecutionProjection.js +2 -0
  61. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  62. package/dist/storage/homeLayout.js +13 -0
  63. package/dist/storage/sqliteSchema.js +44 -0
  64. package/dist/storage/sqliteStore.js +53 -0
  65. package/dist/storage/storageVersions.js +1 -1
  66. package/dist/storage/storeRpc.js +2 -0
  67. package/dist/storage/taskSearch.js +35 -0
  68. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  69. package/dist/task/ordinaryArchive.js +42 -0
  70. package/dist/tmux/tmuxManager.js +8 -8
  71. package/dist/web/assets/assetManifest.js +11 -51
  72. package/dist/web/assets/client/app/catalog.js +215 -0
  73. package/dist/web/assets/client/app/main.js +148 -0
  74. package/dist/web/assets/client/app/selection.js +139 -0
  75. package/dist/web/assets/client/app/settings.js +326 -0
  76. package/dist/web/assets/client/app/taskView.js +280 -0
  77. package/dist/web/assets/client/domain/context.js +96 -0
  78. package/dist/web/assets/client/domain/records.js +126 -0
  79. package/dist/web/assets/client/domain/runs.js +119 -0
  80. package/dist/web/assets/client/domain/taskForms.js +197 -0
  81. package/dist/web/assets/client/domain/vocab.js +69 -0
  82. package/dist/web/assets/client/domain/work.js +116 -0
  83. package/dist/web/assets/client/i18n/en.js +900 -0
  84. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  85. package/dist/web/assets/client/index.js +114 -0
  86. package/dist/web/assets/client/layout/resizer.js +37 -0
  87. package/dist/web/assets/client/layout/router.js +35 -0
  88. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  89. package/dist/web/assets/client/layout/sizing.js +95 -0
  90. package/dist/web/assets/client/layout/workspace.js +247 -0
  91. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  92. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  93. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  94. package/dist/web/assets/client/lib/i18n.js +54 -0
  95. package/dist/web/assets/client/lib/markdown.js +167 -0
  96. package/dist/web/assets/client/lib/prefs.js +28 -0
  97. package/dist/web/assets/client/lib/theme.js +68 -0
  98. package/dist/web/assets/client/ui/containers.js +101 -0
  99. package/dist/web/assets/client/ui/controls.js +51 -0
  100. package/dist/web/assets/client/ui/forms.js +48 -0
  101. package/dist/web/assets/client/ui/metrics.js +137 -0
  102. package/dist/web/assets/client/ui/primitives.js +92 -0
  103. package/dist/web/assets/client/ui/text.js +33 -0
  104. package/dist/web/assets/client/ui/toast.js +12 -0
  105. package/dist/web/assets/client/views/bodySearch.js +172 -0
  106. package/dist/web/assets/client/views/createTask.js +85 -0
  107. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  108. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  109. package/dist/web/assets/client/views/dock/preview.js +129 -0
  110. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  111. package/dist/web/assets/client/views/globalInput.js +67 -0
  112. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  113. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  114. package/dist/web/assets/client/views/skills.js +152 -0
  115. package/dist/web/assets/client/views/task/actions.js +36 -0
  116. package/dist/web/assets/client/views/task/delivery.js +185 -0
  117. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  118. package/dist/web/assets/client/views/task/files.js +272 -0
  119. package/dist/web/assets/client/views/task/observation.js +153 -0
  120. package/dist/web/assets/client/views/task/overview.js +161 -0
  121. package/dist/web/assets/client/views/task/page.js +131 -0
  122. package/dist/web/assets/client/views/task/records.js +163 -0
  123. package/dist/web/assets/client/views/task/runtime.js +130 -0
  124. package/dist/web/assets/client/views/task/timeline.js +190 -0
  125. package/dist/web/assets/shared/geometry.js +19 -0
  126. package/dist/web/assets/shared/taskTabs.js +8 -0
  127. package/dist/web/assets/shared/themes.js +93 -0
  128. package/dist/web/assets/shell/dialogs.js +63 -0
  129. package/dist/web/assets/shell/dock.js +40 -0
  130. package/dist/web/assets/shell/index.js +40 -0
  131. package/dist/web/assets/shell/settings.js +34 -0
  132. package/dist/web/assets/shell/sidebar.js +45 -0
  133. package/dist/web/assets/styles/index.js +41 -0
  134. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  135. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  136. package/dist/web/assets/styles/markdown.js +22 -3
  137. package/dist/web/assets/styles/tokens.js +10 -63
  138. package/dist/web/assets/styles/ui/controls.js +76 -0
  139. package/dist/web/assets/styles/ui/forms.js +46 -0
  140. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  141. package/dist/web/assets/styles/views/delivery.js +29 -0
  142. package/dist/web/assets/styles/views/dock.js +71 -0
  143. package/dist/web/assets/styles/views/page.js +32 -0
  144. package/dist/web/assets/styles/views/records.js +70 -0
  145. package/dist/web/assets/styles/views/settings.js +26 -0
  146. package/dist/web/assets/styles/views/sidebar.js +74 -0
  147. package/dist/web/assets/styles/views/task.js +80 -0
  148. package/dist/web/assets/styles/views/terminal.js +38 -0
  149. package/dist/web/taskPreviews.js +205 -0
  150. package/dist/web/tmuxWebTerminal.js +16 -9
  151. package/dist/web/webConversation.js +373 -0
  152. package/dist/web/webMaterials.js +155 -0
  153. package/dist/web/webServer.js +295 -18
  154. package/dist/web/webSettings.js +331 -0
  155. package/dist/web/webSkills.js +86 -0
  156. package/dist/web/webSnapshot.js +40 -8
  157. package/dist/web/webTaskSurface.js +106 -8
  158. package/dist/workspace/gitChangeSetCapture.js +3 -1
  159. package/docs/managed-turn-and-session-runtime.md +9 -0
  160. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  161. package/docs/release-workflow.md +28 -0
  162. package/docs/release-workflow.zh-CN.md +21 -0
  163. package/docs/storage-baseline.md +38 -1
  164. package/docs/storage-baseline.zh-CN.md +22 -0
  165. package/docs/task-discovery.md +28 -0
  166. package/docs/testing/verification-levels.md +2 -2
  167. package/i18n/README.zh-CN.md +1 -1
  168. package/package.json +1 -1
  169. package/dist/web/assets/client/app.js +0 -808
  170. package/dist/web/assets/client/components.js +0 -477
  171. package/dist/web/assets/client/detail.js +0 -379
  172. package/dist/web/assets/client/dock.js +0 -309
  173. package/dist/web/assets/client/evidence.js +0 -364
  174. package/dist/web/assets/client/forms.js +0 -230
  175. package/dist/web/assets/client/i18n.js +0 -1277
  176. package/dist/web/assets/client/markdown.js +0 -114
  177. package/dist/web/assets/client/records.js +0 -198
  178. package/dist/web/assets/client/sections.js +0 -349
  179. package/dist/web/assets/client/theme.js +0 -71
  180. package/dist/web/assets/shell.js +0 -161
  181. package/dist/web/assets/styles/components.js +0 -216
  182. package/dist/web/assets/styles/views.js +0 -309
  183. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -0,0 +1,331 @@
1
+ import { createHash } from "node:crypto";
2
+ import { CONFIG_DOMAINS, configDefinitionsForDomain } from "../config/configCatalog.js";
3
+ import { effectiveConfigData, runConfigCommand } from "../commands/configCommands.js";
4
+ import { runAgentCommand } from "../commands/agentCommands.js";
5
+ import { runGlobalRoleCommand, previewGlobalRoleAgentConfigurationMutation } from "../commands/globalRoleCommands.js";
6
+ import { runProfileCommand, previewProfileAgentConfigurationMutation } from "../commands/profileCommands.js";
7
+ import { roleOptionSpecs } from "../commands/roleConfiguration.js";
8
+ import { staticAgentConfigurationFields } from "../executor/agentConfigurationFields.js";
9
+ import { validateAgentLaunchConfiguration } from "../executor/agentConfigurationCatalog.js";
10
+ import { resolveAgentProfileView } from "../profile/agentProfileRuntime.js";
11
+ import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
12
+ const ROLE_NOTICE = "Saved desired configuration applies to a subsequent Role launch, not the current Session. Existing Task bindings are unchanged. No Session is restarted here.";
13
+ const PROFILE_NOTICE = "Profile behavior is copied into newly created Task Roles. Inherited runtime resolves from the global Worker when applied. Existing Task Roles are not rebound.";
14
+ const AGENT_NOTICE = "Command, arguments and environment references apply to subsequent Host processes. Authentication remains with the native Agent. Only reference names and presence in the Controller environment are shown; no environment values are returned.";
15
+ /** A view adapter over existing commands, not another writable configuration store. */
16
+ export function createWebSettings(store, options) {
17
+ function groups() {
18
+ return [
19
+ { id: "system", title: "system", section: "general", advanced: false },
20
+ ...store.listGlobalRoles().map(r => ({ id: `role/${r.name}`, title: r.name, section: "roles", advanced: false })),
21
+ ...CONFIG_DOMAINS.filter(id => id !== "system").map(id => ({ id, title: id, section: "advanced", advanced: true })),
22
+ ...store.listConfiguredAgents().map(a => ({ id: `agent/${a.id}`, title: a.id, section: "agents", advanced: true })),
23
+ ...store.listAgentProfiles().map(p => ({ id: `profile/${p.id}`, title: p.id, section: "profiles", advanced: true }))
24
+ ];
25
+ }
26
+ function index(query = "", cursor = "0") {
27
+ if (query.length > 256 || !/^\d{1,8}$/.test(cursor))
28
+ throw new WebRequestRejected("Invalid settings search or cursor.");
29
+ const needle = query.trim().toLowerCase();
30
+ const all = groups().filter(g => !needle || [
31
+ g.id, g.title, g.section,
32
+ ...readGroup(g).fields.map(f => `${f.key} ${f.label} ${f.summary ?? ""}`)
33
+ ].join(" ").toLowerCase().includes(needle));
34
+ const offset = Number(cursor);
35
+ return { groups: all.slice(offset, offset + 50), total: all.length,
36
+ nextCursor: offset + 50 < all.length ? String(offset + 50) : null };
37
+ }
38
+ function read(id) {
39
+ const meta = groups().find(group => group.id === id);
40
+ if (!meta)
41
+ throw new WebRequestRejected("Configuration group not found.");
42
+ return readGroup(meta);
43
+ }
44
+ function readGroup(meta) {
45
+ const { id } = meta;
46
+ let fields;
47
+ let notice = "Saved values and effective defaults share the CLI configuration source. Reset removes the stored override; it does not restart services.";
48
+ let observation;
49
+ let agentId;
50
+ let version;
51
+ if (CONFIG_DOMAINS.includes(id)) {
52
+ const domain = id;
53
+ const config = store.getConfig();
54
+ const effective = effectiveConfigData(config, domain);
55
+ const defaults = effectiveConfigData({ schemaVersion: 1 }, domain);
56
+ fields = configDefinitionsForDomain(domain).map(def => ({
57
+ key: def.key, label: def.label, value: effective[def.property],
58
+ kind: def.key === "review" ? "json" : kind(effective[def.property]), summary: def.summary, takesEffect: def.takesEffect,
59
+ source: config[def.property] === undefined ? "default" : "stored",
60
+ defaultValue: defaults[def.property], reset: true,
61
+ ...(def.key === "default-agent" ? { choices: store.listConfiguredAgents().map(a => a.id) } : {}),
62
+ ...(def.key === "resources-gc-mode" ? { choices: ["report", "quarantine"] } : {})
63
+ }));
64
+ version = fields;
65
+ }
66
+ else if (id.startsWith("agent/")) {
67
+ const agent = store.getConfiguredAgent(id.slice(6));
68
+ agentId = agent.id;
69
+ version = agent;
70
+ notice = AGENT_NOTICE;
71
+ fields = [
72
+ field("command", agent.command),
73
+ field("baseArgs", agent.baseArgs, "json"),
74
+ { ...field("environment", agent.environment, "json"), summary: "Array of {target, source: \"process\", sourceName, required: true}. Only process-variable references are accepted." }
75
+ ];
76
+ observation = { component: agent.component, adapterId: agent.adapterId,
77
+ environment: agent.environment.map(b => ({ ...b, present: !!options.environment[b.sourceName] })) };
78
+ }
79
+ else if (id.startsWith("role/")) {
80
+ const role = store.getGlobalRole(id.slice(5));
81
+ const binding = role.agentBindings[role.activeAgentId];
82
+ agentId = role.activeAgentId;
83
+ version = role;
84
+ notice = ROLE_NOTICE;
85
+ const specs = roleOptionSpecs({ update: true, includeWorkspace: true });
86
+ fields = [
87
+ { ...field("activeAgentId", role.activeAgentId), choices: store.listConfiguredAgents().map(a => a.id),
88
+ summary: "Explicit global Role Agent selection. Save this field alone, then edit the selected Agent's settings. Existing live-session and lifecycle guards apply; no Task is rebound." },
89
+ field("workspace", role.workspace),
90
+ ...["description", "systemPrompt", "expectedOutput", "responsibilities", "constraints", "skills"]
91
+ .map(key => ({ ...field(key, role[key] ?? (["responsibilities", "constraints", "skills"].includes(key) ? [] : ""), ["responsibilities", "constraints", "skills"].includes(key) ? "json" : "multiline"), reset: true })),
92
+ ...staticAgentConfigurationFields(binding.adapterId)
93
+ .filter(f => ROLE_FIELDS[f.key] && specs.has(ROLE_FIELDS[f.key].set))
94
+ .map(f => ({
95
+ ...field(f.key, getPath(binding.config, f.key) ?? "", f.key.endsWith("Tools") ? "json" : "text"),
96
+ summary: f.reason, choices: f.allowCustom ? undefined : f.choices.map(c => c.value),
97
+ reset: !!ROLE_FIELDS[f.key].clear,
98
+ source: getPath(binding.config, f.key) === undefined ? "native/default" : "stored"
99
+ }))
100
+ ];
101
+ observation = { activeAgentId: agentId, launchRevision: role.launchRevision,
102
+ defaultAccess: role.defaultAccess, agentBindings: Object.keys(role.agentBindings),
103
+ session: store.getGlobalRoleSessionSet(role.name)?.sessions[role.activeAgentId]?.status ?? "absent" };
104
+ }
105
+ else {
106
+ const profile = store.getAgentProfile(id.slice(8));
107
+ const view = resolveAgentProfileView(profile, store);
108
+ version = profile;
109
+ notice = PROFILE_NOTICE;
110
+ agentId = view.runtime.status === "resolved" ? view.runtime.binding.agentId : undefined;
111
+ fields = [
112
+ { ...field("access", profile.defaultAccess), choices: ["read", "write"] },
113
+ { ...field("description", profile.description ?? "", "multiline"), reset: true },
114
+ { ...field("instructions", profile.instructions ?? "", "multiline"), reset: true },
115
+ { ...field("skills", profile.skills ?? [], "json"), reset: true },
116
+ { ...field("agent", profile.runtime.source === "explicit" ? profile.runtime.agentId : ""), choices: ["", ...store.listConfiguredAgents().map(a => a.id)], summary: "Empty = inherit global Worker; an explicit Agent enables model/effort overrides." },
117
+ { ...field("model", profile.runtime.source === "explicit" ? profile.runtime.model ?? "" : ""), reset: true },
118
+ { ...field("effort", profile.runtime.source === "explicit" ? profile.runtime.effort ?? "" : ""), reset: true }
119
+ ];
120
+ observation = view.runtime;
121
+ }
122
+ return { ...meta, fields, notice, agentId, observation,
123
+ revision: createHash("sha256").update(JSON.stringify(version)).digest("hex") };
124
+ }
125
+ async function capabilities(id, refresh) {
126
+ const group = read(id);
127
+ if (!group.agentId)
128
+ throw new WebRequestRejected("This group has no resolved Agent.");
129
+ const agent = store.getConfiguredAgent(group.agentId);
130
+ if (!agent)
131
+ throw new WebRequestRejected("Configured Agent not found.");
132
+ const role = id.startsWith("role/") ? store.getGlobalRole(id.slice(5)) : null;
133
+ return options.catalogs.resolve({ agent, refresh,
134
+ cwd: role?.workspace ?? store.getConfig().defaultWorkspace ?? process.cwd(),
135
+ ...(role ? { config: role.agentBindings[role.activeAgentId].config } : {}) });
136
+ }
137
+ async function save(input) {
138
+ let mutationEntered = false;
139
+ try {
140
+ const body = record(input);
141
+ if (Object.keys(body).some(k => !["id", "revision", "changes", "acknowledgeLive"].includes(k))
142
+ || typeof body.id !== "string" || typeof body.revision !== "string"
143
+ || !Array.isArray(body.changes) || !body.changes.length || body.changes.length > 64
144
+ || (body.acknowledgeLive !== undefined && typeof body.acknowledgeLive !== "boolean")) {
145
+ throw new WebRequestRejected("Expected a group, its read revision, and changed fields.");
146
+ }
147
+ const id = body.id;
148
+ const current = read(id);
149
+ if (current.revision !== body.revision)
150
+ throw new WebRequestRejected("Configuration changed since it was read. Compare current values before saving again.");
151
+ const seen = new Set();
152
+ const changes = body.changes.map(value => {
153
+ const change = record(value);
154
+ const def = current.fields.find(f => f.key === change.key);
155
+ if (!def || seen.has(def.key) || Object.keys(change).some(k => !["key", "value", "reset"].includes(k))
156
+ || (change.reset !== undefined && change.reset !== true) || (change.reset === true && !def.reset)
157
+ || (change.reset === true ? Object.hasOwn(change, "value") : !Object.hasOwn(change, "value"))) {
158
+ throw new WebRequestRejected("Invalid or duplicate configuration field.");
159
+ }
160
+ seen.add(def.key);
161
+ return { key: def.key, value: change.value, reset: change.reset === true };
162
+ });
163
+ const args = objectArguments(id, changes);
164
+ if (id.startsWith("role/") && changes.some(c => c.key === "activeAgentId") && changes.length !== 1) {
165
+ throw new WebRequestRejected("Save the Agent selection alone, then read its supported settings.");
166
+ }
167
+ // The same complete candidate and transactional recheck as the CLI.
168
+ const mutation = id.startsWith("profile/")
169
+ ? previewProfileAgentConfigurationMutation(["update", id.slice(8), ...args], store)
170
+ : id.startsWith("role/") && !changes.some(c => c.key === "activeAgentId")
171
+ ? previewGlobalRoleAgentConfigurationMutation(["update", id.slice(5), ...args], store, { yuiHome: store.rootDirectory() }) : undefined;
172
+ let validated;
173
+ if (mutation) {
174
+ const agent = store.getConfiguredAgent(mutation.agentId);
175
+ if (!agent)
176
+ throw new WebRequestRejected("Configured Agent not found.");
177
+ validated = await options.catalogs.resolve({ agent, cwd: mutation.cwd, config: mutation.config });
178
+ validateAgentLaunchConfiguration(validated.catalog, mutation.config);
179
+ }
180
+ const validation = {
181
+ validateAgentConfiguration: (candidate) => {
182
+ if (!mutation || JSON.stringify(candidate) !== JSON.stringify(mutation) || !validated) {
183
+ throw new Error("Agent configuration changed after capability validation. Read it again.");
184
+ }
185
+ validateAgentLaunchConfiguration(validated.catalog, candidate.config);
186
+ }
187
+ };
188
+ mutationEntered = true;
189
+ const output = webLocalMutation(store, tx => {
190
+ if (read(id).revision !== body.revision)
191
+ throw new Error("Configuration changed since it was read. Compare current values before saving again.");
192
+ if (CONFIG_DOMAINS.includes(id)) {
193
+ return changes.map(change => runConfigCommand(id, change.reset ? ["clear", change.key] : ["set", change.key, ...configArguments(change.key, change.value)], tx).output).join("");
194
+ }
195
+ const ack = body.acknowledgeLive === true ? ["--yes"] : [];
196
+ if (id.startsWith("agent/"))
197
+ return runAgentCommand(["update", id.slice(6), ...args, ...ack], tx);
198
+ if (id.startsWith("role/")) {
199
+ const binding = changes.find(c => c.key === "activeAgentId");
200
+ if (binding && changes.length !== 1)
201
+ throw new Error("Save the Agent selection alone, then read its supported settings.");
202
+ return runGlobalRoleCommand(binding
203
+ ? ["bind", id.slice(5), text(binding.value)]
204
+ : ["update", id.slice(5), ...args, ...ack], tx, {
205
+ yuiHome: store.rootDirectory(), env: options.environment, ...validation
206
+ });
207
+ }
208
+ return runProfileCommand(["update", id.slice(8), ...args], tx, () => new Date(), validation).output;
209
+ });
210
+ let refresh = "not-required";
211
+ if (changes.some(c => c.key === "reconciliation-interval-seconds")) {
212
+ try {
213
+ options.refreshConfiguration?.();
214
+ refresh = options.refreshConfiguration ? "applied" : "restart-required";
215
+ }
216
+ catch {
217
+ refresh = "saved-refresh-failed";
218
+ }
219
+ }
220
+ return { status: "saved", output, group: read(id), adoption: "No existing Session or Task was rebound.", refresh };
221
+ }
222
+ catch (error) {
223
+ if (!mutationEntered)
224
+ throw new WebRequestRejected(error instanceof Error ? error.message : "Invalid settings request.");
225
+ throw error;
226
+ }
227
+ }
228
+ return { index, read, save, capabilities };
229
+ }
230
+ function kind(value) {
231
+ return typeof value === "boolean" ? "boolean" : typeof value === "number" ? "number"
232
+ : value !== null && typeof value === "object" ? "json" : "text";
233
+ }
234
+ function field(key, value, type = "text") {
235
+ return { key, label: key, value, kind: type };
236
+ }
237
+ function record(value) {
238
+ if (!value || typeof value !== "object" || Array.isArray(value))
239
+ throw new WebRequestRejected("Expected an object.");
240
+ return value;
241
+ }
242
+ function text(value) {
243
+ if (typeof value !== "string" || value.includes("\0"))
244
+ throw new WebRequestRejected("Expected text.");
245
+ return value;
246
+ }
247
+ function strings(value) {
248
+ if (!Array.isArray(value))
249
+ throw new WebRequestRejected("Expected a JSON array of strings.");
250
+ return value.map(text);
251
+ }
252
+ function getPath(value, path) {
253
+ return path.split(".").reduce((v, k) => v && typeof v === "object" ? v[k] : undefined, value);
254
+ }
255
+ function configArguments(key, value) {
256
+ if (key === "runtime-health") {
257
+ const v = record(value);
258
+ if (Object.keys(v).some(k => !["quietAfterSeconds", "diagnosticAfterSeconds", "stallAfterSeconds"].includes(k)))
259
+ throw new WebRequestRejected("Unknown health field.");
260
+ return ["--quiet-after-seconds", String(v.quietAfterSeconds), "--diagnostic-after-seconds", String(v.diagnosticAfterSeconds), "--stall-after-seconds", String(v.stallAfterSeconds)];
261
+ }
262
+ if (key === "review") {
263
+ const v = record(value);
264
+ if (Object.keys(v).some(k => !["roleName", "trigger"].includes(k)))
265
+ throw new WebRequestRejected("Unknown review field.");
266
+ return ["--role", text(v.roleName), "--trigger", text(v.trigger)];
267
+ }
268
+ if (!["string", "number", "boolean"].includes(typeof value))
269
+ throw new WebRequestRejected("Expected a scalar setting.");
270
+ return [String(value)];
271
+ }
272
+ const ROLE_FIELDS = {
273
+ model: { set: "--model", clear: "--clear-model" }, effort: { set: "--effort", clear: "--clear-effort" },
274
+ "permission.strategy": { set: "--permission-strategy" }, "permission.sandbox": { set: "--sandbox" },
275
+ "permission.approval": { set: "--approval" }, "permission.mode": { set: "--permission-mode" },
276
+ "permission.allowedTools": { set: "--allowed-tool", clear: "--clear-allowed-tools", many: true },
277
+ "permission.disallowedTools": { set: "--disallowed-tool", clear: "--clear-disallowed-tools", many: true },
278
+ search: { set: "--search", clear: "--clear-search" },
279
+ workspace: { set: "--workspace" }, description: { set: "--description", clear: "--clear-description" },
280
+ systemPrompt: { set: "--system-prompt", clear: "--clear-system-prompt" },
281
+ expectedOutput: { set: "--expected-output", clear: "--clear-expected-output" },
282
+ responsibilities: { set: "--responsibility", clear: "--clear-responsibilities", many: true },
283
+ constraints: { set: "--constraint", clear: "--clear-constraints", many: true },
284
+ skills: { set: "--skill", clear: "--clear-skills", many: true }
285
+ };
286
+ function objectArguments(id, changes) {
287
+ if (CONFIG_DOMAINS.includes(id))
288
+ return [];
289
+ return changes.flatMap(c => {
290
+ if (id.startsWith("role/") && c.key === "activeAgentId")
291
+ return [];
292
+ if (id.startsWith("agent/")) {
293
+ if (c.key === "command")
294
+ return ["--command", text(c.value)];
295
+ if (c.key === "baseArgs") {
296
+ const values = strings(c.value);
297
+ return values.length ? values.flatMap(v => ["--arg", v]) : ["--clear-args"];
298
+ }
299
+ if (!Array.isArray(c.value))
300
+ throw new WebRequestRejected("Expected an environment reference array.");
301
+ return c.value.length ? c.value.flatMap(value => {
302
+ const b = record(value);
303
+ if (Object.keys(b).some(k => !["target", "source", "sourceName", "required"].includes(k))
304
+ || b.source !== "process" || b.required !== true)
305
+ throw new WebRequestRejected("Only required process-variable references are supported by the Agent command.");
306
+ return ["--env", `${text(b.target)}=${text(b.sourceName)}`];
307
+ }) : ["--clear-env"];
308
+ }
309
+ if (id.startsWith("profile/")) {
310
+ if (c.key === "agent")
311
+ return c.value === "" ? ["--inherit-worker"] : ["--agent", text(c.value)];
312
+ if (c.reset)
313
+ return [`--clear-${c.key}`];
314
+ if (c.key === "skills") {
315
+ const values = strings(c.value);
316
+ return values.length ? values.flatMap(v => ["--skill", v]) : ["--clear-skills"];
317
+ }
318
+ return [`--${c.key}`, text(c.value)];
319
+ }
320
+ const spec = ROLE_FIELDS[c.key];
321
+ if (!spec)
322
+ throw new WebRequestRejected("Unsupported Role field.");
323
+ if (c.reset)
324
+ return [spec.clear];
325
+ if (spec.many) {
326
+ const values = strings(c.value);
327
+ return values.length ? values.flatMap(v => [spec.set, v]) : [spec.clear];
328
+ }
329
+ return [spec.set, typeof c.value === "boolean" ? String(c.value) : text(c.value)];
330
+ });
331
+ }
@@ -0,0 +1,86 @@
1
+ import { createHash } from "node:crypto";
2
+ import { listSkillSources, readFrozenSkill, readSkillSource } from "../context/skillCatalog.js";
3
+ import { runGlobalRoleCommand } from "../commands/globalRoleCommands.js";
4
+ import { runTaskCommand } from "../commands/taskCommands.js";
5
+ import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
6
+ function target(input) {
7
+ if (!input || typeof input !== "object")
8
+ throw new WebRequestRejected("Choose an explicit Role scope.");
9
+ const v = input;
10
+ if (!["global", "task"].includes(String(v.scope)) || typeof v.role !== "string" || !v.role
11
+ || (v.scope === "task" ? typeof v.task !== "string" || !v.task : v.task !== undefined && v.task !== "")) {
12
+ throw new WebRequestRejected("Choose an explicit global or Task Role.");
13
+ }
14
+ return { scope: v.scope, role: v.role, ...(v.scope === "task" ? { task: v.task } : {}) };
15
+ }
16
+ const revision = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
17
+ /** Local-human adapter; all writes retain CLI Role guards, validation and revision semantics. */
18
+ export function createWebSkills(store) {
19
+ const home = store.rootDirectory();
20
+ function roleState(input) {
21
+ const selected = target(input);
22
+ const role = selected.scope === "global" ? store.getGlobalRole(selected.role) : store.getRole(selected.task, selected.role);
23
+ if (!role)
24
+ throw new WebRequestRejected("Role not found in the selected scope.");
25
+ const sessions = selected.scope === "global" ? store.getGlobalRoleSessionSet(selected.role)
26
+ : store.getRoleSessionSet(selected.task, selected.role);
27
+ const session = sessions?.sessions[role.activeAgentId];
28
+ return { target: selected, revision: revision(role), skills: role.skills ?? [], launchRevision: role.launchRevision,
29
+ session: session ? { nativeSessionId: session.nativeSessionId, status: session.status,
30
+ sourceDesiredRevision: session.effective.sourceDesiredRevision, packages: session.effective.skillPackages ?? null } : null,
31
+ notice: "Configured bindings apply on a subsequent launch. Saving does not load a Skill, restart a Session, rewrite a frozen Run, or delete a package." };
32
+ }
33
+ return {
34
+ catalog: listSkillSources.bind(null, home),
35
+ roles(scope, task) {
36
+ if (scope !== "global" && scope !== "task")
37
+ throw new WebRequestRejected("Choose global or Task scope.");
38
+ if (scope === "task" && (!task || !store.getTask(task)))
39
+ throw new WebRequestRejected("Task not found.");
40
+ return (scope === "global" ? store.listGlobalRoles() : store.listRoles(task)).map(role => ({ name: role.name }));
41
+ },
42
+ role: roleState,
43
+ file(input) {
44
+ if (!input.frozen)
45
+ return readSkillSource(home, input.id, input.resource);
46
+ const state = roleState(input.target);
47
+ let packages = state.session?.packages;
48
+ if (input.run) {
49
+ if (state.target.scope !== "task")
50
+ throw new WebRequestRejected("A frozen Run requires Task scope.");
51
+ const run = store.getRun(state.target.task, input.run);
52
+ if (!run || run.roleName !== state.target.role)
53
+ throw new WebRequestRejected("Run not found for this Task Role.");
54
+ packages = run.effective.skillPackages;
55
+ }
56
+ const ref = packages?.find(p => p.id === input.id);
57
+ if (!ref)
58
+ throw new WebRequestRejected("No frozen package evidence for this Skill. Current source is not a substitute.");
59
+ if (ref.digest !== input.digest)
60
+ throw new WebRequestRejected("Frozen package version changed or was not specified. Reload its evidence.");
61
+ return readFrozenSkill(ref, input.resource);
62
+ },
63
+ save(input) {
64
+ if (!input || typeof input !== "object")
65
+ throw new WebRequestRejected("Invalid Skill binding request.");
66
+ const body = input;
67
+ if (Object.keys(body).some(k => !["target", "revision", "skills", "acknowledgeLive"].includes(k))
68
+ || typeof body.revision !== "string" || !Array.isArray(body.skills) || body.skills.length > 128
69
+ || body.skills.some(s => typeof s !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(s))
70
+ || (body.acknowledgeLive !== undefined && typeof body.acknowledgeLive !== "boolean")) {
71
+ throw new WebRequestRejected("Expected a Role read revision and Skill IDs.");
72
+ }
73
+ const selected = target(body.target);
74
+ const skills = [...new Set(body.skills)];
75
+ const args = skills.length ? skills.flatMap(id => ["--skill", id]) : ["--clear-skills"];
76
+ const output = webLocalMutation(store, tx => {
77
+ if (roleState(selected).revision !== body.revision)
78
+ throw new Error("Role changed since it was read. Reload before saving.");
79
+ if (selected.scope === "global")
80
+ return runGlobalRoleCommand(["update", selected.role, ...args, ...(body.acknowledgeLive ? ["--yes"] : [])], tx, { yuiHome: home, env: {} });
81
+ return runTaskCommand(["role", "update", selected.task, selected.role, ...args], tx, { yuiHome: home, environment: {} }).output;
82
+ });
83
+ return { status: "saved", output, role: roleState(selected) };
84
+ }
85
+ };
86
+ }
@@ -32,7 +32,7 @@ export function buildWebPageSessions(store, options, now) {
32
32
  return { scope: "current-catalog-page", readAt: now.toISOString(), tasks };
33
33
  });
34
34
  }
35
- export function buildWebTaskDetail(store, taskId, now = new Date()) {
35
+ export function buildWebTaskDetail(store, taskId, now = new Date(), compact = false) {
36
36
  return store.transaction((reader) => {
37
37
  const task = reader.getTask(taskId);
38
38
  if (task === null)
@@ -86,6 +86,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
86
86
  adapterId: activeSession.adapterId,
87
87
  nativeSessionId: activeSession.nativeSessionId ?? null,
88
88
  status: activeSession.status,
89
+ skillPackages: activeSession.effective.skillPackages ?? null,
89
90
  endpointImplementation: activeSession.endpointImplementation ?? null
90
91
  },
91
92
  effectiveLaunchSource: activeRun === undefined
@@ -98,9 +99,44 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
98
99
  const execution = buildTaskExecutionProjection(reader, taskId, now);
99
100
  if (execution === null)
100
101
  return null;
102
+ const brief = reader.getTaskBrief(taskId);
103
+ const roleSessionSets = reader.listRoleSessionSets(taskId);
104
+ const sessions = projectWebSessions({ taskId, sessionSets: roleSessionSets, events, now,
105
+ ...(brief?.updatedAt === undefined ? {} : { semanticProgressAt: brief.updatedAt }),
106
+ policy: resolveRuntimeHealth(reader.getConfig().runtimeHealth) });
107
+ if (compact) {
108
+ // Observation is not semantic progress. No Task/Message/Run bodies,
109
+ // Role history or validation logs ride the default polling response.
110
+ const clip = (text) => text?.slice(0, 1200);
111
+ return {
112
+ execution: {
113
+ status: execution.status, owner: execution.owner, action: execution.action, next: execution.next,
114
+ summary: clip(execution.summary), reason: clip(execution.reason),
115
+ monitoring: execution.monitoring, failClosed: execution.failClosed,
116
+ activeRuns: execution.activeRuns.slice(0, 16).map(run => ({ id: run.id })),
117
+ activeRunCount: execution.activeRuns.length,
118
+ attention: execution.attention.slice(0, 8).map(item => ({ ...item, summary: clip(item.summary) })),
119
+ blockers: execution.blockers.slice(0, 8).map(item => ({ ...item, summary: clip(item.summary) }))
120
+ },
121
+ roles: roles.slice(0, 16).map(role => ({
122
+ name: role.name, status: role.status, runtimeSession: role.runtimeSession
123
+ })),
124
+ sessions: { ...sessions, sessions: sessions.sessions.slice(0, 16).map(session => ({
125
+ ...session, reason: session.reason.slice(0, 1200),
126
+ operations: session.operations.slice(0, 16), background: session.background.slice(0, 16)
127
+ })) },
128
+ observability: {
129
+ cost: execution.observability.cost,
130
+ readyCount: execution.observability.dag.readyIds.length,
131
+ context: {
132
+ snapshotCount: execution.observability.context.snapshotCount,
133
+ totalBytes: execution.observability.context.totalBytes
134
+ }
135
+ }
136
+ };
137
+ }
101
138
  const remoteDelivery = webRemoteDelivery(reader, task);
102
139
  const workItems = reader.listWorkItems(taskId);
103
- const roleSessionSets = reader.listRoleSessionSets(taskId);
104
140
  const workItemObservability = new Map(execution.observability.workItems.map((item) => [item.workItemId, item]));
105
141
  return {
106
142
  task: {
@@ -108,14 +144,10 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
108
144
  ...(projectNames.length === 0 ? {} : { projectNames })
109
145
  },
110
146
  execution,
111
- sessions: projectWebSessions({ taskId, sessionSets: roleSessionSets, events, now,
112
- ...(reader.getTaskBrief(taskId)?.updatedAt === undefined ? {} : {
113
- semanticProgressAt: reader.getTaskBrief(taskId).updatedAt
114
- }),
115
- policy: resolveRuntimeHealth(reader.getConfig().runtimeHealth) }),
147
+ sessions,
116
148
  remoteDelivery,
117
149
  observability: execution.observability,
118
- brief: reader.getTaskBrief(taskId),
150
+ brief,
119
151
  roles,
120
152
  workItems: workItems.map((item) => ({
121
153
  ...item,