@zq-silk/yui 0.14.0 → 0.14.2

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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -119,6 +119,11 @@ function updateAgent(args, store) {
119
119
  + "Stop that Role session before changing Agent launch settings.");
120
120
  }
121
121
  if (changes.adapter) {
122
+ const profile = findAgentProfileReference(tx, id);
123
+ if (profile !== null) {
124
+ throw usageError(`Agent ${id} adapter cannot change because Agent Profile ${profile.id} references it. `
125
+ + "Update that explicit Profile or create a new Agent ID instead.");
126
+ }
122
127
  const binding = findRoleBindingReference(tx, id);
123
128
  if (binding !== null) {
124
129
  throw usageError(`Agent ${id} adapter cannot change because ${describeReference(binding)} references it. `
@@ -156,6 +161,11 @@ function removeAgent(args, store) {
156
161
  throw usageError(`Agent ${id} cannot be removed because ${describeReference(binding)} references it. `
157
162
  + "Migrate or remove that Role binding before removing this Agent.");
158
163
  }
164
+ const profile = findAgentProfileReference(tx, id);
165
+ if (profile !== null) {
166
+ throw usageError(`Agent ${id} cannot be removed because Agent Profile ${profile.id} references it. `
167
+ + "Update, inherit, or remove that Profile first.");
168
+ }
159
169
  const session = findNonStoppedSessionReference(tx, id);
160
170
  if (session !== null) {
161
171
  throw usageError(`Agent ${id} cannot be removed because ${describeReference(session)} `
@@ -227,6 +237,9 @@ function findRoleBindingReference(store, agentId) {
227
237
  }
228
238
  return null;
229
239
  }
240
+ function findAgentProfileReference(store, agentId) {
241
+ return store.listAgentProfiles().find((profile) => profile.runtime.source === "explicit" && profile.runtime.agentId === agentId) ?? null;
242
+ }
230
243
  function findNonStoppedSessionReference(store, agentId) {
231
244
  for (const set of store.listGlobalRoleSessionSets()) {
232
245
  const reference = sessionReference(set, agentId, { scope: "global", roleName: set.owner.roleName });
@@ -5,7 +5,7 @@ import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TAS
5
5
  import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
6
6
  import { resolveTimeZone } from "../output/timePresentation.js";
7
7
  import { defaultTableWidth, renderTable } from "../output/table.js";
8
- import { REVIEW_FINDING_LEDGER_MODES, REVIEW_TRIGGERS } from "../review/reviewConfig.js";
8
+ import { REVIEW_TRIGGERS } from "../review/reviewConfig.js";
9
9
  /**
10
10
  * One uniform key model for every durable setting. Each catalog domain uses
11
11
  * the same show/set/clear contract; strategy settings such as `review` and
@@ -21,8 +21,7 @@ const RECONCILIATION_SET_USAGE = "Runtime config set usage: yui config runtime s
21
21
  const RESOURCES_GC_MODE_SET_USAGE = "Resources config set usage: yui config resources set resources-gc-mode <report|quarantine>.";
22
22
  const RESOURCES_GC_AUTO_QUARANTINE_SET_USAGE = "Resources config set usage: yui config resources set resources-gc-auto-quarantine <true|false>.";
23
23
  const LEADER_NEXT_ACTION_SET_USAGE = `Workflow config set usage: yui config workflow set leader-next-action <${LEADER_NEXT_ACTION_MODES.join("|")}>.`;
24
- const REVIEW_SET_USAGE = "Workflow config set usage: yui config workflow set review --role <global-role> --trigger <always|leader|final> "
25
- + "[--finding-ledger <shadow|enforce>].";
24
+ const REVIEW_SET_USAGE = "Workflow config set usage: yui config workflow set review --role <global-role> --trigger <always|leader|final>.";
26
25
  export function runConfigCommand(domain, args, store) {
27
26
  const keys = configDefinitionsForDomain(domain).map(({ key }) => key);
28
27
  const usage = `${capitalize(domain)} config usage: yui config ${domain} show | yui config ${domain} set <key> <value...> | yui config ${domain} clear <key>.`;
@@ -88,8 +87,7 @@ export function effectiveConfigData(config, domain) {
88
87
  ? null
89
88
  : {
90
89
  roleName: config.review.roleName,
91
- trigger: config.review.trigger,
92
- findingLedger: config.review.findingLedger ?? "shadow"
90
+ trigger: config.review.trigger
93
91
  },
94
92
  completionInstallations: config.completionInstallations ?? {}
95
93
  };
@@ -545,8 +543,7 @@ const CONFIG_KEY_HANDLERS = [
545
543
  showLabel: "Review",
546
544
  showValue: (config) => (config.review === undefined
547
545
  ? "disabled"
548
- : `${config.review.roleName} (${config.review.trigger}; finding ledger: ${config.review.findingLedger ?? "shadow"}`
549
- + ")"),
546
+ : `${config.review.roleName} (${config.review.trigger})`),
550
547
  set(args, store) {
551
548
  if (args.length < 4 || args.length % 2 !== 0)
552
549
  throw usageError(REVIEW_SET_USAGE);
@@ -554,7 +551,7 @@ const CONFIG_KEY_HANDLERS = [
554
551
  for (let index = 0; index < args.length; index += 2) {
555
552
  const name = args[index];
556
553
  const value = args[index + 1];
557
- if (!["--role", "--trigger", "--finding-ledger"].includes(name)
554
+ if (!["--role", "--trigger"].includes(name)
558
555
  || value === undefined
559
556
  || options.has(name)) {
560
557
  throw usageError(REVIEW_SET_USAGE);
@@ -572,23 +569,14 @@ const CONFIG_KEY_HANDLERS = [
572
569
  throw usageError(`Global Role not found: ${roleName}.`);
573
570
  }
574
571
  const trigger = rawTrigger;
575
- const rawLedgerMode = options.get("--finding-ledger")?.trim();
576
- let findingLedger;
577
- if (rawLedgerMode !== undefined) {
578
- if (!REVIEW_FINDING_LEDGER_MODES.includes(rawLedgerMode)) {
579
- throw usageError(REVIEW_SET_USAGE);
580
- }
581
- findingLedger = rawLedgerMode;
582
- }
583
572
  saveConfigKey(store, (config) => ({
584
573
  ...config,
585
574
  review: {
586
575
  roleName,
587
- trigger,
588
- ...(findingLedger === undefined ? {} : { findingLedger })
576
+ trigger
589
577
  }
590
578
  }));
591
- return `Review set to ${roleName} (${trigger}; finding ledger: ${findingLedger ?? "shadow"})\n`;
579
+ return `Review set to ${roleName} (${trigger})\n`;
592
580
  },
593
581
  clear(store) {
594
582
  saveConfigKey(store, (config) => {
@@ -25,13 +25,14 @@ export function runConfigOverview(args, store, environment, identity, roleOption
25
25
  name,
26
26
  store.getGlobalRole(name)
27
27
  ]));
28
- const profiles = store.listAgentProfiles();
28
+ const profileResult = runProfileCommand(["list"], store);
29
+ const profiles = profileResult.data.profiles;
29
30
  const completion = inspectCompletionStates(store.getConfig(), environment, identity);
30
31
  const roleOutput = runGlobalRoleCommand(["list"], store, roleOptions);
31
32
  if (typeof roleOutput !== "string") {
32
33
  throw new Error("Config overview received an invalid Role control result.");
33
34
  }
34
- const profileOutput = runProfileCommand(["list"], store).output;
35
+ const profileOutput = profileResult.output;
35
36
  const agentOutput = runAgentCommand(["list"], store);
36
37
  const domainData = Object.fromEntries(CONFIG_DOMAINS.map((domain) => [
37
38
  domain,
@@ -99,7 +99,7 @@ async function acknowledgeJob(args, options) {
99
99
  if (options.store === undefined) {
100
100
  throw usageError("job acknowledge requires a Task store to resolve the caller.");
101
101
  }
102
- taskLocalActor(options.store, options.environment, ref.taskId, options.home);
102
+ taskLocalActor(options.store, options.environment, ref.taskId);
103
103
  const caller = resolveJobCaller(options.environment, ref.taskId);
104
104
  await ensureFileTaskController(options.home, { environment: options.environment });
105
105
  const result = await acknowledgeDurableJob(options.home, ref.taskId, ref.jobId, caller);
@@ -150,14 +150,9 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
150
150
  }
151
151
  if (report.reviews.status === "ok" && report.reviews.data !== undefined) {
152
152
  const reviews = report.reviews.data;
153
- lines.push("", `Review rounds: ${reviews.total} total · ${reviews.completed} completed · ${reviews.failed} failed`, `Execution failures: ${reviews.infraFailed} · semantic negatives: ${reviews.semanticNegative}`, ...(reviews.deltaRechecks.total === 0
153
+ lines.push("", `Review rounds: ${reviews.total} total · ${reviews.completed} completed · ${reviews.failed} failed`, `Execution failures: ${reviews.infraFailed}`, ...(reviews.deltaRechecks === 0
154
154
  ? []
155
- : [
156
- `Delta-rechecks: ${reviews.deltaRechecks.total} total · `
157
- + `${reviews.deltaRechecks.equivalentAndAccepted} accepted · `
158
- + `${reviews.deltaRechecks.finding} finding · `
159
- + `${reviews.deltaRechecks.requiresFullReview} requiring Task Agent decision`
160
- ]));
155
+ : [`Delta-rechecks: ${reviews.deltaRechecks} total`]));
161
156
  }
162
157
  else {
163
158
  lines.push("", ...sectionError("reviews", report));
@@ -224,7 +219,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
224
219
  { header: "Type", minWidth: 8, maxWidth: 12 },
225
220
  { header: "Turns", minWidth: 4, maxWidth: 6 },
226
221
  { header: "WIs", minWidth: 3, maxWidth: 5 },
227
- { header: "Review F/D/N", minWidth: 12, maxWidth: 16 },
222
+ { header: "Review F/D/X", minWidth: 12, maxWidth: 16 },
228
223
  { header: "Integration A/F/R", minWidth: 17, maxWidth: 20 },
229
224
  { header: "Pre-progress gen", minWidth: 12, maxWidth: 16 },
230
225
  { header: "Terminal ws", minWidth: 10, maxWidth: 12 },
@@ -234,7 +229,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
234
229
  task.taskType ?? "unspecified",
235
230
  String(task.turns.total),
236
231
  String(task.workItems),
237
- `${task.reviews.full}/${task.reviews.delta}/${task.reviews.nonSemantic}`,
232
+ `${task.reviews.full}/${task.reviews.delta}/${task.reviews.failed}`,
238
233
  `${task.integrations.attempts}/${task.integrations.failed}/${task.integrations.repeatedIdentities}`,
239
234
  String(task.providerGenerationsBeforeFirstProgress),
240
235
  String(task.terminalWorkspaceCount),
@@ -281,6 +281,12 @@ function unbindRole(args, store) {
281
281
  assertNoArguments(rest, "Role unbind usage: yui config role unbind <role> <agent-id>");
282
282
  store.transaction((tx) => {
283
283
  const role = requireRole(name, tx);
284
+ const profileId = profileUsingWorkerBinding(tx, role, agentId);
285
+ if (profileId !== null) {
286
+ throw usageError(`Agent ${agentId} cannot be unbound from Global Role worker because Agent Profile `
287
+ + `${profileId} derives non-model settings from that binding. `
288
+ + "Update, inherit, or remove that Profile first.");
289
+ }
284
290
  try {
285
291
  const result = unbindRoleAgent(role, tx.getGlobalRoleSessionSet(name), agentId, new Date());
286
292
  tx.saveGlobalRoleWithSessionSet(result.role, result.sessions);
@@ -291,6 +297,17 @@ function unbindRole(args, store) {
291
297
  });
292
298
  return `Unbound Agent ${agentId} from role ${name}\n`;
293
299
  }
300
+ function profileUsingWorkerBinding(store, role, agentId) {
301
+ if (role.name !== "worker")
302
+ return null;
303
+ if (!Object.hasOwn(role.agentBindings, agentId))
304
+ return null;
305
+ if (agentId === role.activeAgentId)
306
+ return null;
307
+ const profile = store.listAgentProfiles().find((candidate) => (candidate.runtime.source === "explicit"
308
+ && candidate.runtime.agentId === agentId));
309
+ return profile?.id ?? null;
310
+ }
294
311
  function enterRole(args, store) {
295
312
  const [rawName, ...rest] = args;
296
313
  const name = roleName(rawName);
@@ -1,14 +1,56 @@
1
1
  import { usageError } from "../errors/cliError.js";
2
2
  import { defaultTableWidth, renderTable } from "../output/table.js";
3
3
  import { BUILTIN_PROFILE_IDS, builtinAgentProfileInputs, createAgentProfile, updateAgentProfile } from "../profile/agentProfile.js";
4
+ import { resolveAgentProfileRuntime, resolveAgentProfileView } from "../profile/agentProfileRuntime.js";
4
5
  import { parseRoleOptions } from "./roleConfiguration.js";
5
- export function runProfileCommand(args, store, now = () => new Date()) {
6
+ /** Build the exact explicit Profile runtime that a command would persist. */
7
+ export function previewProfileAgentConfigurationMutation(args, store) {
8
+ const [command, ...rest] = args;
9
+ let profile;
10
+ if (command === "add") {
11
+ const usage = "Profile add usage: yui config profile add <id> [--access <read|write>] [Profile settings].";
12
+ const [id, ...tail] = rest;
13
+ if (id === undefined || id.startsWith("--"))
14
+ throw usageError("Profile id is required.", usage);
15
+ const parsed = parseProfileOptions(tail, false, usage);
16
+ assertOptionPairs(parsed);
17
+ const runtime = addedRuntime(parsed, usage);
18
+ if (runtime.source === "global-worker")
19
+ return undefined;
20
+ profile = createAgentProfile({ id, runtime }, new Date(0));
21
+ }
22
+ else if (command === "update") {
23
+ const usage = "Profile update usage: yui config profile update <id> [--access <read|write>] [Profile settings].";
24
+ const [id, ...tail] = rest;
25
+ if (id === undefined || id.startsWith("--"))
26
+ throw usageError("Profile id is required.", usage);
27
+ const parsed = parseProfileOptions(tail, true, usage);
28
+ assertOptionPairs(parsed);
29
+ const current = requireProfile(store, id);
30
+ const runtime = updatedRuntime(current, parsed, usage);
31
+ if (runtime === undefined || runtime.source === "global-worker")
32
+ return undefined;
33
+ profile = updateAgentProfile(current, { runtime }, new Date(Math.max(Date.parse(current.updatedAt) + 1, 1)));
34
+ }
35
+ else {
36
+ return undefined;
37
+ }
38
+ const runtime = resolveAgentProfileRuntime(profile, store);
39
+ if (runtime.status === "unavailable")
40
+ throw usageError(runtime.reason);
41
+ return {
42
+ agentId: runtime.binding.agentId,
43
+ config: runtime.binding.config,
44
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd()
45
+ };
46
+ }
47
+ export function runProfileCommand(args, store, now = () => new Date(), options = {}) {
6
48
  const [command, ...rest] = args;
7
49
  switch (command) {
8
- case "add": return addProfile(rest, store, now());
50
+ case "add": return addProfile(rest, store, now(), options);
9
51
  case "list": return listProfiles(rest, store);
10
52
  case "show": return showProfile(rest, store);
11
- case "update": return updateProfile(rest, store, now());
53
+ case "update": return updateProfile(rest, store, now(), options);
12
54
  case "remove": return removeProfile(rest, store);
13
55
  case "reset": return resetProfiles(rest, store, now());
14
56
  default:
@@ -17,45 +59,53 @@ export function runProfileCommand(args, store, now = () => new Date()) {
17
59
  : `Unknown command: config profile ${command}`);
18
60
  }
19
61
  }
20
- function addProfile(args, store, now) {
62
+ function addProfile(args, store, now, options) {
21
63
  const usage = "Profile add usage: yui config profile add <id> [--access <read|write>] [Profile settings].";
22
64
  const [id, ...tail] = args;
23
65
  if (id === undefined || id.startsWith("--"))
24
66
  throw usageError("Profile id is required.", usage);
25
67
  const parsed = parseProfileOptions(tail, false, usage);
68
+ assertOptionPairs(parsed);
26
69
  const profile = createAgentProfile({
27
70
  id,
28
71
  defaultAccess: parseAccess(parsed.one("--access") ?? "read"),
29
- ...profileValues(parsed)
72
+ ...profileValues(parsed),
73
+ runtime: addedRuntime(parsed, usage)
30
74
  }, now);
75
+ validateExplicitRuntime(profile, store, options);
31
76
  store.transaction((tx) => {
32
77
  if (tx.getAgentProfile(profile.id) !== null) {
33
78
  throw usageError(`Agent Profile already exists: ${profile.id}.`);
34
79
  }
35
80
  tx.saveAgentProfile(profile);
36
81
  });
37
- return { output: `Added Agent Profile ${profile.id}\n`, data: { profile } };
82
+ return {
83
+ output: `Added Agent Profile ${profile.id}\n`,
84
+ data: resolveAgentProfileView(profile, store)
85
+ };
38
86
  }
39
87
  function listProfiles(args, store) {
40
88
  noArgs(args, "Profile list usage: yui config profile list.");
41
- const profiles = store.listAgentProfiles();
89
+ const profiles = store.listAgentProfiles().map((profile) => resolveAgentProfileView(profile, store));
42
90
  const output = profiles.length === 0
43
91
  ? "No Agent Profiles found.\n"
44
92
  : `${renderTable("Agent Profiles", [
45
93
  { header: "Profile", minWidth: 7, maxWidth: 24 },
46
94
  { header: "Revision", minWidth: 8, maxWidth: 10 },
47
95
  { header: "Access", minWidth: 6, maxWidth: 8 },
48
- { header: "Description", minWidth: 12, maxWidth: 54 }
49
- ], profiles.map((profile) => [
50
- profile.id,
51
- String(profile.revision),
52
- profile.defaultAccess,
53
- profile.description ?? "-"
54
- ]), defaultTableWidth())}\n`;
96
+ { header: "Runtime", minWidth: 9, maxWidth: 14 },
97
+ { header: "Worker r", minWidth: 8, maxWidth: 10 },
98
+ { header: "Agent", minWidth: 7, maxWidth: 18 },
99
+ { header: "Model", minWidth: 7, maxWidth: 24 },
100
+ { header: "Effort", minWidth: 7, maxWidth: 12 },
101
+ { header: "Description", minWidth: 12, maxWidth: 48 }
102
+ ], profiles.map(profileListRow), defaultTableWidth())}\n`;
55
103
  return { output, data: { profiles } };
56
104
  }
57
105
  function showProfile(args, store) {
58
106
  const profile = requireProfile(store, oneArg(args, "Profile show usage: yui config profile show <id>."));
107
+ const view = resolveAgentProfileView(profile, store);
108
+ const runtime = view.runtime;
59
109
  return {
60
110
  output: `${[
61
111
  `Agent Profile: ${profile.id}`,
@@ -64,13 +114,23 @@ function showProfile(args, store) {
64
114
  `Description: ${profile.description ?? "-"}`,
65
115
  `Instructions: ${profile.instructions ?? "-"}`,
66
116
  `Skills: ${profile.skills?.join(", ") || "-"}`,
67
- `Model: ${profile.model ?? "-"}`,
68
- `Effort: ${profile.effort ?? "-"}`
117
+ `Runtime source: ${runtime.source}`,
118
+ `Worker revision: ${runtime.source === "global-worker"
119
+ ? runtime.workerRevision ?? "unavailable"
120
+ : "-"}`,
121
+ `Runtime status: ${runtime.status}`,
122
+ ...(runtime.status === "resolved"
123
+ ? [
124
+ `Effective Agent: ${runtime.binding.agentId}/${runtime.binding.adapterId}`,
125
+ `Effective Model: ${runtime.binding.config.model ?? "CLI default"}`,
126
+ `Effective Effort: ${runtime.binding.config.effort ?? "CLI default"}`
127
+ ]
128
+ : [`Runtime unavailable: ${runtime.reason}`])
69
129
  ].join("\n")}\n`,
70
- data: { profile }
130
+ data: view
71
131
  };
72
132
  }
73
- function updateProfile(args, store, now) {
133
+ function updateProfile(args, store, now, options) {
74
134
  const usage = "Profile update usage: yui config profile update <id> [--access <read|write>] [Profile settings].";
75
135
  const [id, ...tail] = args;
76
136
  if (id === undefined || id.startsWith("--"))
@@ -80,14 +140,19 @@ function updateProfile(args, store, now) {
80
140
  throw usageError("At least one Profile option is required.", usage);
81
141
  assertOptionPairs(parsed);
82
142
  const current = requireProfile(store, id);
143
+ const runtime = updatedRuntime(current, parsed, usage);
83
144
  const updated = updateAgentProfile(current, {
84
145
  ...(parsed.has("--access") ? { defaultAccess: parseAccess(parsed.one("--access")) } : {}),
85
- ...profilePatch(parsed)
146
+ ...profilePatch(parsed),
147
+ ...(runtime === undefined ? {} : { runtime })
86
148
  }, now);
149
+ if (runtime !== undefined) {
150
+ validateExplicitRuntime(updated, store, options);
151
+ }
87
152
  store.saveAgentProfile(updated);
88
153
  return {
89
154
  output: `Updated Agent Profile ${updated.id} to revision ${updated.revision}\n`,
90
- data: { profile: updated }
155
+ data: resolveAgentProfileView(updated, store)
91
156
  };
92
157
  }
93
158
  function removeProfile(args, store) {
@@ -111,12 +176,11 @@ function resetProfiles(args, store, now) {
111
176
  description: desired.description,
112
177
  instructions: desired.instructions,
113
178
  skills: desired.skills,
114
- model: desired.model,
115
- effort: desired.effort
179
+ runtime: desired.runtime
116
180
  }, now));
117
181
  }
118
182
  });
119
- const profiles = BUILTIN_PROFILE_IDS.map((id) => requireProfile(store, id));
183
+ const profiles = BUILTIN_PROFILE_IDS.map((id) => resolveAgentProfileView(requireProfile(store, id), store));
120
184
  return {
121
185
  output: `Reset ${BUILTIN_PROFILE_IDS.length} built-in Agent Profiles\n`,
122
186
  data: { profiles }
@@ -127,8 +191,10 @@ const BASE_OPTIONS = [
127
191
  ["--description", "value"],
128
192
  ["--instructions", "value"],
129
193
  ["--skill", "repeatable"],
194
+ ["--agent", "value"],
130
195
  ["--model", "value"],
131
- ["--effort", "value"]
196
+ ["--effort", "value"],
197
+ ["--inherit-worker", "flag"]
132
198
  ];
133
199
  const CLEAR_OPTIONS = [
134
200
  ["--clear-description", "flag"],
@@ -147,9 +213,7 @@ function profileValues(parsed) {
147
213
  return {
148
214
  ...(parsed.has("--description") ? { description: requiredText(parsed.one("--description")) } : {}),
149
215
  ...(parsed.has("--instructions") ? { instructions: requiredText(parsed.one("--instructions")) } : {}),
150
- ...(parsed.has("--skill") ? { skills: parsed.many("--skill").map(requiredText) } : {}),
151
- ...(parsed.has("--model") ? { model: requiredText(parsed.one("--model")) } : {}),
152
- ...(parsed.has("--effort") ? { effort: requiredText(parsed.one("--effort")) } : {})
216
+ ...(parsed.has("--skill") ? { skills: parsed.many("--skill").map(requiredText) } : {})
153
217
  };
154
218
  }
155
219
  function profilePatch(parsed) {
@@ -157,11 +221,117 @@ function profilePatch(parsed) {
157
221
  ...profileValues(parsed),
158
222
  ...(parsed.has("--clear-description") ? { description: undefined } : {}),
159
223
  ...(parsed.has("--clear-instructions") ? { instructions: undefined } : {}),
160
- ...(parsed.has("--clear-skills") ? { skills: undefined } : {}),
161
- ...(parsed.has("--clear-model") ? { model: undefined } : {}),
162
- ...(parsed.has("--clear-effort") ? { effort: undefined } : {})
224
+ ...(parsed.has("--clear-skills") ? { skills: undefined } : {})
163
225
  };
164
226
  }
227
+ function addedRuntime(parsed, usage) {
228
+ assertRuntimeSourceOptions(parsed, usage);
229
+ const agentId = parsed.one("--agent")?.trim();
230
+ if (parsed.has("--inherit-worker") || agentId === undefined) {
231
+ if (parsed.has("--model") || parsed.has("--effort")) {
232
+ throw usageError("--model and --effort require --agent for an explicit Profile.", usage);
233
+ }
234
+ return { source: "global-worker" };
235
+ }
236
+ return {
237
+ source: "explicit",
238
+ agentId: requiredText(agentId),
239
+ ...(parsed.has("--model") ? { model: requiredText(parsed.one("--model")) } : {}),
240
+ ...(parsed.has("--effort") ? { effort: requiredText(parsed.one("--effort")) } : {})
241
+ };
242
+ }
243
+ function updatedRuntime(profile, parsed, usage) {
244
+ assertRuntimeSourceOptions(parsed, usage);
245
+ if (parsed.has("--inherit-worker"))
246
+ return { source: "global-worker" };
247
+ const runtimeOptions = [
248
+ "--agent", "--model", "--effort", "--clear-model", "--clear-effort"
249
+ ].some((option) => parsed.has(option));
250
+ if (!runtimeOptions)
251
+ return undefined;
252
+ const agentId = parsed.one("--agent")?.trim();
253
+ if (profile.runtime.source === "global-worker") {
254
+ if (agentId === undefined) {
255
+ throw usageError("An inherited Profile requires --agent before model or effort can be customized.", usage);
256
+ }
257
+ return {
258
+ source: "explicit",
259
+ agentId: requiredText(agentId),
260
+ ...(parsed.has("--model") ? { model: requiredText(parsed.one("--model")) } : {}),
261
+ ...(parsed.has("--effort") ? { effort: requiredText(parsed.one("--effort")) } : {})
262
+ };
263
+ }
264
+ const nextAgentId = agentId ?? profile.runtime.agentId;
265
+ const changesAgent = nextAgentId !== profile.runtime.agentId;
266
+ if (changesAgent) {
267
+ for (const [field, setOption, clearOption] of [
268
+ ["model", "--model", "--clear-model"],
269
+ ["effort", "--effort", "--clear-effort"]
270
+ ]) {
271
+ if (profile.runtime[field] !== undefined
272
+ && !parsed.has(setOption)
273
+ && !parsed.has(clearOption)) {
274
+ throw usageError(`Changing --agent requires ${setOption} or ${clearOption} so the old ${field} is not reused.`, usage);
275
+ }
276
+ }
277
+ return {
278
+ source: "explicit",
279
+ agentId: requiredText(nextAgentId),
280
+ ...(parsed.has("--model") ? { model: requiredText(parsed.one("--model")) } : {}),
281
+ ...(parsed.has("--effort") ? { effort: requiredText(parsed.one("--effort")) } : {})
282
+ };
283
+ }
284
+ return {
285
+ source: "explicit",
286
+ agentId: profile.runtime.agentId,
287
+ ...(parsed.has("--clear-model")
288
+ ? {}
289
+ : parsed.has("--model")
290
+ ? { model: requiredText(parsed.one("--model")) }
291
+ : profile.runtime.model === undefined ? {} : { model: profile.runtime.model }),
292
+ ...(parsed.has("--clear-effort")
293
+ ? {}
294
+ : parsed.has("--effort")
295
+ ? { effort: requiredText(parsed.one("--effort")) }
296
+ : profile.runtime.effort === undefined ? {} : { effort: profile.runtime.effort })
297
+ };
298
+ }
299
+ function assertRuntimeSourceOptions(parsed, usage) {
300
+ if (!parsed.has("--inherit-worker"))
301
+ return;
302
+ if (["--agent", "--model", "--effort", "--clear-model", "--clear-effort"]
303
+ .some((option) => parsed.has(option))) {
304
+ throw usageError("--inherit-worker cannot be combined with explicit Agent, model, or effort options.", usage);
305
+ }
306
+ }
307
+ function validateExplicitRuntime(profile, store, options) {
308
+ if (profile.runtime.source !== "explicit")
309
+ return;
310
+ const runtime = resolveAgentProfileRuntime(profile, store);
311
+ if (runtime.status === "unavailable")
312
+ throw usageError(runtime.reason);
313
+ options.validateAgentConfiguration?.({
314
+ agentId: runtime.binding.agentId,
315
+ config: runtime.binding.config,
316
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd()
317
+ });
318
+ }
319
+ function profileListRow(view) {
320
+ const { profile, runtime } = view;
321
+ return [
322
+ profile.id,
323
+ String(profile.revision),
324
+ profile.defaultAccess,
325
+ runtime.source,
326
+ runtime.source === "global-worker"
327
+ ? runtime.workerRevision === undefined ? "-" : String(runtime.workerRevision)
328
+ : "-",
329
+ runtime.status === "resolved" ? runtime.binding.agentId : "unavailable",
330
+ runtime.status === "resolved" ? runtime.binding.config.model ?? "CLI default" : "-",
331
+ runtime.status === "resolved" ? runtime.binding.config.effort ?? "CLI default" : "-",
332
+ profile.description ?? "-"
333
+ ];
334
+ }
165
335
  function assertOptionPairs(parsed) {
166
336
  for (const [value, clear] of [
167
337
  ["--description", "--clear-description"],
@@ -104,7 +104,7 @@ function sameStopCandidate(session, candidate) {
104
104
  return session.agentId === candidate.agentId
105
105
  && session.adapterId === candidate.adapterId
106
106
  && session.nativeSessionId === candidate.nativeSessionId
107
- && session.launchId === candidate.launchId
107
+ && session.runtimeGenerationId === candidate.runtimeGenerationId
108
108
  && session.sessionUpdatedAt === candidate.sessionUpdatedAt;
109
109
  }
110
110
  function renderSessionOwner(session) {