@zq-silk/yui 0.15.6 → 0.15.8

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 (282) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +167 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +188 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +57 -0
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1216 -730
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +77 -0
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +127 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +530 -476
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +423 -0
  106. package/dist/kernel/capabilityRegistry.js +314 -0
  107. package/dist/kernel/capabilitySchema.js +91 -0
  108. package/dist/kernel/instanceHost.js +18 -1
  109. package/dist/kernel/kernelPorts.js +4 -1
  110. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  111. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  112. package/dist/message/message.js +62 -6
  113. package/dist/message/messageContinuation.js +204 -0
  114. package/dist/observability/executionAudit.js +70 -72
  115. package/dist/observability/faultClassification.js +2 -2
  116. package/dist/observability/orchestrationMetrics.js +8 -8
  117. package/dist/operator/operatorSessionHistory.js +1 -7
  118. package/dist/output/agentConfigurationPresentation.js +8 -3
  119. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  120. package/dist/output/rolePresentation.js +54 -3
  121. package/dist/plugins/pluginChild.js +104 -0
  122. package/dist/plugins/pluginIntent.js +26 -0
  123. package/dist/plugins/pluginInterpreter.js +43 -0
  124. package/dist/plugins/pluginPackage.js +101 -0
  125. package/dist/plugins/pluginProcess.js +112 -0
  126. package/dist/plugins/pluginService.js +380 -0
  127. package/dist/profile/agentProfile.js +1 -1
  128. package/dist/repository/gitWorkspace.js +26 -4
  129. package/dist/repository/project.js +19 -4
  130. package/dist/repository/taskBaseFreshness.js +13 -13
  131. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  132. package/dist/repository/taskWorkspacePreparer.js +344 -83
  133. package/dist/resources/autoResourceGc.js +3 -3
  134. package/dist/resources/liveReferences.js +3 -3
  135. package/dist/resources/projectResource.js +123 -0
  136. package/dist/resources/projectResourceService.js +421 -0
  137. package/dist/resources/resourceDiscovery.js +6 -6
  138. package/dist/resources/resourceGc.js +1 -1
  139. package/dist/resources/resourceRegistrar.js +1 -1
  140. package/dist/resources/resourceTypes.js +1 -1
  141. package/dist/review/deltaRecheck.js +3 -3
  142. package/dist/review/reviewAcceptance.js +16 -16
  143. package/dist/review/reviewDecision.js +7 -7
  144. package/dist/review/reviewRound.js +21 -20
  145. package/dist/review/reviewerAvailability.js +2 -2
  146. package/dist/role/role.js +51 -7
  147. package/dist/role/taskRoleUpdate.js +30 -0
  148. package/dist/runtime/acpProtocol.js +425 -0
  149. package/dist/runtime/acpSession.js +731 -0
  150. package/dist/runtime/acpSessionConfiguration.js +260 -0
  151. package/dist/runtime/agentDriver.js +30 -11
  152. package/dist/runtime/agentEndpoint.js +278 -0
  153. package/dist/runtime/agentEndpointIdentity.js +86 -0
  154. package/dist/runtime/agentEndpointOwnership.js +239 -0
  155. package/dist/runtime/agentError.js +2 -10
  156. package/dist/runtime/agentHost.js +565 -314
  157. package/dist/runtime/agentRunConfiguration.js +258 -0
  158. package/dist/runtime/builtinAgentDrivers.js +134 -18
  159. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  160. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  161. package/dist/runtime/claude-process-owner +0 -0
  162. package/dist/runtime/codexAppServerRuntime.js +38 -30
  163. package/dist/runtime/codexInteractiveHost.js +41 -6
  164. package/dist/runtime/continuationManager.js +2 -6
  165. package/dist/runtime/executionEnvironment.js +30 -0
  166. package/dist/runtime/firstProgressAdvisory.js +11 -11
  167. package/dist/runtime/index.js +4 -3
  168. package/dist/runtime/jsonLineChannel.js +109 -0
  169. package/dist/runtime/launchBroker.js +91 -16
  170. package/dist/runtime/launchDiagnostics.js +2 -2
  171. package/dist/runtime/lifecycleReservation.js +10 -18
  172. package/dist/runtime/managedCaller.js +61 -17
  173. package/dist/runtime/nativeSessionControl.js +102 -0
  174. package/dist/runtime/ports.js +6 -21
  175. package/dist/runtime/processExitObservation.js +8 -7
  176. package/dist/runtime/promptEnvelope.js +17 -6
  177. package/dist/runtime/providerContinuation.js +3 -9
  178. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  179. package/dist/runtime/providerControl.js +2 -7
  180. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  181. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  182. package/dist/runtime/runtimeBinding.js +0 -1
  183. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  184. package/dist/runtime/runtimeDeadlines.js +9 -0
  185. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  186. package/dist/runtime/runtimeObservation.js +29 -65
  187. package/dist/runtime/runtimeProjection.js +43 -51
  188. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  189. package/dist/runtime/sessionLaunchRequest.js +3 -7
  190. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  191. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  192. package/dist/runtime/sessionReconciliation.js +4 -8
  193. package/dist/runtime/sessionTerminationGuard.js +70 -259
  194. package/dist/runtime/sessionTokenMetrics.js +5 -16
  195. package/dist/runtime/structuredProviderHost.js +237 -117
  196. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  197. package/dist/runtime/tmuxAdapters.js +39 -86
  198. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  199. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  200. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  201. package/dist/scheduler/ports.js +80 -9
  202. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  203. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  204. package/dist/scheduler/taskExecutionProjection.js +120 -124
  205. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  206. package/dist/scheduler/taskWake.js +11 -4
  207. package/dist/scheduler/wakeReason.js +9 -1
  208. package/dist/setup/setupCommand.js +1 -0
  209. package/dist/storage/migrations/agentRunContract.js +159 -0
  210. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  211. package/dist/storage/sqliteSchema.js +431 -5
  212. package/dist/storage/sqliteStore.js +375 -220
  213. package/dist/storage/storageVersions.js +1 -1
  214. package/dist/storage/storeRpc.js +10 -5
  215. package/dist/storage/taskStore.js +13 -11
  216. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  217. package/dist/surface/surfaceContributions.js +102 -0
  218. package/dist/task/completionReadiness.js +32 -6
  219. package/dist/task/deliveryGuard.js +16 -16
  220. package/dist/task/draftPlan.js +72 -12
  221. package/dist/task/nextAction.js +144 -128
  222. package/dist/task/remoteDelivery.js +6 -6
  223. package/dist/task/task.js +184 -18
  224. package/dist/task/taskActivation.js +301 -0
  225. package/dist/task/taskActivationService.js +392 -0
  226. package/dist/task/taskRecordReference.js +5 -4
  227. package/dist/task/taskRecordRetirement.js +1 -1
  228. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  229. package/dist/telemetry/telemetryConfig.js +14 -14
  230. package/dist/telemetry/telemetryWiring.js +2 -2
  231. package/dist/web/assets/assetManifest.js +2 -0
  232. package/dist/web/assets/client/app.js +120 -20
  233. package/dist/web/assets/client/components.js +87 -54
  234. package/dist/web/assets/client/i18n.js +83 -41
  235. package/dist/web/assets/client/markdown.js +1 -1
  236. package/dist/web/assets/client/taskSurface.js +353 -0
  237. package/dist/web/assets/client/view.js +49 -44
  238. package/dist/web/assets/shell.js +1 -1
  239. package/dist/web/assets/styles/cards.js +22 -4
  240. package/dist/web/controllerWeb.js +60 -0
  241. package/dist/web/webMutation.js +28 -0
  242. package/dist/web/webServer.js +118 -8
  243. package/dist/web/webSnapshot.js +81 -74
  244. package/dist/web/webTaskSurface.js +64 -0
  245. package/dist/workItem/dependencyGate.js +1 -1
  246. package/dist/workItem/workItem.js +80 -48
  247. package/dist/workspace/workItemChangeSetManager.js +16 -9
  248. package/docs/agent-result-consumption.md +94 -0
  249. package/docs/agent-runtime-drivers.md +91 -0
  250. package/docs/architecture/README.md +38 -0
  251. package/docs/architecture/capabilities-and-resources.md +79 -0
  252. package/docs/managed-turn-and-session-runtime.md +222 -0
  253. package/docs/observability/README.md +81 -0
  254. package/docs/plugin-sdk.md +290 -0
  255. package/docs/provider-runtime.md +163 -0
  256. package/docs/release-workflow.md +303 -0
  257. package/docs/roles-and-configuration.md +113 -0
  258. package/docs/sqlite-control-plane-design.md +76 -0
  259. package/docs/task-dag-semantics.md +57 -0
  260. package/docs/task-delivery.md +103 -0
  261. package/docs/task-local-identity.md +6 -6
  262. package/docs/testing/verification-levels.md +86 -0
  263. package/i18n/README.zh-CN.md +98 -739
  264. package/package.json +5 -5
  265. package/skills/yui-leader/SKILL.md +130 -103
  266. package/skills/yui-leader/references/integration.md +39 -0
  267. package/skills/yui-leader/references/replicated-execution.md +42 -0
  268. package/skills/yui-leader/references/task-plugins.md +33 -0
  269. package/skills/yui-operator/SKILL.md +30 -59
  270. package/skills/yui-reviewer/SKILL.md +35 -36
  271. package/skills/yui-runtime/SKILL.md +88 -24
  272. package/skills/yui-runtime/references/publication.md +22 -0
  273. package/skills/yui-runtime/references/recovery.md +64 -0
  274. package/skills/yui-worker/SKILL.md +37 -39
  275. package/dist/cli/roleOptionCatalog.js +0 -68
  276. package/dist/context/sourceTurnContext.js +0 -30
  277. package/dist/execution/reviewMainTurn.js +0 -161
  278. package/dist/execution/workItemMainTurn.js +0 -164
  279. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  280. package/dist/runtime/preallocatedNativeSession.js +0 -13
  281. package/dist/runtime/runtimeStopReceipt.js +0 -42
  282. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,5 +1,5 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { isCompletedReviewExecutionFromTurns } from "../review/reviewAcceptance.js";
2
+ import { isCompletedReviewExecutionFromRuns } 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) {
@@ -48,10 +48,10 @@ export function projectTaskOrchestration(facts) {
48
48
  timeToFirstProjectCommitMs: firstCommitAt === undefined
49
49
  ? null
50
50
  : Math.max(0, Date.parse(firstCommitAt) - Date.parse(facts.task.createdAt)),
51
- turns: {
52
- total: facts.turns.length,
53
- byStatus: counts(facts.turns.map(({ status }) => status)),
54
- byRole: counts(facts.turns.map(({ roleName }) => roleName))
51
+ runs: {
52
+ total: facts.runs.length,
53
+ byStatus: counts(facts.runs.map(({ status }) => status)),
54
+ byRole: counts(facts.runs.map(({ roleName }) => roleName))
55
55
  },
56
56
  workItems: facts.workItems.length,
57
57
  reviews: {
@@ -65,7 +65,7 @@ export function projectTaskOrchestration(facts) {
65
65
  repeatedIdentities,
66
66
  evidenceReuses: facts.integrations.filter((attempt) => ((attempt.checks ?? []).some(({ details }) => details?.startsWith("Reused successful check evidence from ")))).length
67
67
  },
68
- providerGenerationsBeforeFirstProgress: firstProgress.generationsBeforeFirstProgress,
68
+ providerSessionsBeforeFirstProgress: firstProgress.sessionsBeforeFirstProgress,
69
69
  publicationToCompletionMs: publicationAt === undefined || facts.task.completedAt === undefined
70
70
  ? null
71
71
  : Math.max(0, Date.parse(facts.task.completedAt) - Date.parse(publicationAt)),
@@ -91,7 +91,7 @@ function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressA
91
91
  .map(({ id }) => `durable-job:${id}`)
92
92
  });
93
93
  }
94
- const completedFull = fullRounds.filter((round) => (isCompletedReviewExecutionFromTurns(round, facts.turns)))
94
+ const completedFull = fullRounds.filter((round) => (isCompletedReviewExecutionFromRuns(round, facts.runs)))
95
95
  .sort((left, right) => left.createdAt.localeCompare(right.createdAt));
96
96
  const recent = completedFull.slice(-3);
97
97
  const sameCandidate = recent.length === 3
@@ -108,7 +108,7 @@ function projectAdvisories(facts, fullRounds, repeatedIdentities, firstProgressA
108
108
  if (firstProgressAttention) {
109
109
  result.push({
110
110
  code: "provider-first-progress-advisory",
111
- reason: "Two fresh Leader generations produced no first durable progress; consider Operator attention before another generation.",
111
+ reason: "Two fresh Leader Sessions produced no first durable progress; consider Operator attention before another Session.",
112
112
  refs: [facts.task.id]
113
113
  });
114
114
  }
@@ -1,3 +1,4 @@
1
+ import { agentAdapterLabel as adapterLabel } from "../agent/adapterCatalog.js";
1
2
  import { normalizeRoleAgentSessionText, roleAgentSessionRef, validateRoleSessionSet } from "../executor/agentExecutor.js";
2
3
  /** Separates the one selected writer authority from retained conversations. */
3
4
  export function projectOperatorStatus(sessions, activeAgentId, activeAdapterId) {
@@ -141,13 +142,6 @@ function optionalDisplayText(value) {
141
142
  const normalized = normalizeRoleAgentSessionText(value);
142
143
  return normalized.length === 0 ? undefined : normalized;
143
144
  }
144
- function adapterLabel(adapterId) {
145
- return adapterId === "codex"
146
- ? "Codex"
147
- : adapterId === "claude"
148
- ? "Claude"
149
- : adapterId;
150
- }
151
145
  function requireText(value, label) {
152
146
  if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
153
147
  throw new Error(`${label} is required.`);
@@ -1,4 +1,5 @@
1
1
  import { renderAgentConfigurationResolutionNotice } from "../cli/agentConfigurationPicker.js";
2
+ import { agentHandshakeLine } from "./agentRunConfigurationPresentation.js";
2
3
  import { defaultTableWidth, renderTable } from "./table.js";
3
4
  export function renderAgentConfigurationCatalog(resolved) {
4
5
  const { catalog } = resolved;
@@ -11,13 +12,17 @@ export function renderAgentConfigurationCatalog(resolved) {
11
12
  model.serviceTiers?.map((tier) => tier.value).join(", ") || "none reported"
12
13
  ]);
13
14
  const fieldRows = catalog.fields
14
- .filter((field) => field.key !== "model" && field.key !== "effort")
15
+ .filter((field) => modelRows.length === 0 || (field.key !== "model" && field.key !== "effort"))
15
16
  .map((field) => [
16
17
  field.key,
17
18
  field.choices.map((choice) => choice.value).join(", ") || "none reported",
19
+ // An available field can still carry a reason, and for ACP it always does:
20
+ // the values are enumerated per Session, so the probe deliberately lists
21
+ // none. Dropping that sentence left "none reported" looking like a failed
22
+ // query rather than a deliberate boundary, so it is shown either way.
18
23
  field.available === false
19
24
  ? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
20
- : "available",
25
+ : field.reason === undefined ? "available" : `available: ${field.reason}`,
21
26
  field.allowCustom ? "yes" : "no"
22
27
  ]);
23
28
  const sections = [
@@ -39,5 +44,5 @@ export function renderAgentConfigurationCatalog(resolved) {
39
44
  { header: "Custom", minWidth: 6, maxWidth: 8 }
40
45
  ], fieldRows, defaultTableWidth())])
41
46
  ];
42
- return `${sections.join("\n\n")}\n${renderAgentConfigurationResolutionNotice(resolved)}`;
47
+ return `${sections.join("\n\n")}\n${agentHandshakeLine(catalog.handshake)}\n${renderAgentConfigurationResolutionNotice(resolved)}`;
43
48
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Rendering what a live Agent reports about its own run configuration.
3
+ *
4
+ * The whole point of this display is to keep two things visibly apart that a
5
+ * reader would otherwise merge: what the launch asked for, and what the Agent
6
+ * says it is doing. So the confirmation strength is printed for every requested
7
+ * value rather than summarized into a checkmark, and a value Yui could not
8
+ * verify is labelled as such in the same column where a verified one would
9
+ * appear. There is no state in which this renders a requested value as though
10
+ * the Agent had agreed to it.
11
+ */
12
+ import { renderTable, defaultTableWidth } from "./table.js";
13
+ /**
14
+ * The one handshake line, shared by every surface that has one to show.
15
+ *
16
+ * Both the capability catalog and a Session inspect display this same fact, and
17
+ * a reader comparing them is entitled to assume the wording means the same thing
18
+ * in both. One function is what guarantees that, in the same way one projection
19
+ * function guarantees the two produce the same observation to begin with.
20
+ *
21
+ * `undefined` is the catalog's third state — no connection was attempted — and
22
+ * is distinct from a plan that negotiates nothing.
23
+ */
24
+ export function agentHandshakeLine(handshake) {
25
+ if (handshake === undefined) {
26
+ return "Agent handshake: not attempted (these values did not come from a live connection)";
27
+ }
28
+ if (handshake.status === "unsupported") {
29
+ return `Agent handshake: unsupported (${handshake.reason})`;
30
+ }
31
+ return [
32
+ `Agent handshake: protocol v${handshake.protocolVersion};`,
33
+ ` reported as ${handshake.agentName} ${handshake.agentVersion};`,
34
+ ` capabilities ${handshake.capabilities.join(", ") || "none"};`,
35
+ // Method ids only. Which methods an Agent offers is a capability; any
36
+ // credential or token that satisfies one is not Yui's to display.
37
+ ` auth methods ${handshake.authMethods.join(", ") || "none"}`
38
+ ].join("");
39
+ }
40
+ /**
41
+ * The one-line form, for a status display that has a line to spare.
42
+ *
43
+ * Deliberately says which of the three "no value" answers applies instead of
44
+ * printing a blank: a reader who sees nothing cannot tell whether Yui failed to
45
+ * ask, the Agent has nothing to report, or the Agent reported a problem.
46
+ */
47
+ export function agentRunConfigurationLabel(observation) {
48
+ if (observation === undefined)
49
+ return "not queried";
50
+ if (observation.status === "unknown")
51
+ return `unknown (${observation.reason})`;
52
+ if (observation.status === "unsupported")
53
+ return `unsupported (${observation.reason})`;
54
+ const summary = observation.requested.length === 0
55
+ ? "no configuration requested; the Agent's own defaults stand"
56
+ : observation.requested
57
+ .map((request) => `${request.field}=${requestValueLabel(request)}`)
58
+ .join(", ");
59
+ return `${summary}; read at ${observation.observedAt}`;
60
+ }
61
+ /**
62
+ * The full form: one table for what was asked and one for what the Agent offers.
63
+ *
64
+ * Returns an empty string when there is nothing an Agent reported, so a caller
65
+ * can append it unconditionally without producing an empty heading.
66
+ */
67
+ export function renderAgentRunConfiguration(observation) {
68
+ if (observation === undefined || observation.status !== "observed")
69
+ return "";
70
+ const sections = [];
71
+ if (observation.requested.length > 0) {
72
+ sections.push(renderTable("Requested run configuration (launch-time confirmation)", [
73
+ { header: "Field", minWidth: 10, maxWidth: 12 },
74
+ { header: "Requested", minWidth: 12, maxWidth: 30 },
75
+ { header: "Evidence", minWidth: 12, maxWidth: 14 },
76
+ { header: "Agent reports now", minWidth: 16, maxWidth: 46 }
77
+ ], observation.requested.map((request) => [
78
+ request.field,
79
+ request.value,
80
+ confirmationLabel(request.confirmation),
81
+ currentValueLabel(request.current, request.value)
82
+ ]), defaultTableWidth()));
83
+ }
84
+ if (observation.axes.length > 0) {
85
+ sections.push(renderTable("Agent-reported configuration options (Yui-supported axes)", [
86
+ { header: "Option", minWidth: 10, maxWidth: 22 },
87
+ { header: "Axis", minWidth: 8, maxWidth: 14 },
88
+ { header: "Current", minWidth: 12, maxWidth: 40 },
89
+ { header: "Offered", minWidth: 18, maxWidth: 44 }
90
+ ], observation.axes.map((axis) => [
91
+ axis.key,
92
+ axis.category ?? "unnamed",
93
+ axis.current.status === "observed"
94
+ ? axis.current.value
95
+ : `unobserved: ${axis.current.reason}`,
96
+ axis.offered.join(", ") || "none reported"
97
+ ]), defaultTableWidth()));
98
+ }
99
+ sections.push(agentHandshakeLine(observation.handshake));
100
+ sections.push(`Read from the live Session at ${observation.observedAt}. `
101
+ + "Requests describe this connection's launch, not subsequent Role edits; "
102
+ + "current values are the latest peer reports, not independent execution verification.");
103
+ return sections.join("\n\n");
104
+ }
105
+ /** Spell out what each confirmation actually proves, in the table cell. */
106
+ function confirmationLabel(confirmation) {
107
+ if (confirmation === "already")
108
+ return "already set";
109
+ if (confirmation === "observed")
110
+ return "observed";
111
+ return "ack only";
112
+ }
113
+ function requestValueLabel(request) {
114
+ if (request.current.status === "unobserved")
115
+ return `${request.value} (unverified)`;
116
+ return request.current.value === request.value
117
+ ? request.value
118
+ // Drift is the case this whole path exists to make visible, so it is stated
119
+ // rather than resolved in favour of either value.
120
+ : `${request.current.value} (requested ${request.value})`;
121
+ }
122
+ function currentValueLabel(current, requested) {
123
+ if (current.status === "unobserved")
124
+ return `unobserved: ${current.reason}`;
125
+ return current.value === requested
126
+ ? current.value
127
+ : `${current.value} — differs from the requested ${requested}`;
128
+ }
@@ -1,5 +1,29 @@
1
1
  import { activeRoleAgentSession } from "../executor/agentExecutor.js";
2
+ import { effectiveRoleForLaunch } from "../executor/effectiveLaunch.js";
2
3
  import { defaultTableWidth, renderTable } from "./table.js";
4
+ /** Desired configuration and the fixed Session request are not Provider evidence. */
5
+ export function renderRoleLaunchComparison(role, effective) {
6
+ const pinned = effectiveRoleForLaunch(role, effective);
7
+ return renderTable("Configuration intent (not observed Provider state)", [
8
+ { header: "Source", minWidth: 14, maxWidth: 20 },
9
+ { header: "Agent / component", minWidth: 18, maxWidth: 32 },
10
+ { header: "Model", minWidth: 10, maxWidth: 20 },
11
+ { header: "Effort", minWidth: 8, maxWidth: 14 },
12
+ { header: "Permission", minWidth: 12, maxWidth: 34 },
13
+ { header: "Profile intent", minWidth: 14, maxWidth: 14 }
14
+ ], [[`Role desired r${role.launchRevision}`, role],
15
+ [`Session pinned r${effective.sourceDesiredRevision}`, pinned]].map(([source, current]) => {
16
+ const binding = current.agentBindings[current.activeAgentId];
17
+ return [
18
+ source,
19
+ `${binding.agentId}/${binding.component}`,
20
+ binding.config.model ?? "Agent default",
21
+ binding.config.effort ?? "Agent default",
22
+ permission(binding),
23
+ current.defaultAccess
24
+ ];
25
+ }), defaultTableWidth());
26
+ }
3
27
  export function activeRoleSummary(role) {
4
28
  const binding = role.agentBindings[role.activeAgentId];
5
29
  return {
@@ -24,10 +48,20 @@ export function renderRoleDetails(title, role, input) {
24
48
  ` Kind ${input.kind}`,
25
49
  ` Active Agent ${role.activeAgentId}`,
26
50
  ` Workspace ${role.workspace}`,
51
+ ...("taskId" in role ? [
52
+ ` Desired environment ${role.executionEnvironment === undefined
53
+ ? "managed workspace"
54
+ : `${role.executionEnvironment.environmentRef}; ${role.executionEnvironment.directory.path}; ${role.executionEnvironment.access}`}`,
55
+ ` Session environment ${effective === undefined ? "not started"
56
+ : effective.executionEnvironment === undefined ? "managed workspace"
57
+ : `${effective.executionEnvironment.environmentRef}; ${effective.executionEnvironment.directory.path}; ${effective.executionEnvironment.access}`}`
58
+ ] : []),
27
59
  ` Desired launch r${role.launchRevision}; Profile intent=${role.defaultAccess}`,
60
+ // The component is what the Session is pinned to; its plan follows from it.
61
+ // Naming the plan alone would show two different ACP products identically.
28
62
  ` Effective launch ${effective === undefined
29
63
  ? "not started"
30
- : `${effective.agentId}/${effective.adapterId}; r${effective.sourceDesiredRevision}; Profile intent=${effective.profileAccess}; permission=${effective.permission.strategy}`}`,
64
+ : `${effective.agentId}/${effective.component}; r${effective.sourceDesiredRevision}; Profile intent=${effective.profileAccess}; permission=${effective.permission.strategy}`}`,
31
65
  ` Desired drift ${effective === undefined
32
66
  ? "-"
33
67
  : effective.sourceDesiredRevision === role.launchRevision
@@ -46,7 +80,10 @@ export function renderRoleDetails(title, role, input) {
46
80
  renderTable("Agent settings", [
47
81
  { header: "Agent", minWidth: 5, maxWidth: 20 },
48
82
  { header: "Active", minWidth: 6, maxWidth: 6 },
49
- { header: "Adapter", minWidth: 7, maxWidth: 10 },
83
+ // Wide enough to hold the longest component id whole: it is an
84
+ // identifier, so splitting it mid-token reads worse than letting the
85
+ // prose in Permission wrap, which it does naturally.
86
+ { header: "Component", minWidth: 17, maxWidth: 18 },
50
87
  { header: "Model", minWidth: 8, maxWidth: 24 },
51
88
  { header: "Effort", minWidth: 8, maxWidth: 16 },
52
89
  { header: "Permission", minWidth: 10, maxWidth: 34 },
@@ -59,7 +96,7 @@ function bindingRow(binding, role, sessions) {
59
96
  return [
60
97
  binding.agentId,
61
98
  binding.agentId === role.activeAgentId ? "yes" : "",
62
- binding.adapterId,
99
+ binding.component,
63
100
  binding.config.model ?? "CLI default",
64
101
  binding.config.effort ?? "CLI default",
65
102
  permission(binding),
@@ -81,6 +118,20 @@ function permission(binding) {
81
118
  permission.approval === undefined ? undefined : `approval=${permission.approval}`
82
119
  ].filter((value) => value !== undefined).join("; ");
83
120
  }
121
+ // ACP's strategy decides the Session mode Yui requests over the protocol. It
122
+ // is stated separately from how Yui answers `session/request_permission`,
123
+ // which is always a decline on this transport: those are two different
124
+ // questions, and collapsing them would make a configured Session mode read as
125
+ // interactive consent Yui does not hold.
126
+ if (binding.config.adapterId === "acp") {
127
+ const permission = binding.config.permission;
128
+ const requests = "requests declined";
129
+ if (permission.strategy === "default")
130
+ return `Agent default; ${requests}`;
131
+ if (permission.strategy === "bypass")
132
+ return `bypass; ${requests}`;
133
+ return `mode=${permission.mode}; ${requests}`;
134
+ }
84
135
  const permission = binding.config.permission;
85
136
  if (permission.strategy === "default")
86
137
  return "CLI default";
@@ -0,0 +1,104 @@
1
+ /** Dedicated trusted-local process. Module linking fixes the dependency closure
2
+ * to captured package bytes; node:vm is NOT a security boundary. The explicit
3
+ * trusted-local execution grant, not this loader, authorizes author code. */
4
+ import { createContext, SourceTextModule } from "node:vm";
5
+ import { posix } from "node:path";
6
+ const send = (message) => process.send?.(message);
7
+ const pending = new Map();
8
+ let nextCall = 0;
9
+ let instance;
10
+ let selfTest;
11
+ const context = createContext(Object.create(null), { codeGeneration: { strings: false, wasm: false } });
12
+ process.on("message", (raw) => {
13
+ if (raw.type === "nested-result") {
14
+ const entry = pending.get(raw.id);
15
+ if (!entry)
16
+ return;
17
+ pending.delete(raw.id);
18
+ if (raw.error)
19
+ entry.reject(new Error(String(raw.error)));
20
+ else
21
+ entry.resolve(raw.value);
22
+ return;
23
+ }
24
+ void execute(raw).then((value) => send({ type: "result", id: raw.id, value }), (error) => send({ type: "result", id: raw.id, error: error instanceof Error ? error.message : String(error) }));
25
+ });
26
+ async function execute(message) {
27
+ if (message.type === "load") {
28
+ const files = message.files;
29
+ const modules = new Map();
30
+ const load = (name) => {
31
+ const previous = modules.get(name);
32
+ if (previous)
33
+ return previous;
34
+ if (!Object.hasOwn(files, name))
35
+ throw new Error(`Package module missing: ${name}`);
36
+ const module = new SourceTextModule(files[name], {
37
+ context, identifier: name,
38
+ importModuleDynamically: async () => { throw new Error("Dynamic imports are not supported; bundle package dependencies."); }
39
+ });
40
+ modules.set(name, module);
41
+ return module;
42
+ };
43
+ const root = load(message.entry);
44
+ await root.link((specifier, parent) => {
45
+ if (!specifier.startsWith("./") && !specifier.startsWith("../"))
46
+ throw new Error("Only package-relative module imports are supported.");
47
+ const path = posix.normalize(posix.join(posix.dirname(parent.identifier), specifier));
48
+ if (path.startsWith("../") || path.startsWith("/"))
49
+ throw new Error("Module import escapes the captured package.");
50
+ return load(path);
51
+ });
52
+ await root.evaluate();
53
+ const exports = root.namespace;
54
+ if (typeof exports.initialize !== "function")
55
+ throw new Error("Executable entry must export initialize().");
56
+ // No host invocation/identity/resource port exists during initialization.
57
+ instance = await exports.initialize();
58
+ if (!instance || typeof instance.handlers !== "object" || !instance.handlers
59
+ || Object.values(instance.handlers).some((handler) => typeof handler !== "function")
60
+ || (instance.dispose !== undefined && typeof instance.dispose !== "function")) {
61
+ throw new Error("initialize() must return handlers and an optional dispose function.");
62
+ }
63
+ selfTest = exports.selfTest;
64
+ return Object.keys(instance.handlers).sort();
65
+ }
66
+ if (message.type === "test") {
67
+ if (typeof selfTest !== "function")
68
+ throw new Error("Executable validation requires exported selfTest().");
69
+ if (await selfTest() !== true)
70
+ throw new Error("selfTest() must return true.");
71
+ return true;
72
+ }
73
+ if (message.type === "dispose") {
74
+ await instance?.dispose?.();
75
+ return null;
76
+ }
77
+ if (message.type !== "invoke")
78
+ throw new Error("Unknown child request.");
79
+ let open = true;
80
+ try {
81
+ const value = await instance.handlers[message.name](message.input, Object.freeze({
82
+ context: Object.freeze(message.context), requestId: message.requestId,
83
+ call: (request) => {
84
+ if (!open)
85
+ return Promise.reject(new Error("Plugin invocation ended."));
86
+ const id = `nested-${++nextCall}`;
87
+ return new Promise((resolve, reject) => {
88
+ pending.set(id, { resolve, reject });
89
+ send({ type: "nested", id, parentId: message.id, request });
90
+ });
91
+ }
92
+ }));
93
+ // IPC's JSON projection must not hide bad output from the parent registry.
94
+ return JSON.parse(JSON.stringify(value, (_key, item) => {
95
+ if (typeof item === "bigint" || typeof item === "function" || typeof item === "symbol"
96
+ || (typeof item === "number" && !Number.isFinite(item)))
97
+ throw new Error("Plugin output is not JSON data.");
98
+ return item;
99
+ }));
100
+ }
101
+ finally {
102
+ open = false;
103
+ }
104
+ }
@@ -0,0 +1,26 @@
1
+ import { requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
2
+ export function validatePluginId(id) {
3
+ if (typeof id !== "string" || !/^[a-z][a-z0-9-]*$/u.test(id))
4
+ throw new Error("Invalid plugin id.");
5
+ return id;
6
+ }
7
+ export function validatePluginIntentFailure(failure) {
8
+ requireText(failure.message, "Plugin failure");
9
+ requireTimestamp(failure.occurredAt, "Plugin failure time");
10
+ return failure;
11
+ }
12
+ export function validatePluginIntent(intent) {
13
+ if (intent.schemaVersion !== 1 || typeof intent.enabled !== "boolean"
14
+ || !Number.isSafeInteger(intent.revision) || intent.revision < 1)
15
+ throw new Error("Invalid plugin intent.");
16
+ requireIdentity(intent.taskId, "Plugin intent Task");
17
+ validatePluginId(intent.pluginId);
18
+ requireTimestamp(intent.updatedAt, "Plugin intent time");
19
+ if (intent.validationId !== undefined)
20
+ requireIdentity(intent.validationId, "Plugin validation reference");
21
+ if (intent.enabled && intent.validationId === undefined)
22
+ throw new Error("Enabled intent requires a fixed validation reference.");
23
+ if (intent.lastFailure !== undefined)
24
+ validatePluginIntentFailure(intent.lastFailure);
25
+ return intent;
26
+ }
@@ -0,0 +1,43 @@
1
+ import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
2
+ /** Bounded data interpreter, not JavaScript evaluation or a workflow engine. */
3
+ export function interpretPlugin(pkg) {
4
+ const handlers = JSON.parse(pkg.files[pkg.manifest.entry]);
5
+ const text = { type: "string", minLength: 1 };
6
+ const handlerSchema = {
7
+ anyOf: [
8
+ { type: "object", properties: { type: { const: "echo" } }, required: ["type"], additionalProperties: false },
9
+ { type: "object", properties: { type: { const: "constant" }, value: {} }, required: ["type", "value"], additionalProperties: false },
10
+ { type: "object", properties: { type: { const: "call" }, name: text, contractVersion: text, providerId: text },
11
+ required: ["type", "name", "contractVersion"], additionalProperties: false }
12
+ ]
13
+ };
14
+ const error = capabilitySchemaError({
15
+ type: "object", required: pkg.manifest.capabilities.map((capability) => capability.name),
16
+ properties: Object.fromEntries(pkg.manifest.capabilities.map((capability) => [capability.name, handlerSchema])),
17
+ additionalProperties: false
18
+ }, handlers);
19
+ if (error)
20
+ throw new Error(`Declarative entry: ${error}`);
21
+ const entries = handlers;
22
+ for (const handler of Object.values(entries)) {
23
+ if (handler.type === "call" && !pkg.manifest.required.some((dependency) => dependency.name === handler.name && dependency.contractVersion === handler.contractVersion)) {
24
+ throw new Error("Declarative call must name a required dependency.");
25
+ }
26
+ }
27
+ return {
28
+ async invoke(name, input, invocation) {
29
+ const handler = entries[name];
30
+ if (handler.type === "echo")
31
+ return input;
32
+ if (handler.type === "constant")
33
+ return structuredClone(handler.value);
34
+ const result = await invocation.call({
35
+ name: handler.name, input, contractVersion: handler.contractVersion,
36
+ providerId: handler.providerId, requestId: invocation.requestId
37
+ });
38
+ if (!["value", "operation"].includes(result.kind))
39
+ throw new Error(result.detail ?? result.kind);
40
+ return result.value;
41
+ }
42
+ };
43
+ }
@@ -0,0 +1,101 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
+ import { join, posix } from "node:path";
4
+ import { capabilitySchemaError, checkCapabilitySchema } from "../kernel/capabilitySchema.js";
5
+ const text = { type: "string", minLength: 1 };
6
+ const texts = { type: "array", items: text };
7
+ const object = (properties, required = Object.keys(properties)) => ({ type: "object", properties, required, additionalProperties: false });
8
+ const manifestSchema = object({
9
+ id: text, version: text, apiVersion: { const: "1" }, entry: text,
10
+ kind: { enum: ["declarative", "trusted-local"] }, reloadMode: { const: "manual" },
11
+ permissions: texts,
12
+ required: { type: "array", items: object({ name: text, contractVersion: text }) },
13
+ capabilities: { type: "array", minItems: 1, items: object({
14
+ name: text, contractVersion: text, summary: text,
15
+ inputSchema: { type: "object" }, outputSchema: { type: "object" },
16
+ effect: { enum: ["query", "local-mutation", "external-operation"] }, requiredPermissions: texts
17
+ }) },
18
+ build: { type: "array", minItems: 1, items: text }
19
+ }, ["id", "version", "apiVersion", "entry", "kind", "reloadMode", "permissions", "required", "capabilities"]);
20
+ export function parsePluginManifest(value) {
21
+ const error = capabilitySchemaError(manifestSchema, value);
22
+ if (error)
23
+ throw new Error(`Plugin manifest: ${error}`);
24
+ const manifest = value;
25
+ if (!/^[a-z][a-z0-9-]*$/u.test(manifest.id))
26
+ throw new Error("Plugin id must be a lowercase unqualified name.");
27
+ packagePath(manifest.entry);
28
+ if (manifest.kind === "declarative" && manifest.build !== undefined)
29
+ throw new Error("Declarative packages do not execute builds.");
30
+ if (new Set(manifest.permissions).size !== manifest.permissions.length)
31
+ throw new Error("Duplicate plugin permissions.");
32
+ if (manifest.permissions.includes("plugin:manage"))
33
+ throw new Error("Task-local plugins cannot manage their own Host or other plugins.");
34
+ for (const capability of manifest.capabilities) {
35
+ checkCapabilitySchema(capability.inputSchema);
36
+ checkCapabilitySchema(capability.outputSchema);
37
+ if (capability.requiredPermissions.some((permission) => !manifest.permissions.includes(permission))) {
38
+ throw new Error("Capability permission exceeds the manifest permission set.");
39
+ }
40
+ }
41
+ return manifest;
42
+ }
43
+ export function packagePath(value) {
44
+ if (!value || value.startsWith("/") || value.includes("\\") || value.includes("\0")
45
+ || value.split("/").some((part) => !part || part === "." || part === "..")
46
+ || posix.normalize(value) !== value)
47
+ throw new Error("Package paths must be normalized relative paths.");
48
+ return value;
49
+ }
50
+ /** Data-only scan. Symlinks, special files and non-UTF8 dependencies are not a
51
+ * supported package. Capture every byte, including build inputs and dependencies. */
52
+ export function readPluginPackage(directory, requireEntry = true) {
53
+ if (realpathSync(directory) !== directory)
54
+ throw new Error("Plugin directory must be canonical.");
55
+ const files = Object.create(null);
56
+ let bytes = 0;
57
+ const walk = (prefix) => {
58
+ for (const name of readdirSync(join(directory, prefix)).sort()) {
59
+ const path = prefix ? `${prefix}/${name}` : name;
60
+ packagePath(path);
61
+ const stat = lstatSync(join(directory, path));
62
+ if (stat.isDirectory())
63
+ walk(path);
64
+ else {
65
+ if (!stat.isFile() || stat.isSymbolicLink())
66
+ throw new Error("Plugin package contains a non-regular file.");
67
+ bytes += stat.size;
68
+ if (bytes > 4 * 1024 * 1024 || Object.keys(files).length >= 256)
69
+ throw new Error("Plugin package exceeds 4 MiB/256 files.");
70
+ const buffer = readFileSync(join(directory, path));
71
+ const content = buffer.toString("utf8");
72
+ if (!Buffer.from(content).equals(buffer))
73
+ throw new Error("Plugin package files must be UTF-8.");
74
+ files[path] = content;
75
+ }
76
+ }
77
+ };
78
+ walk("");
79
+ const manifest = parsePluginManifest(JSON.parse(files["plugin.json"] ?? "null"));
80
+ if (requireEntry && !Object.hasOwn(files, manifest.entry))
81
+ throw new Error("Plugin entry is missing.");
82
+ return { manifest, files, digest: packageDigest(files) };
83
+ }
84
+ export function packageDigest(files) {
85
+ return createHash("sha256").update(JSON.stringify(Object.entries(files).sort(([a], [b]) => a.localeCompare(b)))).digest("hex");
86
+ }
87
+ export function validatePluginValidation(record) {
88
+ if (record.schemaVersion !== 1 || !record.id?.startsWith("validation-") || !record.taskId || !record.directory || !record.preparationId
89
+ || !record.environment?.path || record.environment.isolation !== "trusted-local"
90
+ || !record.environment.node || !record.environment.device || !record.environment.inode
91
+ || !/^[0-9a-f]{64}$/u.test(record.sourceDigest) || !Number.isFinite(Date.parse(record.createdAt))
92
+ || !Array.isArray(record.checks) || !record.checks.every((value) => typeof value === "string")) {
93
+ throw new Error("Invalid plugin validation record.");
94
+ }
95
+ const manifest = parsePluginManifest(JSON.parse(record.package.files["plugin.json"]));
96
+ if (JSON.stringify(manifest) !== JSON.stringify(record.package.manifest)
97
+ || packageDigest(record.package.files) !== record.package.digest
98
+ || !Object.hasOwn(record.package.files, manifest.entry))
99
+ throw new Error("Plugin validation content mismatch.");
100
+ return record;
101
+ }