@zq-silk/yui 0.15.7 → 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 (281) 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 +157 -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 +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  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 +1188 -710
  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 +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -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 +529 -479
  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 +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -0,0 +1,380 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { checkGrant, recordGrantUse } from "../grant/capabilityGrant.js";
7
+ import { createProjectResources } from "../resources/projectResourceService.js";
8
+ import { CapabilityExecutionError } from "../kernel/capabilityRegistry.js";
9
+ import { validatePluginId } from "./pluginIntent.js";
10
+ import { packagePath, readPluginPackage } from "./pluginPackage.js";
11
+ import { interpretPlugin } from "./pluginInterpreter.js";
12
+ import { PluginProcess, pluginProcessEnvironment } from "./pluginProcess.js";
13
+ const execute = promisify(execFile);
14
+ /** SDK management owner. The Store owns desired selection and validation;
15
+ * the Host alone owns live implementations. Reading/restarting never executes
16
+ * author code or treats saved intent as a grant. */
17
+ export function createPluginService(store, host, registry) {
18
+ const resources = createProjectResources(store);
19
+ const active = new Map();
20
+ // Resource references mirror actual SDK operations/Host ownership only.
21
+ // They prevent explicit environment release while those owners still use it.
22
+ const environmentUsers = new Map();
23
+ const retainEnvironment = (taskId, preparationId) => {
24
+ const token = {};
25
+ environmentUsers.set(token, { taskId, preparationId });
26
+ return () => { environmentUsers.delete(token); };
27
+ };
28
+ const keyFor = (taskId, pluginId) => `plugin:${taskId}:${pluginId}`;
29
+ const selection = (taskId, validationId) => {
30
+ const report = store.getPluginValidation(taskId, validationId);
31
+ if (!report)
32
+ throw new Error("Plugin selection references missing validation evidence.");
33
+ return { validationId, version: report.package.manifest.version, digest: report.package.digest };
34
+ };
35
+ const inspect = (taskId, pluginId) => {
36
+ validatePluginId(pluginId);
37
+ return store.transaction(() => {
38
+ const desired = store.getPluginIntent(taskId, pluginId);
39
+ const selected = active.get(keyFor(taskId, pluginId));
40
+ const actual = selected && host.isAvailable(selected.ref)
41
+ ? { ...selection(taskId, selected.validationId), provider: selected.ref } : null;
42
+ return {
43
+ pluginId, scope: { kind: "task", id: taskId },
44
+ desired: desired === null ? null : {
45
+ ...desired, ...(desired.validationId === undefined ? {} : selection(taskId, desired.validationId))
46
+ },
47
+ actual,
48
+ instances: host.inspect(keyFor(taskId, pluginId)),
49
+ needsActivation: desired?.enabled === true && desired.validationId !== actual?.validationId
50
+ };
51
+ });
52
+ };
53
+ const choose = (taskId, pluginId, enabled, validationId) => store.transaction(() => {
54
+ const previous = store.getPluginIntent(taskId, pluginId);
55
+ const chosen = validationId ?? previous?.validationId;
56
+ const intent = {
57
+ schemaVersion: 1, taskId, pluginId, revision: (previous?.revision ?? 0) + 1,
58
+ enabled, ...(chosen === undefined ? {} : { validationId: chosen }), updatedAt: new Date().toISOString()
59
+ };
60
+ store.savePluginIntent(intent);
61
+ return intent;
62
+ });
63
+ const fail = (intent, error) => {
64
+ let message = error instanceof Error ? error.message : String(error);
65
+ try {
66
+ store.recordPluginIntentFailure(intent.taskId, intent.pluginId, intent.revision, { message, occurredAt: new Date().toISOString() });
67
+ }
68
+ catch (recordError) {
69
+ message += `; failure diagnostic could not be saved: ${recordError instanceof Error ? recordError.message : String(recordError)}`;
70
+ }
71
+ return new CapabilityExecutionError(message, inspect(intent.taskId, intent.pluginId));
72
+ };
73
+ const environment = (taskId, preparationId) => {
74
+ const task = store.getTask(taskId);
75
+ if (!task || !["draft", "active"].includes(task.status))
76
+ throw new Error("Plugin environment requires an open Task.");
77
+ const prepared = store.getEnvironmentPreparation(taskId, preparationId);
78
+ if (!prepared || prepared.disposition !== "adopted" || !prepared.directory || prepared.access !== "write") {
79
+ throw new Error("Plugin development/execution requires an adopted writable T05 environment.");
80
+ }
81
+ // Reuse the same current-adoption proof as native execution, including
82
+ // the resource grant reservation. A new grant alone is not re-adoption.
83
+ resources.resolveExecutionEnvironment(taskId, preparationId);
84
+ return prepared;
85
+ };
86
+ const packageDirectory = (taskId, preparationId, directory) => {
87
+ const env = environment(taskId, preparationId);
88
+ const root = env.directory.path;
89
+ const path = resolve(root, directory);
90
+ const child = relative(root, path);
91
+ packagePath(child);
92
+ if (realpathSync(path) !== path)
93
+ throw new Error("Plugin directory must not traverse symlinks.");
94
+ return { env, path };
95
+ };
96
+ const descriptors = (taskId, pkg, ref) => pkg.manifest.capabilities.map((capability) => ({
97
+ ...capability, provider: ref, scope: { kind: "task", id: taskId },
98
+ source: `plugin:${pkg.manifest.id}@${pkg.digest}`, required: pkg.manifest.required
99
+ }));
100
+ const refFor = (taskId, pkg) => ({ id: keyFor(taskId, pkg.manifest.id), generation: randomUUID() });
101
+ /** Each execution consumes one use. A live call's bound closure carries its
102
+ * admission; it is never resumed from a durable reservation or sent to code.
103
+ * Subsequent actions still check current authority, not cached permission. */
104
+ const execution = (taskId, preparationId, pkg, phase) => {
105
+ const params = { pluginId: pkg.manifest.id, digest: pkg.digest,
106
+ environmentRef: `${taskId}/${preparationId}`, trust: "trusted-local", phase };
107
+ // This grants the capability to run code without OS effect confinement;
108
+ // it does not claim that every invocation actually has irreversible effects.
109
+ const request = { action: "plugin.execute", params, irreversibility: "irreversible" };
110
+ const reservation = phase === "call" ? undefined : `plugin/${randomUUID()}`;
111
+ const grantId = store.transaction((tx) => {
112
+ const env = environment(taskId, preparationId);
113
+ const grant = tx.listCapabilityGrants(taskId).find((candidate) => Object.entries(params).every(([key, value]) => candidate.parameterBounds[key]?.includes(value))
114
+ && (candidate.scope.taskId === undefined || candidate.scope.taskId === taskId)
115
+ && candidate.scope.projectIds === undefined && candidate.scope.repositories === undefined
116
+ && candidate.scope.packages === undefined
117
+ && (candidate.scope.homePath === undefined || candidate.scope.homePath === env.directory.path)
118
+ && checkGrant(candidate, request, new Date()).allowed);
119
+ if (!grant)
120
+ throw new Error(`Explicit trusted-local execution grant unavailable (${phase}); pluginId=${params.pluginId}, digest=${params.digest}, environmentRef=${params.environmentRef}.`);
121
+ tx.saveCapabilityGrant(taskId, recordGrantUse(grant, new Date(), reservation));
122
+ return grant.id;
123
+ });
124
+ return () => {
125
+ environment(taskId, preparationId);
126
+ const grant = store.listCapabilityGrants(taskId).find((candidate) => candidate.id === grantId);
127
+ if (!grant || (reservation !== undefined && !grant.useReservations.includes(reservation))
128
+ || !checkGrant(grant, request, new Date(), { skipUsesCheck: true }).allowed) {
129
+ throw new Error("Plugin execution authority was revoked.");
130
+ }
131
+ };
132
+ };
133
+ const prepare = async (taskId, preparationId, pkg, phase, executionInput = pkg, admittedExecution) => {
134
+ if (pkg.manifest.kind === "declarative") {
135
+ const implementation = interpretPlugin(pkg);
136
+ const release = retainEnvironment(taskId, preparationId);
137
+ return { implementation, dispose: async () => { release(); } };
138
+ }
139
+ const check = admittedExecution ?? execution(taskId, preparationId, executionInput, phase);
140
+ const env = environment(taskId, preparationId);
141
+ const process = new PluginProcess(env.directory.path);
142
+ const release = retainEnvironment(taskId, preparationId);
143
+ const dispose = async () => { try {
144
+ await process.dispose();
145
+ }
146
+ finally {
147
+ release();
148
+ } };
149
+ try {
150
+ check();
151
+ await process.load(pkg);
152
+ check();
153
+ if (phase === "validate") {
154
+ await process.test();
155
+ check();
156
+ }
157
+ const implementation = {
158
+ invoke(name, input, invocation) {
159
+ const checkCall = execution(taskId, preparationId, pkg, "call");
160
+ return process.invoke(name, input, invocation, checkCall);
161
+ }
162
+ };
163
+ return { implementation, dispose };
164
+ }
165
+ catch (error) {
166
+ try {
167
+ await dispose();
168
+ }
169
+ catch (cleanup) {
170
+ throw new AggregateError([error, cleanup], "Plugin initialization and candidate cleanup failed.");
171
+ }
172
+ throw error;
173
+ }
174
+ };
175
+ return {
176
+ current: inspect,
177
+ list(taskId) {
178
+ return store.transaction(() => store.listPluginIntents(taskId)
179
+ .sort((left, right) => left.pluginId.localeCompare(right.pluginId))
180
+ .map((intent) => inspect(taskId, intent.pluginId)));
181
+ },
182
+ assertEnvironmentUnused(taskId, preparationId) {
183
+ if ([...environmentUsers.values()].some((owner) => owner.taskId === taskId && owner.preparationId === preparationId)) {
184
+ throw new Error("Environment has live plugin references; disable and drain its plugins or finish validation first.");
185
+ }
186
+ },
187
+ scan(taskId, preparationId, directory) {
188
+ const { path } = packageDirectory(taskId, preparationId, directory);
189
+ const pkg = readPluginPackage(path, false);
190
+ return { directory: path, preparationId, digest: pkg.digest, manifest: pkg.manifest,
191
+ files: Object.keys(pkg.files), executedAuthorCode: false };
192
+ },
193
+ create(taskId, preparationId, id, kind) {
194
+ if (!/^[a-z][a-z0-9-]*$/u.test(id) || !["declarative", "trusted-local"].includes(kind))
195
+ throw new Error("Invalid plugin template.");
196
+ const env = environment(taskId, preparationId);
197
+ const directory = join(env.directory.path, id);
198
+ mkdirSync(directory, { mode: 0o700 }); // Never overwrite an author's directory.
199
+ const name = `${id}.echo`;
200
+ const manifest = {
201
+ id, version: "1.0.0", apiVersion: "1", kind, entry: kind === "declarative" ? "entry.json" : "entry.mjs",
202
+ capabilities: [{ name, contractVersion: "1", summary: "Return JSON input.", inputSchema: {}, outputSchema: {},
203
+ effect: "query", requiredPermissions: [] }], required: [], permissions: [], reloadMode: "manual"
204
+ };
205
+ writeFileSync(join(directory, "plugin.json"), JSON.stringify(manifest, null, 2) + "\n", { flag: "wx", mode: 0o600 });
206
+ writeFileSync(join(directory, manifest.entry), kind === "declarative"
207
+ ? JSON.stringify({ [name]: { type: "echo" } }, null, 2) + "\n"
208
+ : `export function initialize() {\n return { handlers: { ${JSON.stringify(name)}: async (input) => input }, dispose() {} };\n}\nexport function selfTest() { return true; }\n`, { flag: "wx", mode: 0o600 });
209
+ return { directory, preparationId, kind, executedAuthorCode: false };
210
+ },
211
+ async validate(context, preparationId, directory) {
212
+ const taskId = context.targetId;
213
+ const { env, path } = packageDirectory(taskId, preparationId, directory);
214
+ const source = readPluginPackage(path, false);
215
+ registry.checkRegistration(context, descriptors(taskId, source, refFor(taskId, source)));
216
+ const checks = ["data-only manifest/schema/namespace/permissions/dependencies"];
217
+ let pkg = source;
218
+ if (source.manifest.build) {
219
+ const check = execution(taskId, preparationId, source, "build");
220
+ const [script, ...args] = source.manifest.build;
221
+ packagePath(script);
222
+ if (!Object.hasOwn(source.files, script))
223
+ throw new Error("Build script must be inside the captured source package.");
224
+ const buildDirectory = mkdtempSync(join(env.directory.path, ".plugin-build-"));
225
+ const release = retainEnvironment(taskId, preparationId);
226
+ try {
227
+ for (const [file, content] of Object.entries(source.files)) {
228
+ const target = join(buildDirectory, file);
229
+ mkdirSync(dirname(target), { recursive: true });
230
+ writeFileSync(target, content, { flag: "wx", mode: 0o600 });
231
+ }
232
+ check();
233
+ await execute(process.execPath, [join(buildDirectory, script), ...args], {
234
+ cwd: buildDirectory, env: pluginProcessEnvironment(buildDirectory),
235
+ timeout: 30_000, maxBuffer: 64 * 1024
236
+ });
237
+ check();
238
+ pkg = readPluginPackage(buildDirectory);
239
+ checks.push(`build cwd=${buildDirectory}; executable=${process.execPath}; argv=${JSON.stringify(source.manifest.build)}; exit=0`);
240
+ }
241
+ finally {
242
+ try {
243
+ if (realpathSync(buildDirectory) !== buildDirectory)
244
+ throw new Error("Build directory identity changed; retained for inspection.");
245
+ rmSync(buildDirectory, { recursive: true });
246
+ }
247
+ finally {
248
+ release();
249
+ }
250
+ }
251
+ checks.push("trusted-local Node build completed");
252
+ }
253
+ else {
254
+ if (!Object.hasOwn(source.files, source.manifest.entry))
255
+ throw new Error("Plugin entry is missing.");
256
+ checks.push("no build command; package is directly loadable");
257
+ }
258
+ // Builds may change products, not silently replace permissions or contract.
259
+ if (JSON.stringify(pkg.manifest) !== JSON.stringify(source.manifest))
260
+ throw new Error("Build changed plugin manifest; inspect and validate explicitly.");
261
+ registry.checkRegistration(context, descriptors(taskId, pkg, refFor(taskId, pkg)));
262
+ const candidate = await prepare(taskId, preparationId, pkg, "validate", source);
263
+ await candidate.dispose();
264
+ checks.push(pkg.manifest.kind === "declarative" ? "trusted interpreter parsed complete entry" : "child initialize/full contribution set/selfTest/dispose");
265
+ const current = packageDirectory(taskId, preparationId, directory);
266
+ if (readPluginPackage(current.path, false).digest !== source.digest)
267
+ throw new Error("Plugin changed during validation.");
268
+ registry.checkRegistration(context, descriptors(taskId, pkg, refFor(taskId, pkg)));
269
+ const validation = {
270
+ schemaVersion: 1, id: `validation-${randomUUID()}`, taskId, directory: path, preparationId, sourceDigest: source.digest, package: pkg,
271
+ environment: { ...env.directory, isolation: "trusted-local", node: process.version },
272
+ checks, createdAt: new Date().toISOString()
273
+ };
274
+ store.savePluginValidation(validation);
275
+ return { ...validation, package: { manifest: pkg.manifest, digest: pkg.digest } };
276
+ },
277
+ inspect(taskId, validationId) {
278
+ const validation = store.getPluginValidation(taskId, validationId);
279
+ if (!validation)
280
+ throw new Error("Plugin validation not found in this Task.");
281
+ const { files: _files, ...pkg } = validation.package;
282
+ return { ...validation, package: pkg };
283
+ },
284
+ async activate(context, validationId) {
285
+ const taskId = context.targetId;
286
+ const validation = store.getPluginValidation(taskId, validationId);
287
+ if (!validation)
288
+ throw new Error("Plugin validation not found in this Task.");
289
+ const { package: pkg, preparationId, directory } = validation;
290
+ const verify = () => {
291
+ const current = packageDirectory(taskId, preparationId, directory);
292
+ const expected = validation.environment;
293
+ if (current.env.directory.device !== expected.device || current.env.directory.inode !== expected.inode
294
+ || current.env.directory.path !== expected.path || expected.node !== process.version) {
295
+ throw new Error("Validation execution environment changed; validate again.");
296
+ }
297
+ if (readPluginPackage(current.path, false).digest !== validation.sourceDigest)
298
+ throw new Error("Plugin bytes differ from validated source.");
299
+ };
300
+ verify();
301
+ const ref = refFor(taskId, pkg);
302
+ const entries = descriptors(taskId, pkg, ref);
303
+ registry.checkRegistration(context, entries);
304
+ // Intent is admitted only after input, content, environment, caller and
305
+ // execution authorization pass. Grant consumption and selection commit
306
+ // together, before any author initialization or publication.
307
+ let admittedExecution;
308
+ const intent = store.transaction(() => {
309
+ if (pkg.manifest.kind === "trusted-local")
310
+ admittedExecution = execution(taskId, preparationId, pkg, "activate");
311
+ return choose(taskId, pkg.manifest.id, true, validationId);
312
+ });
313
+ let candidate;
314
+ let attached = false;
315
+ let previous;
316
+ try {
317
+ candidate = await prepare(taskId, preparationId, pkg, "activate", pkg, admittedExecution);
318
+ verify();
319
+ registry.checkRegistration(context, entries);
320
+ if (store.getPluginIntent(taskId, pkg.manifest.id)?.revision !== intent.revision) {
321
+ throw new Error("Plugin activation superseded by another explicit management action.");
322
+ }
323
+ const implementation = candidate.implementation;
324
+ host.attach(ref, {
325
+ invoke: (name, input, invocation) => {
326
+ environment(taskId, preparationId);
327
+ return implementation.invoke(name, input, invocation);
328
+ }
329
+ }, [candidate.dispose]);
330
+ attached = true;
331
+ previous = active.get(ref.id);
332
+ registry.register(entries);
333
+ active.set(ref.id, { ref, validationId });
334
+ }
335
+ catch (error) {
336
+ let failure = error;
337
+ try {
338
+ if (attached)
339
+ await host.detach(ref);
340
+ else
341
+ await candidate?.dispose();
342
+ }
343
+ catch (cleanup) {
344
+ failure = new Error(`${error instanceof Error ? error.message : String(error)}; candidate cleanup failed: ${String(cleanup)}`);
345
+ }
346
+ throw fail(intent, failure);
347
+ }
348
+ // Publication has committed. A later diagnostic/read failure must never
349
+ // enter candidate rollback or detach the new selected implementation.
350
+ if (previous) {
351
+ const retired = previous;
352
+ void host.detach(retired.ref).catch((error) => {
353
+ resources.saveArtifact(taskId, { kind: "content", displayName: "Plugin instance cleanup failure",
354
+ provenance: `PluginService.dispose ${retired.ref.id}/${retired.ref.generation}`,
355
+ content: error instanceof Error ? error.message : String(error) });
356
+ });
357
+ }
358
+ return { provider: ref, validationId, digest: pkg.digest, capabilities: entries.map((entry) => entry.name),
359
+ lifetime: "current-controller", previousDraining: previous?.ref, ...inspect(taskId, pkg.manifest.id) };
360
+ },
361
+ async disable(taskId, pluginId) {
362
+ validatePluginId(pluginId);
363
+ const key = keyFor(taskId, pluginId);
364
+ // A failed commit must leave the currently selected implementation alone.
365
+ const intent = choose(taskId, pluginId, false);
366
+ const current = active.get(key);
367
+ if (!current)
368
+ return { disabled: true, providerId: key, ...inspect(taskId, pluginId) };
369
+ registry.disable(key);
370
+ active.delete(key);
371
+ try {
372
+ await host.detach(current.ref);
373
+ }
374
+ catch (error) {
375
+ throw fail(intent, new Error(`Plugin disabled; owned cleanup failed: ${error instanceof Error ? error.message : String(error)}`));
376
+ }
377
+ return { disabled: true, provider: current.ref, drained: true, ...inspect(taskId, pluginId) };
378
+ }
379
+ };
380
+ }
@@ -75,7 +75,7 @@ export function builtinAgentProfileInputs() {
75
75
  {
76
76
  id: "reviewer",
77
77
  description: "Review one candidate against the user's core outcome, supported behavior, and direct evidence.",
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.",
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 AgentRun'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
79
  defaultAccess: "write",
80
80
  runtime: { source: "global-worker" }
81
81
  }
@@ -1,6 +1,6 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
- import { lstat, mkdir, realpath, rm } from "node:fs/promises";
3
+ import { lstat, mkdir, readFile, realpath, rm } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  const executeFile = promisify(execFile);
@@ -688,7 +688,8 @@ export class NodeGitWorkspace {
688
688
  repositoryPath: input.repositoryPath,
689
689
  container: input.container,
690
690
  identity: integrationWorktreeIdentity(input.taskSegment, input.integrationId),
691
- baseRef: input.baseRef
691
+ baseRef: input.baseRef,
692
+ allowRebase: true
692
693
  });
693
694
  }
694
695
  async #ensureManagedWorktree(input) {
@@ -703,7 +704,7 @@ export class NodeGitWorkspace {
703
704
  // original base branch/tag is later deleted.
704
705
  const project = await this.inspect(input.repositoryPath);
705
706
  await assertOwnedWorktree(project, container, path);
706
- await assertExpectedBranch(path, identity.branch);
707
+ await assertExpectedBranch(path, identity.branch, input.allowRebase);
707
708
  const head = await gitLine([
708
709
  "-C", path, "rev-parse", "--verify", "--end-of-options", "HEAD^{commit}"
709
710
  ]);
@@ -917,7 +918,28 @@ function gitRefSegment(identity) {
917
918
  const digest = createHash("sha256").update(identity).digest("hex").slice(0, 24);
918
919
  return `encoded-${digest}`;
919
920
  }
920
- async function assertExpectedBranch(path, expected) {
921
+ async function assertExpectedBranch(path, expected, allowRebase = false) {
922
+ if (allowRebase && !await gitSucceeds(["-C", path, "symbolic-ref", "--quiet", "HEAD"])) {
923
+ // Rebase temporarily detaches HEAD. Git's per-worktree metadata must still
924
+ // identify this exact Integration branch; unrelated detachment is invalid.
925
+ for (const backend of ["rebase-merge", "rebase-apply"]) {
926
+ const headNamePath = await gitLine(["-C", path, "rev-parse", "--path-format=absolute",
927
+ "--git-path", `${backend}/head-name`]);
928
+ let headName;
929
+ try {
930
+ headName = (await readFile(headNamePath, "utf8")).trim();
931
+ }
932
+ catch (error) {
933
+ if (error.code === "ENOENT")
934
+ continue;
935
+ throw error;
936
+ }
937
+ if (headName === `refs/heads/${expected}`)
938
+ return;
939
+ throw new Error(`Managed worktree is rebasing an unexpected branch: ${headName}.`);
940
+ }
941
+ throw new Error("Managed worktree has detached HEAD without its expected rebase.");
942
+ }
921
943
  const branch = await gitLine(["-C", path, "symbolic-ref", "--short", "HEAD"]);
922
944
  if (branch !== expected) {
923
945
  throw new Error(`Managed worktree is on an unexpected branch: ${branch}.`);
@@ -8,7 +8,7 @@ export function managedProjectPath(home, projectId) {
8
8
  export function createProject(id, name, path, branches, now, metadata = {}) {
9
9
  const timestamp = now.toISOString();
10
10
  return validateProject({
11
- schemaVersion: 5,
11
+ schemaVersion: 6,
12
12
  id: requireIdentity(id, "Project id"),
13
13
  name: validateProjectName(name),
14
14
  aliases: normalizeAliases(metadata.aliases ?? [], name),
@@ -21,6 +21,8 @@ export function createProject(id, name, path, branches, now, metadata = {}) {
21
21
  developmentBranch: requireGitRef(branches.development, "Project development branch"),
22
22
  status: "active",
23
23
  knowledge: [],
24
+ resourceRefs: [],
25
+ defaultCapabilityProviders: {},
24
26
  knowledgeProposals: [],
25
27
  createdAt: timestamp,
26
28
  updatedAt: timestamp
@@ -31,7 +33,7 @@ export function validateProjectName(value) {
31
33
  }
32
34
  /**
33
35
  * Soft-retire a Project: mark it retired and record the audit trail. The
34
- * catalog record, checkout, and every historical Task/Turn/Review/Integration/
36
+ * catalog record, checkout, and every historical Task/AgentRun/Review/Integration/
35
37
  * Publication reference are retained. Retiring an already-retired Project
36
38
  * fails closed; a retired Project must be deleted (or kept) as-is.
37
39
  */
@@ -276,8 +278,21 @@ export function assertProjectCatalog(projects) {
276
278
  }
277
279
  }
278
280
  export function validateProject(project) {
279
- if (project.schemaVersion !== 5) {
280
- throw new Error("Project must use schemaVersion 5.");
281
+ if (project.schemaVersion !== 6) {
282
+ throw new Error("Project must use schemaVersion 6.");
283
+ }
284
+ if (!Array.isArray(project.resourceRefs))
285
+ throw new Error("Project resourceRefs must be an array.");
286
+ for (const id of project.resourceRefs)
287
+ requireIdentity(id, "Project resource");
288
+ if (new Set(project.resourceRefs).size !== project.resourceRefs.length)
289
+ throw new Error("Duplicate Project resource.");
290
+ if (!project.defaultCapabilityProviders || typeof project.defaultCapabilityProviders !== "object"
291
+ || Array.isArray(project.defaultCapabilityProviders))
292
+ throw new Error("Invalid Project capability defaults.");
293
+ for (const [name, provider] of Object.entries(project.defaultCapabilityProviders)) {
294
+ requireText(name, "Capability name");
295
+ requireText(provider, "Capability provider reference");
281
296
  }
282
297
  requireIdentity(project.id, "Project id");
283
298
  requireIdentity(project.name, "Project name");
@@ -61,10 +61,10 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
61
61
  const task = requireTask(store, taskId);
62
62
  const git = options.git ?? newGitWorkspace();
63
63
  const workspace = store.getTaskWorkspace(taskId);
64
- // Collect active Turn workspace snapshots once so every Project entry can
65
- // report whether a live Turn is pinned to a different commit.
66
- const activeTurns = store.listTurns(taskId)
67
- .filter((turn) => turn.status === "active" && turn.workspace !== undefined);
64
+ // Collect active AgentRun workspace snapshots once so every Project entry can
65
+ // report whether a live AgentRun is pinned to a different commit.
66
+ const activeRuns = store.listRuns(taskId)
67
+ .filter((run) => run.status === "active" && run.workspace !== undefined);
68
68
  const entries = await Promise.all(task.projectBindings.map(async (binding) => {
69
69
  const project = requireProject(store, binding.projectId);
70
70
  const entry = workspace === null ? undefined : workspaceProjectEntry(workspace, project.id);
@@ -77,12 +77,12 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
77
77
  catch {
78
78
  physicalHead = undefined;
79
79
  }
80
- // Find the first active Turn whose workspace snapshot covers this Project.
81
- const turnSnapshot = activeTurns.find((turn) => {
82
- if (turn.workspace === undefined)
80
+ // Find the first active AgentRun whose workspace snapshot covers this Project.
81
+ const runSnapshot = activeRuns.find((run) => {
82
+ if (run.workspace === undefined)
83
83
  return false;
84
- const turnEntry = workspaceProjectEntry(turn.workspace, project.id);
85
- return turnEntry !== undefined;
84
+ const runEntry = workspaceProjectEntry(run.workspace, project.id);
85
+ return runEntry !== undefined;
86
86
  });
87
87
  const provenance = latestBaseProvenance(store.listEvents(taskId), project.id);
88
88
  const tracked = await resolveTracked(git, project, workspacePath, options.refresh === true);
@@ -99,11 +99,11 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
99
99
  workspacePath,
100
100
  workspaceClean,
101
101
  ...(physicalHead === undefined ? {} : { physicalHead }),
102
- ...(turnSnapshot?.workspace === undefined
102
+ ...(runSnapshot?.workspace === undefined
103
103
  ? {}
104
104
  : {
105
- turnSnapshotCommit: workspaceProjectEntry(turnSnapshot.workspace, project.id)?.baseCommit,
106
- turnSnapshotId: turnSnapshot.id
105
+ runSnapshotCommit: workspaceProjectEntry(runSnapshot.workspace, project.id)?.baseCommit,
106
+ runSnapshotId: runSnapshot.id
107
107
  }),
108
108
  ...(tracked === undefined ? {} : {
109
109
  trackedRef: tracked.ref,
@@ -161,7 +161,7 @@ export function renderTaskBaseFreshnessReport(report) {
161
161
  `Task base freshness: ${report.taskId} (source: ${report.refreshed ? "remote refresh" : "local tracking"})`
162
162
  ];
163
163
  for (const entry of report.entries) {
164
- lines.push(`- ${entry.directory}: ${entry.projectId} @ ${entry.baseCommit}`, ` status: ${entry.status}; workspace: ${entry.workspaceClean === null ? "unknown" : entry.workspaceClean ? "clean" : "dirty"}`, ` physical HEAD: ${entry.physicalHead ?? "-"}`, ` Turn snapshot: ${entry.turnSnapshotCommit ?? "-"}${entry.turnSnapshotId === undefined ? "" : ` (${entry.turnSnapshotId})`}`, ` observed remote: ${entry.observedRemoteUrl ?? "-"}`, ` tracked: ${entry.trackedCommit ?? "-"}${entry.trackedRef === undefined ? "" : ` (${entry.trackedRef})`}`, ` observed: ${entry.observedTrackingCommit ?? "-"}${entry.observedAt === undefined ? "" : ` at ${entry.observedAt}`}`);
164
+ lines.push(`- ${entry.directory}: ${entry.projectId} @ ${entry.baseCommit}`, ` status: ${entry.status}; workspace: ${entry.workspaceClean === null ? "unknown" : entry.workspaceClean ? "clean" : "dirty"}`, ` physical HEAD: ${entry.physicalHead ?? "-"}`, ` AgentRun snapshot: ${entry.runSnapshotCommit ?? "-"}${entry.runSnapshotId === undefined ? "" : ` (${entry.runSnapshotId})`}`, ` observed remote: ${entry.observedRemoteUrl ?? "-"}`, ` tracked: ${entry.trackedCommit ?? "-"}${entry.trackedRef === undefined ? "" : ` (${entry.trackedRef})`}`, ` observed: ${entry.observedTrackingCommit ?? "-"}${entry.observedAt === undefined ? "" : ` at ${entry.observedAt}`}`);
165
165
  if (entry.risk !== undefined)
166
166
  lines.push(` risk: ${entry.risk}`);
167
167
  if (entry.error !== undefined)