@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
@@ -1,16 +1,13 @@
1
- import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
1
  export const FAULT_CLASSES = [
3
- "provider-transient",
4
- "policy-denied",
5
2
  "session-dead",
6
3
  "delivery-uncertain",
7
- "storage-backend-lock",
4
+ "result-missing",
5
+ "runtime-failure",
6
+ "workspace-state",
8
7
  "review-infra",
9
- "review-semantic-negative",
10
8
  "integration-environment",
11
9
  "integration-candidate-failure",
12
10
  "stale-base-target-cas",
13
- "archive-resource-leak",
14
11
  "other"
15
12
  ];
16
13
  export const NO_FAULT = Object.freeze({
@@ -18,87 +15,76 @@ export const NO_FAULT = Object.freeze({
18
15
  basis: "none",
19
16
  evidence: ""
20
17
  });
21
- const PROVIDER_TRANSIENT_PATTERN = /\b5\d{2}\b|gateway time|connection lost|server error|overloaded|rate.?limit|ECONNRESET|socket hang up|API Error|nonstream call error|mid-response/iu;
22
- const POLICY_DENIED_PATTERN = /policy|permission denied|forbidden|not authorized|\b403\b/iu;
23
- const SESSION_DEAD_PATTERN = /tmux session exited|session cannot be replaced|could not start|pane[^\n]*(dead|exited)|session[^\n]*(dead|exited|broken)|native session|launch reservation/iu;
24
- const STORAGE_LOCK_PATTERN = /storage lock|database is locked|SQLITE_BUSY|lock timeout|COMMAND_TIMED_OUT|storage conflict|timed out waiting/iu;
25
- const DELIVERY_UNCERTAIN_PATTERN = /delivery[^\n]*(uncertain|unknown)|uncertain[^\n]*delivery|push[^\n]*uncertain/iu;
26
- const STALE_BASE_PATTERN = /stale (role|agent|base|target|state)|expected head|base[^\n]*(changed|moved)|conflict|CAS/iu;
27
18
  const INTEGRATION_ENVIRONMENT_PATTERN = /tsc: not found|command not found|ENOENT|runner disappeared|runner vanished|dirty target|wrong argument|not a git repository|npm error|node: not found/iu;
28
19
  function excerpt(text, max = 160) {
29
20
  const normalized = text.replace(/\s+/g, " ").trim();
30
21
  return normalized.length <= max ? normalized : `${normalized.slice(0, max)}…`;
31
22
  }
32
- /**
33
- * Classify a failed Turn. A structured hint (from a future capability
34
- * provider) always wins; otherwise the summary is matched as historical
35
- * import text. Non-failed Turns return {@link NO_FAULT}.
36
- */
37
- export function classifyTurnFailure(run, structured) {
23
+ /** Classify a failed Turn without inspecting Agent-authored output. */
24
+ export function classifyTurnFailure(run) {
38
25
  if (run.status !== "failed")
39
26
  return NO_FAULT;
40
- if (structured !== undefined) {
27
+ const failureReason = run.result?.failureReason;
28
+ if (failureReason === "cancelled")
29
+ return NO_FAULT;
30
+ if (failureReason === "delivery-unknown") {
41
31
  return {
42
- faultClass: structured.faultClass,
43
- basis: "structured",
44
- evidence: structured.evidence
32
+ faultClass: "delivery-uncertain",
33
+ basis: "core-fact",
34
+ evidence: failureReason
45
35
  };
46
36
  }
47
- const summary = run.result?.output ?? "";
48
- if (summary.length === 0) {
49
- return { faultClass: "other", basis: "text-historical", evidence: "" };
50
- }
51
- const text = excerpt(summary);
52
- if (POLICY_DENIED_PATTERN.test(summary)) {
53
- return { faultClass: "policy-denied", basis: "text-historical", evidence: text };
54
- }
55
- if (PROVIDER_TRANSIENT_PATTERN.test(summary)) {
56
- return { faultClass: "provider-transient", basis: "text-historical", evidence: text };
57
- }
58
- if (STORAGE_LOCK_PATTERN.test(summary)) {
59
- return { faultClass: "storage-backend-lock", basis: "text-historical", evidence: text };
60
- }
61
- if (DELIVERY_UNCERTAIN_PATTERN.test(summary)) {
62
- return { faultClass: "delivery-uncertain", basis: "text-historical", evidence: text };
37
+ if (failureReason === "startup-failed") {
38
+ return {
39
+ faultClass: "session-dead",
40
+ basis: "core-fact",
41
+ evidence: failureReason
42
+ };
63
43
  }
64
- if (STALE_BASE_PATTERN.test(summary)) {
65
- return { faultClass: "stale-base-target-cas", basis: "text-historical", evidence: text };
44
+ if (failureReason === "missing-result") {
45
+ return {
46
+ faultClass: "result-missing",
47
+ basis: "core-fact",
48
+ evidence: failureReason
49
+ };
66
50
  }
67
- if (SESSION_DEAD_PATTERN.test(summary)) {
68
- return { faultClass: "session-dead", basis: "text-historical", evidence: text };
51
+ if (failureReason === "runtime-failed") {
52
+ return {
53
+ faultClass: "runtime-failure",
54
+ basis: "core-fact",
55
+ evidence: failureReason
56
+ };
69
57
  }
70
- return { faultClass: "other", basis: "text-historical", evidence: text };
71
- }
72
- /**
73
- * Review execution failure (the Round itself failed to execute/deliver) is
74
- * `review-infra`; a completed Round with failed checks is a semantic negative.
75
- */
76
- export function classifyReviewRound(round, evidence) {
77
- const outcome = classifyReviewRoundOutcome(round, evidence);
78
- if (outcome?.kind === "non-semantic") {
58
+ if (failureReason === "workspace-unavailable"
59
+ || failureReason === "workspace-dirty"
60
+ || failureReason === "workspace-branch-mismatch") {
79
61
  return {
80
- faultClass: "review-infra",
81
- basis: "structured",
82
- evidence: outcome.reason
62
+ faultClass: "workspace-state",
63
+ basis: "core-fact",
64
+ evidence: failureReason
83
65
  };
84
66
  }
85
- if (outcome?.kind === "semantic" && (round.checks ?? []).some((c) => c.outcome === "failed")) {
86
- const failed = (round.checks ?? [])
87
- .filter((c) => c.outcome === "failed")
88
- .map((c) => c.name)
89
- .join(",");
67
+ return {
68
+ faultClass: "other",
69
+ basis: "core-fact",
70
+ evidence: failureReason ?? "failed without Core failure reason"
71
+ };
72
+ }
73
+ /** ReviewRound failure is an execution fault; completed prose is not classified. */
74
+ export function classifyReviewRound(round) {
75
+ if (round.status === "failed") {
90
76
  return {
91
- faultClass: "review-semantic-negative",
92
- basis: "structured",
93
- evidence: `failed checks: ${failed}`
77
+ faultClass: "review-infra",
78
+ basis: "core-fact",
79
+ evidence: round.failure?.message ?? "Review execution failed without Core failure detail."
94
80
  };
95
81
  }
96
82
  return NO_FAULT;
97
83
  }
98
84
  /**
99
85
  * Integration failure classes: environment/toolchain failure, stale base/CAS
100
- * conflict, or candidate failure. Conflict reports are structured; check
101
- * details are matched as historical text.
86
+ * conflict, or candidate failure. Conflict/check records are Core-owned, but
87
+ * environment attribution still uses a regex over their diagnostic text.
102
88
  */
103
89
  export function classifyIntegrationAttempt(attempt) {
104
90
  if (attempt.status !== "failed")
@@ -106,7 +92,7 @@ export function classifyIntegrationAttempt(attempt) {
106
92
  if (attempt.conflict !== undefined) {
107
93
  return {
108
94
  faultClass: "stale-base-target-cas",
109
- basis: "structured",
95
+ basis: "core-fact",
110
96
  evidence: excerpt(attempt.conflict.summary)
111
97
  };
112
98
  }
@@ -116,20 +102,20 @@ export function classifyIntegrationAttempt(attempt) {
116
102
  if (INTEGRATION_ENVIRONMENT_PATTERN.test(checkText)) {
117
103
  return {
118
104
  faultClass: "integration-environment",
119
- basis: "text-historical",
105
+ basis: "core-diagnostic",
120
106
  evidence: excerpt(checkText)
121
107
  };
122
108
  }
123
109
  if ((attempt.checks ?? []).some((c) => c.outcome === "failed")) {
124
110
  return {
125
111
  faultClass: "integration-candidate-failure",
126
- basis: "structured",
112
+ basis: "core-fact",
127
113
  evidence: excerpt(checkText)
128
114
  };
129
115
  }
130
116
  return {
131
117
  faultClass: "other",
132
- basis: "structured",
118
+ basis: "core-fact",
133
119
  evidence: "failed without checks or conflict"
134
120
  };
135
121
  }
@@ -138,8 +124,10 @@ export function emptyFaultClassCounts() {
138
124
  }
139
125
  export function countFaultClasses(classifications) {
140
126
  const counts = new Map(FAULT_CLASSES.map((name) => [name, 0]));
141
- for (const { faultClass } of classifications) {
142
- counts.set(faultClass, (counts.get(faultClass) ?? 0) + 1);
127
+ for (const classification of classifications) {
128
+ if (classification.basis === "none")
129
+ continue;
130
+ counts.set(classification.faultClass, (counts.get(classification.faultClass) ?? 0) + 1);
143
131
  }
144
132
  return Object.freeze(Object.fromEntries(counts));
145
133
  }
@@ -1,19 +1,10 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
2
+ import { isCompletedReviewExecutionFromTurns } from "../review/reviewAcceptance.js";
3
3
  import { projectFirstProgressAdvisory } from "../runtime/firstProgressAdvisory.js";
4
4
  /** One Task's orchestration cost and advisory projection, with no writes. */
5
5
  export function projectTaskOrchestration(facts) {
6
- const evidence = {
7
- listTurns: () => facts.turns,
8
- listReviewFindings: () => facts.reviewFindings,
9
- listEvents: () => facts.events
10
- };
11
6
  const fullRounds = facts.reviewRounds.filter((round) => round.deltaRecheck === undefined);
12
7
  const deltaRounds = facts.reviewRounds.filter((round) => round.deltaRecheck !== undefined);
13
- const classifications = new Map(facts.reviewRounds.map((round) => [round.id, classifyReviewRoundOutcome(round, evidence)]));
14
- const semanticRounds = facts.reviewRounds.filter((round) => (classifications.get(round.id)?.kind === "semantic"));
15
- const p1P2Findings = facts.reviewFindings.filter((finding) => ((finding.severity === "p1" || finding.severity === "p2")
16
- && semanticRounds.some((round) => round.id === finding.firstReviewRoundId))).length;
17
8
  const candidateTimes = [
18
9
  ...facts.changeSets.map(({ createdAt }) => createdAt),
19
10
  ...facts.workItems.flatMap((item) => item.candidates
@@ -43,7 +34,7 @@ export function projectTaskOrchestration(facts) {
43
34
  reviewRounds: facts.reviewRounds,
44
35
  integrations: facts.integrations
45
36
  });
46
- const advisories = projectAdvisories(facts, classifications, fullRounds, repeatedIdentities, firstProgress.attentionRecommended);
37
+ const advisories = projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgress.attentionRecommended);
47
38
  const publicationAt = facts.task.completedAt === undefined
48
39
  ? undefined
49
40
  : facts.publications
@@ -66,12 +57,7 @@ export function projectTaskOrchestration(facts) {
66
57
  reviews: {
67
58
  full: fullRounds.length,
68
59
  delta: deltaRounds.length,
69
- nonSemantic: [...classifications.values()].filter((value) => value?.kind === "non-semantic").length,
70
- ambiguous: [...classifications.values()].filter((value) => value?.kind === "ambiguous").length,
71
- p1P2Findings,
72
- p1P2FindingsPerSemanticReview: semanticRounds.length === 0
73
- ? 0
74
- : p1P2Findings / semanticRounds.length
60
+ failed: facts.reviewRounds.filter(({ status }) => status === "failed").length
75
61
  },
76
62
  integrations: {
77
63
  attempts: facts.integrations.length,
@@ -87,7 +73,7 @@ export function projectTaskOrchestration(facts) {
87
73
  advisories
88
74
  });
89
75
  }
90
- function projectAdvisories(facts, classifications, fullRounds, repeatedIdentities, firstProgressAttention) {
76
+ function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressAttention) {
91
77
  const result = [];
92
78
  if (facts.task.type === "bugfix" && facts.workItems.length > 0) {
93
79
  result.push({
@@ -96,26 +82,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
96
82
  refs: facts.workItems.map(({ id }) => `work-item:${id}`)
97
83
  });
98
84
  }
99
- const repairItems = facts.workItems.filter((item) => (item.acceptance.some((line) => line.startsWith("review-finding:"))));
100
- const byRound = new Map();
101
- for (const item of repairItems) {
102
- const roundIds = new Set(item.acceptance.flatMap((line) => {
103
- const findingId = line.startsWith("review-finding:") ? line.slice("review-finding:".length) : "";
104
- const finding = facts.reviewFindings.find(({ id }) => id === findingId);
105
- return finding === undefined ? [] : [finding.firstReviewRoundId];
106
- }));
107
- for (const roundId of roundIds)
108
- byRound.set(roundId, [...(byRound.get(roundId) ?? []), item]);
109
- }
110
- for (const [roundId, items] of byRound) {
111
- if (items.length < 2 || hasRepairFanoutDecision(facts.decisions, roundId, items))
112
- continue;
113
- result.push({
114
- code: "review-repair-fanout",
115
- reason: `Findings from Review ${roundId} were split across ${items.length} WorkItems without a durable Decision explaining independent ownership.`,
116
- refs: [`review-round:${roundId}`, ...items.map(({ id }) => `work-item:${id}`)]
117
- });
118
- }
119
85
  if (repeatedIdentities > 0) {
120
86
  result.push({
121
87
  code: "repeated-integration-check",
@@ -125,19 +91,17 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
125
91
  .map(({ id }) => `durable-job:${id}`)
126
92
  });
127
93
  }
128
- const semanticFull = fullRounds.filter((round) => classifications.get(round.id)?.kind === "semantic")
94
+ const completedFull = fullRounds.filter((round) => (isCompletedReviewExecutionFromTurns(round, facts.turns)))
129
95
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
130
- const recent = semanticFull.slice(-3);
131
- const recentIds = new Set(recent.map(({ id }) => id));
132
- const newFinding = facts.reviewFindings.some(({ firstReviewRoundId }) => recentIds.has(firstReviewRoundId));
96
+ const recent = completedFull.slice(-3);
133
97
  const sameCandidate = recent.length === 3
134
98
  && recent.every((round) => isDeepStrictEqual(round.taskCandidate, recent[0].taskCandidate));
135
99
  const sameReviewer = recent.length === 3
136
100
  && recent.every((round) => round.reviewerRoleName === recent[0].reviewerRoleName);
137
- if (sameCandidate && sameReviewer && !newFinding) {
101
+ if (sameCandidate && sameReviewer) {
138
102
  result.push({
139
103
  code: "repeated-full-review",
140
- reason: `The same Reviewer completed three full semantic Reviews of the same frozen candidate without a new finding; this is a cost advisory, not a Review limit.`,
104
+ reason: `The same Reviewer completed three full Reviews of the same frozen candidate; Core does not inspect their result text, so this is only a cost advisory.`,
141
105
  refs: recent.map(({ id }) => `review-round:${id}`)
142
106
  });
143
107
  }
@@ -150,12 +114,6 @@ function projectAdvisories(facts, classifications, fullRounds, repeatedIdentitie
150
114
  }
151
115
  return result;
152
116
  }
153
- function hasRepairFanoutDecision(decisions, roundId, items) {
154
- return decisions.some((decision) => {
155
- const text = `${decision.title}\n${decision.rationale}`;
156
- return text.includes(roundId) && items.every(({ id }) => text.includes(id));
157
- });
158
- }
159
117
  function terminalWorkspaceCount(facts) {
160
118
  return facts.managedWorkspaces.filter(({ owner }) => {
161
119
  if (owner.type === "task")
@@ -8,7 +8,7 @@ export const BUILTIN_PROFILE_IDS = Object.freeze([
8
8
  export function createAgentProfile(input, now) {
9
9
  const timestamp = now.toISOString();
10
10
  return validateAgentProfile({
11
- schemaVersion: 2,
11
+ schemaVersion: 3,
12
12
  id: requireIdentity(input.id, "Agent Profile id"),
13
13
  revision: 1,
14
14
  ...normalizeInput(input),
@@ -24,15 +24,15 @@ export function updateAgentProfile(profile, patch, now) {
24
24
  revision: profile.revision + 1,
25
25
  updatedAt: now.toISOString()
26
26
  };
27
- for (const key of ["description", "instructions", "skills", "model", "effort"]) {
27
+ for (const key of ["description", "instructions", "skills"]) {
28
28
  if (candidate[key] === undefined)
29
29
  delete candidate[key];
30
30
  }
31
31
  return validateAgentProfile(candidate);
32
32
  }
33
33
  export function validateAgentProfile(profile) {
34
- if (profile.schemaVersion !== 2) {
35
- throw new Error("AgentProfile must use schemaVersion 2.");
34
+ if (profile.schemaVersion !== 3) {
35
+ throw new Error("AgentProfile must use schemaVersion 3.");
36
36
  }
37
37
  requireIdentity(profile.id, "Agent Profile id");
38
38
  requirePositiveInteger(profile.revision, "Agent Profile revision");
@@ -43,10 +43,7 @@ export function validateAgentProfile(profile) {
43
43
  optionalText(profile.instructions, "Profile instructions");
44
44
  if (profile.skills !== undefined)
45
45
  normalizedUniqueText(profile.skills, "Profile skill");
46
- if (profile.model !== undefined)
47
- optionalText(profile.model, "Profile model");
48
- if (profile.effort !== undefined)
49
- optionalText(profile.effort, "Profile effort");
46
+ validateAgentProfileRuntime(profile.runtime);
50
47
  requireTimestamp(profile.createdAt, "Agent Profile createdAt");
51
48
  requireTimestamp(profile.updatedAt, "Agent Profile updatedAt");
52
49
  if (Date.parse(profile.updatedAt) < Date.parse(profile.createdAt)) {
@@ -59,24 +56,28 @@ export function builtinAgentProfileInputs() {
59
56
  {
60
57
  id: "worker",
61
58
  description: "Implement and validate one bounded delegated WorkItem.",
62
- defaultAccess: "write"
59
+ defaultAccess: "write",
60
+ runtime: { source: "global-worker" }
63
61
  },
64
62
  {
65
63
  id: "explorer",
66
64
  description: "Inspect sources and return evidence without modifying them.",
67
65
  instructions: "Do not modify files or external state.",
68
- defaultAccess: "read"
66
+ defaultAccess: "read",
67
+ runtime: { source: "global-worker" }
69
68
  },
70
69
  {
71
70
  id: "implementer",
72
71
  description: "Implement and validate one bounded result.",
73
- defaultAccess: "write"
72
+ defaultAccess: "write",
73
+ runtime: { source: "global-worker" }
74
74
  },
75
75
  {
76
76
  id: "reviewer",
77
77
  description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
78
78
  instructions: "Start from user intent and acceptance criteria. Inspect the complete relevant change and report only reachable, material, actionable problems with direct evidence. Separate defects from verification gaps, and prefer the smallest sufficient correction. Follow the bound Project's Policy and Knowledge for build, test, migration, release, and review expectations; do not import rules from another Project or Task. For normal software delivery, review the frozen Task result as one final ReviewRound rather than inventing a per-WorkItem protocol unless the Project Policy explicitly requires one. A Task-final Round has no synthetic WorkItem anchor, and the Reviewer Session and physical workspace continue across changed-head Rounds without reusing an earlier verdict. The current Turn's frozen candidate remains the only scope even if Task main advances. For Delta Recheck, return equivalent-and-accepted, finding, or requires-full-review with explicit reasoning; never create the next Round. Do not turn speculative or extreme edge cases into new state, retries, fallbacks, or protocol. In the Review workspace you may edit source or tests, run local checks, and optionally commit diagnostic evidence. Never push, integrate, mutate Task state, touch another workspace or stable checkout, or write the real Yui control-plane home. End the Provider turn with complete findings, checks actually run, uncertainty, and bounded next actions; Yui preserves the full free-form report automatically. Expose evidence and options to the Leader, who decides.",
79
- defaultAccess: "write"
79
+ defaultAccess: "write",
80
+ runtime: { source: "global-worker" }
80
81
  }
81
82
  ];
82
83
  }
@@ -92,8 +93,7 @@ function normalizeInput(input) {
92
93
  ...(input.skills === undefined
93
94
  ? {}
94
95
  : { skills: normalizedUniqueText(input.skills, "Profile skill") }),
95
- ...(input.model === undefined ? {} : { model: optionalText(input.model, "Profile model") }),
96
- ...(input.effort === undefined ? {} : { effort: optionalText(input.effort, "Profile effort") })
96
+ runtime: normalizeRuntime(input.runtime ?? { source: "global-worker" })
97
97
  };
98
98
  }
99
99
  function normalizePatch(patch) {
@@ -101,7 +101,7 @@ function normalizePatch(patch) {
101
101
  if (Object.hasOwn(patch, "defaultAccess")) {
102
102
  result.defaultAccess = validatedAccess(patch.defaultAccess);
103
103
  }
104
- for (const key of ["description", "instructions", "model", "effort"]) {
104
+ for (const key of ["description", "instructions"]) {
105
105
  if (!Object.hasOwn(patch, key))
106
106
  continue;
107
107
  result[key] = patch[key] === undefined
@@ -113,8 +113,53 @@ function normalizePatch(patch) {
113
113
  ? undefined
114
114
  : normalizedUniqueText(patch.skills, "Profile skill");
115
115
  }
116
+ if (Object.hasOwn(patch, "runtime")) {
117
+ if (patch.runtime === undefined) {
118
+ throw new Error("Agent Profile runtime is required.");
119
+ }
120
+ result.runtime = normalizeRuntime(patch.runtime);
121
+ }
116
122
  return result;
117
123
  }
124
+ function normalizeRuntime(runtime) {
125
+ validateAgentProfileRuntime(runtime);
126
+ return runtime.source === "global-worker"
127
+ ? { source: "global-worker" }
128
+ : {
129
+ source: "explicit",
130
+ agentId: requireIdentity(runtime.agentId, "Profile Agent id"),
131
+ ...(runtime.model === undefined
132
+ ? {}
133
+ : { model: optionalText(runtime.model, "Profile model") }),
134
+ ...(runtime.effort === undefined
135
+ ? {}
136
+ : { effort: optionalText(runtime.effort, "Profile effort") })
137
+ };
138
+ }
139
+ function validateAgentProfileRuntime(runtime) {
140
+ if (runtime === null || typeof runtime !== "object" || Array.isArray(runtime)) {
141
+ throw new Error("Agent Profile runtime is invalid.");
142
+ }
143
+ const source = runtime.source;
144
+ if (source === "global-worker") {
145
+ if (Object.keys(runtime).some((key) => key !== "source")) {
146
+ throw new Error("Worker-inherited Agent Profile runtime cannot persist Agent settings.");
147
+ }
148
+ return;
149
+ }
150
+ if (source !== "explicit") {
151
+ throw new Error(`Agent Profile runtime source is invalid: ${String(source)}.`);
152
+ }
153
+ if (Object.keys(runtime).some((key) => (key !== "source" && key !== "agentId" && key !== "model" && key !== "effort"))) {
154
+ throw new Error("Explicit Agent Profile runtime contains unsupported fields.");
155
+ }
156
+ const explicit = runtime;
157
+ requireIdentity(explicit.agentId, "Profile Agent id");
158
+ if (explicit.model !== undefined)
159
+ optionalText(explicit.model, "Profile model");
160
+ if (explicit.effort !== undefined)
161
+ optionalText(explicit.effort, "Profile effort");
162
+ }
118
163
  function validatedAccess(value) {
119
164
  if (value === undefined)
120
165
  throw new Error("Agent Profile default access is required.");
@@ -0,0 +1,100 @@
1
+ import { createRoleAgentBinding } from "../role/role.js";
2
+ /** The single read model for persisted Profile intent and current runtime. */
3
+ export function resolveAgentProfileView(profile, store) {
4
+ return {
5
+ profile,
6
+ runtime: resolveAgentProfileRuntime(profile, store)
7
+ };
8
+ }
9
+ /**
10
+ * Resolve one Profile into a complete Role binding. Inherited Profiles copy
11
+ * the current Global Worker active binding verbatim. An explicit Profile with
12
+ * a matching Worker binding preserves that complete binding, whether active or
13
+ * dormant, and overlays its own model/effort; another Agent starts from its
14
+ * provider defaults.
15
+ */
16
+ export function resolveAgentProfileRuntime(profile, store) {
17
+ if (profile.runtime.source === "global-worker") {
18
+ const worker = store.getGlobalRole("worker");
19
+ if (worker === null) {
20
+ return {
21
+ status: "unavailable",
22
+ source: "global-worker",
23
+ reason: "Global Role worker is not configured."
24
+ };
25
+ }
26
+ const binding = worker.agentBindings[worker.activeAgentId];
27
+ if (binding === undefined) {
28
+ return {
29
+ status: "unavailable",
30
+ source: "global-worker",
31
+ workerRevision: worker.launchRevision,
32
+ reason: `Global Role worker active Agent is not bound: ${worker.activeAgentId}.`
33
+ };
34
+ }
35
+ const agent = store.getConfiguredAgent(binding.agentId);
36
+ if (agent === null) {
37
+ return {
38
+ status: "unavailable",
39
+ source: "global-worker",
40
+ workerRevision: worker.launchRevision,
41
+ reason: `Global Role worker Agent is not configured: ${binding.agentId}.`
42
+ };
43
+ }
44
+ if (agent.adapterId !== binding.adapterId) {
45
+ return {
46
+ status: "unavailable",
47
+ source: "global-worker",
48
+ workerRevision: worker.launchRevision,
49
+ reason: `Global Role worker Agent adapter does not match its binding: ${binding.agentId}.`
50
+ };
51
+ }
52
+ return {
53
+ status: "resolved",
54
+ source: "global-worker",
55
+ workerRevision: worker.launchRevision,
56
+ binding: createRoleAgentBinding(agent, binding.config)
57
+ };
58
+ }
59
+ const agent = store.getConfiguredAgent(profile.runtime.agentId);
60
+ if (agent === null) {
61
+ return {
62
+ status: "unavailable",
63
+ source: "explicit",
64
+ reason: `Configured Agent not found: ${profile.runtime.agentId}.`
65
+ };
66
+ }
67
+ const worker = store.getGlobalRole("worker");
68
+ const workerBinding = worker?.agentBindings[agent.id];
69
+ if (workerBinding !== undefined && workerBinding.adapterId !== agent.adapterId) {
70
+ return {
71
+ status: "unavailable",
72
+ source: "explicit",
73
+ reason: `Global Role worker Agent adapter does not match its binding: ${agent.id}.`
74
+ };
75
+ }
76
+ const base = workerBinding === undefined
77
+ ? createRoleAgentBinding(agent)
78
+ : createRoleAgentBinding(agent, workerBinding.config);
79
+ const config = structuredClone(base.config);
80
+ if (profile.runtime.model === undefined)
81
+ delete config.model;
82
+ else
83
+ config.model = profile.runtime.model;
84
+ if (profile.runtime.effort === undefined)
85
+ delete config.effort;
86
+ else
87
+ config.effort = profile.runtime.effort;
88
+ return {
89
+ status: "resolved",
90
+ source: "explicit",
91
+ binding: createRoleAgentBinding(agent, config)
92
+ };
93
+ }
94
+ export function requireResolvedAgentProfileRuntime(profile, store) {
95
+ const runtime = resolveAgentProfileRuntime(profile, store);
96
+ if (runtime.status === "unavailable") {
97
+ throw new Error(`Agent Profile ${profile.id} runtime is unavailable: ${runtime.reason}`);
98
+ }
99
+ return runtime;
100
+ }
@@ -1,10 +1,7 @@
1
- import { execFile } from "node:child_process";
2
1
  import { createHash, randomBytes } from "node:crypto";
3
- import { accessSync, constants } from "node:fs";
4
2
  import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
5
3
  import { tmpdir } from "node:os";
6
- import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
7
- import { promisify } from "node:util";
4
+ import { dirname, join, resolve } from "node:path";
8
5
  import { runUpdate } from "../cli/updateOrchestrator.js";
9
6
  import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
10
7
  import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
@@ -14,25 +11,9 @@ import { isConcreteVersion } from "../domain/validation.js";
14
11
  import { resolveProject } from "../repository/project.js";
15
12
  import { resolveVerificationGate } from "../verification/verificationGateService.js";
16
13
  import { findL2ArtifactForCommit } from "../verification/gateArtifactStore.js";
17
- const defaultRunCommand = async (command, args, cwd, env) => {
18
- const exec = promisify(execFile);
19
- try {
20
- const { stdout, stderr } = await exec(command, args, {
21
- cwd,
22
- maxBuffer: 16 * 1024 * 1024,
23
- ...(env === undefined ? {} : { env: { ...process.env, ...env } })
24
- });
25
- return { code: 0, stdout, stderr };
26
- }
27
- catch (error) {
28
- const failure = error;
29
- return {
30
- code: typeof failure.code === "number" ? failure.code : 1,
31
- stdout: failure.stdout ?? "",
32
- stderr: failure.stderr ?? failure.message ?? String(error)
33
- };
34
- }
35
- };
14
+ import { createExecFileCommandRunner, createPinnedCommandRunner, resolveExecutable } from "../external/pinnedCommandRunner.js";
15
+ export { resolveExecutable } from "../external/pinnedCommandRunner.js";
16
+ const defaultRunCommand = createExecFileCommandRunner();
36
17
  /**
37
18
  * The external commands the production adapter shells out to. Each one is
38
19
  * resolved to an absolute path once, at adapter construction time, so a PATH
@@ -47,24 +28,6 @@ const PINNED_EXTERNAL_COMMANDS = ["gh", "git", "npm", "tar", "sh"];
47
28
  * command that cannot be resolved returns undefined. Callers must fail closed
48
29
  * without invoking a mutable PATH fallback.
49
30
  */
50
- export function resolveExecutable(command, environmentPath) {
51
- if (isAbsolute(command))
52
- return command;
53
- const pathValue = environmentPath ?? "";
54
- for (const directory of pathValue.split(delimiter)) {
55
- if (directory.length === 0)
56
- continue;
57
- const candidate = resolve(directory, command);
58
- try {
59
- accessSync(candidate, constants.X_OK);
60
- return candidate;
61
- }
62
- catch {
63
- // Keep walking PATH.
64
- }
65
- }
66
- return undefined;
67
- }
68
31
  /**
69
32
  * Wrap the default runner so every external command is pinned to the
70
33
  * absolute path resolved at construction. Injected runners (tests) are used
@@ -75,33 +38,7 @@ export function resolveExecutable(command, environmentPath) {
75
38
  * unresolvable for the adapter's lifetime.
76
39
  */
77
40
  export function createPinnedRunner(base) {
78
- const pinned = new Map();
79
- for (const name of PINNED_EXTERNAL_COMMANDS) {
80
- pinned.set(name, resolveExecutable(name, process.env.PATH));
81
- }
82
- return (command, args, cwd, env) => {
83
- // P2 (rr24): `Map.get` returns undefined both for a missing key and for
84
- // a key whose resolved value is undefined (a command that was not on
85
- // PATH at construction). Use `Map.has` to distinguish them so the
86
- // negative cache holds: a command resolved as missing stays missing even
87
- // if it appears on PATH later in the adapter's lifetime.
88
- let resolved;
89
- if (pinned.has(command)) {
90
- resolved = pinned.get(command);
91
- }
92
- else {
93
- resolved = isAbsolute(command) ? command : resolveExecutable(command, process.env.PATH);
94
- pinned.set(command, resolved);
95
- }
96
- if (resolved === undefined) {
97
- return Promise.resolve({
98
- code: 127,
99
- stdout: "",
100
- stderr: `Unable to resolve trusted executable: ${command}`
101
- });
102
- }
103
- return base(resolved, args, cwd, env);
104
- };
41
+ return createPinnedCommandRunner(base, PINNED_EXTERNAL_COMMANDS);
105
42
  }
106
43
  /**
107
44
  * The real adapter. It is NEVER exercised by the deterministic test suite