@zq-silk/yui 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +19 -3
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +25 -11
  7. package/dist/cli/interactionPolicy.js +4 -0
  8. package/dist/cli/invocationAuthority.js +1 -1
  9. package/dist/commands/executionAuditCommands.js +5 -0
  10. package/dist/commands/globalRoleCommands.js +41 -3
  11. package/dist/commands/projectCommands.js +89 -94
  12. package/dist/commands/roleSkillValidation.js +4 -8
  13. package/dist/commands/taskCommands.js +30 -9
  14. package/dist/commands/taskIntegrationCommands.js +5 -16
  15. package/dist/commands/taskUsageCommand.js +38 -0
  16. package/dist/context/roleSessionContext.js +18 -2
  17. package/dist/context/runContextPack.js +5 -1
  18. package/dist/context/sessionBootstrapManifest.js +10 -1
  19. package/dist/context/skillCatalog.js +108 -0
  20. package/dist/context/skillPackage.js +161 -0
  21. package/dist/context/taskContext.js +3 -2
  22. package/dist/context/taskSearch.js +127 -0
  23. package/dist/controlPlaneCli.js +51 -39
  24. package/dist/controller/clientRuntime.js +3 -2
  25. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  26. package/dist/controller/jobControl.js +2 -0
  27. package/dist/controller/nativeHostInput.js +37 -0
  28. package/dist/controller/runtime.js +93 -2
  29. package/dist/controller/runtimeEventInbox.js +2 -2
  30. package/dist/controller/runtimeEventProcessor.js +3 -3
  31. package/dist/doctor/doctor.js +10 -0
  32. package/dist/execution/reviewMainRun.js +1 -0
  33. package/dist/execution/workItemMainRun.js +1 -0
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  37. package/dist/integration/gitIntegrationService.js +15 -79
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/job/httpPreview.js +61 -0
  40. package/dist/job/jobCleanupInspection.js +67 -0
  41. package/dist/job/jobRunner.js +12 -2
  42. package/dist/kernel/builtinCapabilities.js +3 -1
  43. package/dist/message/message.js +15 -1
  44. package/dist/output/rolePresentation.js +4 -1
  45. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  46. package/dist/repository/gitCompatibility.js +62 -0
  47. package/dist/repository/gitWorkspace.js +86 -80
  48. package/dist/repository/gitWorktreeInventory.js +121 -0
  49. package/dist/repository/project.js +74 -25
  50. package/dist/repository/taskWorkspaceCoordinator.js +72 -145
  51. package/dist/repository/taskWorkspacePreparer.js +39 -43
  52. package/dist/repository/workspaceCleanupInspection.js +88 -7
  53. package/dist/resources/resourceDiscovery.js +2 -2
  54. package/dist/resources/resourceGc.js +7 -4
  55. package/dist/resources/resourceRegistrar.js +18 -11
  56. package/dist/runtime/agentEndpoint.js +8 -0
  57. package/dist/runtime/agentHost.js +170 -3
  58. package/dist/runtime/codexAppServerRuntime.js +1 -1
  59. package/dist/runtime/codexNativeAccess.js +175 -0
  60. package/dist/runtime/controlledCodexTui.js +116 -0
  61. package/dist/runtime/integrationRuntimeIsolation.js +21 -0
  62. package/dist/runtime/publicReply.js +23 -0
  63. package/dist/runtime/runtimeObservation.js +48 -0
  64. package/dist/runtime/structuredProviderHost.js +16 -7
  65. package/dist/runtime/taskCostMetrics.js +133 -0
  66. package/dist/runtime/taskRuntimeIsolation.js +91 -21
  67. package/dist/runtime/taskUsageMetrics.js +11 -2
  68. package/dist/runtime/taskUsageQuery.js +38 -0
  69. package/dist/scheduler/taskExecutionProjection.js +2 -0
  70. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  71. package/dist/storage/homeLayout.js +13 -0
  72. package/dist/storage/sqliteSchema.js +44 -0
  73. package/dist/storage/sqliteStore.js +53 -0
  74. package/dist/storage/storageVersions.js +1 -1
  75. package/dist/storage/storeRpc.js +2 -0
  76. package/dist/storage/taskSearch.js +35 -0
  77. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  78. package/dist/task/archiveDiagnostics.js +26 -8
  79. package/dist/task/archivePreflight.js +26 -9
  80. package/dist/task/leaderArchive.js +9 -15
  81. package/dist/task/ordinaryArchive.js +42 -0
  82. package/dist/tmux/tmuxManager.js +8 -8
  83. package/dist/web/assets/assetManifest.js +11 -51
  84. package/dist/web/assets/client/app/catalog.js +215 -0
  85. package/dist/web/assets/client/app/main.js +148 -0
  86. package/dist/web/assets/client/app/selection.js +139 -0
  87. package/dist/web/assets/client/app/settings.js +326 -0
  88. package/dist/web/assets/client/app/taskView.js +280 -0
  89. package/dist/web/assets/client/domain/context.js +96 -0
  90. package/dist/web/assets/client/domain/records.js +126 -0
  91. package/dist/web/assets/client/domain/runs.js +119 -0
  92. package/dist/web/assets/client/domain/taskForms.js +197 -0
  93. package/dist/web/assets/client/domain/vocab.js +69 -0
  94. package/dist/web/assets/client/domain/work.js +116 -0
  95. package/dist/web/assets/client/i18n/en.js +900 -0
  96. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  97. package/dist/web/assets/client/index.js +114 -0
  98. package/dist/web/assets/client/layout/resizer.js +37 -0
  99. package/dist/web/assets/client/layout/router.js +35 -0
  100. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  101. package/dist/web/assets/client/layout/sizing.js +95 -0
  102. package/dist/web/assets/client/layout/workspace.js +247 -0
  103. package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
  104. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  105. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  106. package/dist/web/assets/client/lib/i18n.js +54 -0
  107. package/dist/web/assets/client/lib/markdown.js +167 -0
  108. package/dist/web/assets/client/lib/prefs.js +28 -0
  109. package/dist/web/assets/client/lib/theme.js +68 -0
  110. package/dist/web/assets/client/ui/containers.js +101 -0
  111. package/dist/web/assets/client/ui/controls.js +51 -0
  112. package/dist/web/assets/client/ui/forms.js +48 -0
  113. package/dist/web/assets/client/ui/metrics.js +137 -0
  114. package/dist/web/assets/client/ui/primitives.js +92 -0
  115. package/dist/web/assets/client/ui/text.js +33 -0
  116. package/dist/web/assets/client/ui/toast.js +12 -0
  117. package/dist/web/assets/client/views/bodySearch.js +172 -0
  118. package/dist/web/assets/client/views/createTask.js +85 -0
  119. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  120. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  121. package/dist/web/assets/client/views/dock/preview.js +129 -0
  122. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  123. package/dist/web/assets/client/views/globalInput.js +67 -0
  124. package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
  125. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  126. package/dist/web/assets/client/views/skills.js +152 -0
  127. package/dist/web/assets/client/views/task/actions.js +36 -0
  128. package/dist/web/assets/client/views/task/delivery.js +185 -0
  129. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  130. package/dist/web/assets/client/views/task/files.js +272 -0
  131. package/dist/web/assets/client/views/task/observation.js +153 -0
  132. package/dist/web/assets/client/views/task/overview.js +161 -0
  133. package/dist/web/assets/client/views/task/page.js +131 -0
  134. package/dist/web/assets/client/views/task/records.js +163 -0
  135. package/dist/web/assets/client/views/task/runtime.js +130 -0
  136. package/dist/web/assets/client/views/task/timeline.js +190 -0
  137. package/dist/web/assets/shared/geometry.js +19 -0
  138. package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
  139. package/dist/web/assets/shared/taskTabs.js +8 -0
  140. package/dist/web/assets/shared/themes.js +93 -0
  141. package/dist/web/assets/shell/dialogs.js +63 -0
  142. package/dist/web/assets/shell/dock.js +40 -0
  143. package/dist/web/assets/shell/index.js +40 -0
  144. package/dist/web/assets/shell/settings.js +34 -0
  145. package/dist/web/assets/shell/sidebar.js +45 -0
  146. package/dist/web/assets/styles/index.js +41 -0
  147. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
  148. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  149. package/dist/web/assets/styles/markdown.js +22 -3
  150. package/dist/web/assets/styles/tokens.js +10 -63
  151. package/dist/web/assets/styles/ui/controls.js +76 -0
  152. package/dist/web/assets/styles/ui/forms.js +46 -0
  153. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  154. package/dist/web/assets/styles/views/delivery.js +29 -0
  155. package/dist/web/assets/styles/views/dock.js +71 -0
  156. package/dist/web/assets/styles/views/page.js +32 -0
  157. package/dist/web/assets/styles/views/records.js +70 -0
  158. package/dist/web/assets/styles/views/settings.js +26 -0
  159. package/dist/web/assets/styles/views/sidebar.js +74 -0
  160. package/dist/web/assets/styles/views/task.js +80 -0
  161. package/dist/web/assets/styles/views/terminal.js +38 -0
  162. package/dist/web/taskPreviews.js +205 -0
  163. package/dist/web/tmuxWebTerminal.js +16 -9
  164. package/dist/web/webConversation.js +373 -0
  165. package/dist/web/webMaterials.js +155 -0
  166. package/dist/web/webServer.js +306 -18
  167. package/dist/web/webSettings.js +331 -0
  168. package/dist/web/webSkills.js +86 -0
  169. package/dist/web/webSnapshot.js +40 -8
  170. package/dist/web/webTaskSurface.js +114 -9
  171. package/dist/workspace/gitChangeSetCapture.js +3 -1
  172. package/docs/managed-turn-and-session-runtime.md +9 -0
  173. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  174. package/docs/observability/README.md +13 -3
  175. package/docs/observability/README.zh-CN.md +10 -3
  176. package/docs/release-workflow.md +53 -0
  177. package/docs/release-workflow.zh-CN.md +40 -0
  178. package/docs/storage-baseline.md +38 -1
  179. package/docs/storage-baseline.zh-CN.md +22 -0
  180. package/docs/task-delivery.md +36 -8
  181. package/docs/task-delivery.zh-CN.md +24 -7
  182. package/docs/task-discovery.md +28 -0
  183. package/docs/testing/verification-levels.md +2 -2
  184. package/i18n/README.zh-CN.md +4 -3
  185. package/package.json +1 -1
  186. package/skills/yui-leader/references/execution.md +2 -1
  187. package/skills/yui-leader/references/integration.md +20 -0
  188. package/skills/yui-operator/SKILL.md +4 -0
  189. package/skills/yui-reviewer/SKILL.md +3 -0
  190. package/skills/yui-runtime/SKILL.md +4 -0
  191. package/skills/yui-runtime/references/publication.md +20 -4
  192. package/skills/yui-worker/SKILL.md +3 -0
  193. package/dist/web/assets/client/app.js +0 -804
  194. package/dist/web/assets/client/components.js +0 -418
  195. package/dist/web/assets/client/detail.js +0 -333
  196. package/dist/web/assets/client/dock.js +0 -225
  197. package/dist/web/assets/client/evidence.js +0 -172
  198. package/dist/web/assets/client/forms.js +0 -230
  199. package/dist/web/assets/client/i18n.js +0 -1161
  200. package/dist/web/assets/client/markdown.js +0 -114
  201. package/dist/web/assets/client/records.js +0 -63
  202. package/dist/web/assets/client/sections.js +0 -313
  203. package/dist/web/assets/client/theme.js +0 -71
  204. package/dist/web/assets/shell.js +0 -161
  205. package/dist/web/assets/styles/components.js +0 -169
  206. package/dist/web/assets/styles/views.js +0 -292
@@ -35,7 +35,7 @@ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } fro
35
35
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
36
36
  import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
37
37
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
38
- import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
38
+ import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeUsageEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
39
39
  import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
40
40
  import { enqueueSupervisorEvent, recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
41
41
  import { RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT, foldRunProgressFacts, isRoleRunStalled, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey } from "../scheduler/roleRunStall.js";
@@ -1192,6 +1192,16 @@ export class FileSchedulerStoreAdapter {
1192
1192
  throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
1193
1193
  }
1194
1194
  const currentRun = binding.run;
1195
+ if (input.roleName === "leader" && input.attemptId.startsWith(`notification:${input.taskId}/`)) {
1196
+ const wakeId = input.attemptId.split("/")[1];
1197
+ const wake = store.listTaskWakes(input.taskId).find(w => w.id === wakeId);
1198
+ const ids = new Set(wake?.refs?.filter(r => r.type === "message").map(r => r.id));
1199
+ if (store.listMessages(input.taskId).some(m => ids.has(m.id)
1200
+ && m.inputControl?.expectedSessionId !== undefined
1201
+ && m.inputControl.expectedSessionId !== input.nativeSessionId)) {
1202
+ throw new AgentHostProviderTurnFenceError("Notification input belongs to the selected old Session; do not retarget.");
1203
+ }
1204
+ }
1195
1205
  if (session.effective.executionEnvironment !== undefined) {
1196
1206
  assertExecutionEnvironmentCurrent(store, input.taskId, session.effective.executionEnvironment);
1197
1207
  }
@@ -1248,7 +1258,7 @@ export class FileSchedulerStoreAdapter {
1248
1258
  // before the Provider write; provider-visible userMessage items alone
1249
1259
  // are NOT proof of human authorship (managed prompts use those too).
1250
1260
  // This shares the exact writer/session admission and its transaction.
1251
- if (input.authorityOwner === "human" && input.roleName === "leader"
1261
+ if ((input.authorityOwner === "human" || input.attemptId.startsWith(`human:${input.holderId}:`)) && input.roleName === "leader"
1252
1262
  && input.runId === undefined && input.boundedText !== undefined) {
1253
1263
  const prior = store.listEvents(input.taskId).find(event => event.type === "message.native-user-input" && event.payload.attemptId === input.attemptId);
1254
1264
  if (prior !== undefined) {
@@ -1770,7 +1780,7 @@ export class FileSchedulerStoreAdapter {
1770
1780
  directive: `Plan Task ${taskId} with the user. Persist requirements and decisions. Request activation explicitly; planning grants no delivery authority.`,
1771
1781
  contextSnapshotRef: contextSnapshotRef(snapshot),
1772
1782
  deltaRefIds: [] }), now, {
1773
- purpose: "planning", effective: resolveEffectiveLaunch({ role, purpose: "planning" })
1783
+ purpose: "planning", effective: resolveEffectiveLaunch({ store, role, purpose: "planning" })
1774
1784
  });
1775
1785
  store.saveRun(run);
1776
1786
  store.saveActiveRun(run);
@@ -1849,6 +1859,18 @@ export class FileSchedulerStoreAdapter {
1849
1859
  const sessions = store.getTaskRoleSessionSet(taskId, "leader");
1850
1860
  const session = sessions?.sessions[sessions.activeAgentId];
1851
1861
  const obsoleteIds = new Set();
1862
+ for (const message of store.listMessages(taskId)) {
1863
+ if (!pendingMessageIds.has(message.id) || message.inputControl?.expectedSessionId === undefined)
1864
+ continue;
1865
+ if (message.inputControl.expectedSessionId !== session?.nativeSessionId
1866
+ || message.continuation?.notDeliveredReason !== undefined) {
1867
+ obsoleteIds.add(message.id);
1868
+ if (message.continuation?.notDeliveredReason === undefined)
1869
+ store.updateMessage(taskId, {
1870
+ ...message, continuation: { notDeliveredReason: "selected-native-session-changed" }
1871
+ });
1872
+ }
1873
+ }
1852
1874
  for (const message of claimedMessages) {
1853
1875
  const claim = message.interruptThen;
1854
1876
  if (claim.notDeliveredReason !== undefined
@@ -3136,6 +3158,7 @@ function mapRole(store, role) {
3136
3158
  // Active AgentRun delivery separately and exclusively uses turn.effective.
3137
3159
  const effective = reopened || (liveSession !== null && liveSession.agentId !== role.activeAgentId)
3138
3160
  ? resolveEffectiveLaunch({
3161
+ store,
3139
3162
  role,
3140
3163
  purpose,
3141
3164
  ...(workspace === undefined ? {} : { workspace }),
@@ -3144,6 +3167,7 @@ function mapRole(store, role) {
3144
3167
  : liveSession !== null && workspace?.owner.type === "task"
3145
3168
  ? effectiveLaunchWithTaskMainWorkspace(liveSession.effective, workspace)
3146
3169
  : liveSession?.effective ?? resolveEffectiveLaunch({
3170
+ store,
3147
3171
  role,
3148
3172
  purpose,
3149
3173
  ...(workspace === undefined ? {} : { workspace }),
@@ -3199,6 +3223,8 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
3199
3223
  return effective;
3200
3224
  }
3201
3225
  function globalSessionEffective(role, existing) {
3226
+ // A native hook without a captured launch is not proof of loaded resources.
3227
+ // Actual controlled launches supply their frozen effective snapshot explicitly.
3202
3228
  return existing?.effective ?? resolveEffectiveLaunch({ role, purpose: "execution" });
3203
3229
  }
3204
3230
  function mapSession(session) {
@@ -3292,10 +3318,10 @@ function compactedRuntimeObservationIds(events, incoming) {
3292
3318
  });
3293
3319
  const remove = ({ observation }) => {
3294
3320
  if (incoming.kind === "activity.observed") {
3295
- if (isRuntimeTokenEvidence(incoming))
3321
+ if (isRuntimeUsageEvidence(incoming))
3296
3322
  return false;
3297
3323
  return observation.kind === "activity.observed"
3298
- && !isRuntimeTokenEvidence(observation);
3324
+ && !isRuntimeUsageEvidence(observation);
3299
3325
  }
3300
3326
  if (incoming.kind === "operation.started") {
3301
3327
  return (observation.kind === "operation.started"
@@ -3549,7 +3575,7 @@ function usageSnapshotIsSuperseded(events, incoming) {
3549
3575
  && observationIsStrictlyNewer(observation, incoming));
3550
3576
  }
3551
3577
  function hasPersistedRuntimeObservation(events, incoming) {
3552
- const usageIdentity = isRuntimeTokenEvidence(incoming)
3578
+ const usageIdentity = isRuntimeUsageEvidence(incoming)
3553
3579
  ? incoming.eventId
3554
3580
  : undefined;
3555
3581
  return events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
@@ -20,6 +20,7 @@ import { CliError } from "../errors/cliError.js";
20
20
  import { assertJobAssignmentScope, JobAssignmentScopeError } from "../job/jobAssignmentScope.js";
21
21
  import { assertContextRecordReadable } from "../context/taskContext.js";
22
22
  import { assertTaskProjectWriteAuthority } from "../task/taskAuthority.js";
23
+ import { validateHttpPreview } from "../job/httpPreview.js";
23
24
  export function createDurableJobControl(store) {
24
25
  const authority = createJobCallAuthority(store);
25
26
  return {
@@ -32,6 +33,7 @@ export function createDurableJobControl(store) {
32
33
  const context = authority.authenticate(Object.freeze({ ...params.caller }), params.taskId);
33
34
  requireJobAssignment(tx, params, params.caller);
34
35
  assertNonSecretJobInput(params);
36
+ validateHttpPreview(params.env, params.steps);
35
37
  const baseKey = durableJobIdempotencyKey({
36
38
  owner: params.owner,
37
39
  projectId: params.projectId,
@@ -0,0 +1,37 @@
1
+ import { applyTaskInputControl } from "../commands/taskCommands.js";
2
+ import { applyGlobalInputControl } from "../commands/globalRoleCommands.js";
3
+ /** The Host terminal is a human ingress, like the authenticated local Web
4
+ * surface. Fence that ingress before reusing the existing Message/control
5
+ * transaction; the Host performs the one Provider write after this returns. */
6
+ export function prepareNativeHostSteer(store, input) {
7
+ return store.transaction(tx => {
8
+ const set = input.taskId === undefined ? tx.getGlobalRoleSessionSet(input.roleName)
9
+ : tx.getTaskRoleSessionSet(input.taskId, input.roleName);
10
+ const session = set?.sessions[input.agentId], binding = set?.providerBinding;
11
+ if (!session || session.status !== "active" || session.adapterId !== "codex"
12
+ || set?.activeAgentId !== input.agentId || session.nativeSessionId !== input.nativeSessionId
13
+ || binding?.run?.status !== "accepted" || binding.run.nativeTurnId !== input.nativeTurnId
14
+ || binding.authority.owner !== input.authorityOwner || binding.authority.epoch !== input.authorityEpoch
15
+ || binding.authority.holderId !== input.holderId
16
+ || (input.taskId !== undefined && input.roleName !== "leader")) {
17
+ throw new Error("Native steer carries a stale or unsupported Session/Turn/writer fence.");
18
+ }
19
+ if (!input.boundedText.trim() || input.boundedText.includes("\0") || Buffer.byteLength(input.boundedText) > 12000) {
20
+ throw new Error("Native steer exceeds its text bound.");
21
+ }
22
+ const control = { action: "steer", body: input.boundedText, to: input.roleName,
23
+ requestId: input.attemptId, expectedTarget: input.nativeTurnId, expectedSessionId: input.nativeSessionId };
24
+ if (input.taskId === undefined) {
25
+ applyGlobalInputControl(input.roleName, control, tx, { env: {} });
26
+ }
27
+ else {
28
+ applyTaskInputControl(input.taskId, { ...control, to: "leader" }, tx, { environment: {}, now: () => input.now });
29
+ }
30
+ const messages = input.taskId === undefined ? tx.listGlobalRoleMessages(input.roleName) : tx.listMessages(input.taskId);
31
+ const message = messages.find(message => message.inputControl?.requestId === input.attemptId);
32
+ if (message?.control?.outcome !== "pending") {
33
+ throw new Error("Native steer is already settled or unconfirmed; do not resubmit.");
34
+ }
35
+ return { attemptId: message.control.receiptId, boundedText: input.boundedText };
36
+ });
37
+ }
@@ -1,4 +1,5 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
+ import { prepareNativeHostSteer } from "./nativeHostInput.js";
2
3
  import { archiveLeaderTask } from "../task/leaderArchive.js";
3
4
  import { TaskWorkspaceCoordinator } from "../repository/taskWorkspaceCoordinator.js";
4
5
  import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
@@ -33,8 +34,15 @@ import { openSchedulerTelemetry } from "../telemetry/telemetryWiring.js";
33
34
  import { NodeCommandExecutor } from "../tmux/commandExecutor.js";
34
35
  import { TmuxManager, yuiTmuxServerName } from "../tmux/tmuxManager.js";
35
36
  import { createControllerWeb } from "../web/controllerWeb.js";
37
+ import { createTaskPreviews } from "../web/taskPreviews.js";
36
38
  import { TmuxWebTerminalService } from "../web/tmuxWebTerminal.js";
37
39
  import { createWebTaskSurface } from "../web/webTaskSurface.js";
40
+ import { createWebConversationSurface } from "../web/webConversation.js";
41
+ import { WebRequestRejected } from "../web/webMutation.js";
42
+ import { inspectAgentHost, sendAgentHostNativeControl, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
43
+ import { archiveOrdinaryTask } from "../task/ordinaryArchive.js";
44
+ import { createWebSettings } from "../web/webSettings.js";
45
+ import { createWebSkills } from "../web/webSkills.js";
38
46
  import { isTaskOwnedWorkspace, sameManagedWorkspaceIdentity } from "../worktree/managedWorkspace.js";
39
47
  import { AgentRuntimeObserver } from "./agentRuntimeObserver.js";
40
48
  import { createCapabilityDispatcher } from "./capabilityBridge.js";
@@ -378,10 +386,64 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
378
386
  runningRuntime?.signal(`task:${target.taskId}`);
379
387
  },
380
388
  reconcileTask: (taskId) => runningRuntime?.signal(`task:${taskId}`)
381
- }, yuiHome: home });
389
+ }, yuiHome: home }, [], undefined, {
390
+ archive: async (taskId) => {
391
+ if (!(workspacePreparer instanceof FileTaskWorkspacePreparer))
392
+ throw new Error("Archive workspace owner unavailable.");
393
+ return archiveOrdinaryTask(new TaskWorkspaceCoordinator(store, workspacePreparer, webWorkflow), taskId, { environment: {}, yuiHome: home });
394
+ }
395
+ });
382
396
  const surfaces = new SurfaceContributions(kernel.capabilities.registry);
397
+ const validateNativeWrite = (request) => {
398
+ const sessions = request.scope === "task" ? store.getTaskRoleSessionSet(request.taskId, request.roleName)
399
+ : store.getGlobalRoleSessionSet(request.roleName);
400
+ const selected = sessions?.sessions[sessions.activeAgentId];
401
+ if (!request.nativeSessionId || selected?.nativeSessionId !== request.nativeSessionId
402
+ || selected.status !== "active"
403
+ || (request.scope === "task" && (request.roleName !== "leader"
404
+ || selected.adapterId !== "codex" || !sessions?.providerBinding
405
+ || !["active", "draft"].includes(store.getTask(request.taskId)?.status ?? "")
406
+ || store.getTask(request.taskId)?.executionGate.state !== "enabled"))) {
407
+ throw new Error("Native input requires the selected current controlled Codex Operator/Leader.");
408
+ }
409
+ };
383
410
  const web = createControllerWeb(store, {
411
+ previews: () => createTaskPreviews(store, home),
412
+ skills: createWebSkills(store),
413
+ settings: createWebSettings(store, {
414
+ environment: options.environment ?? process.env, catalogs,
415
+ refreshConfiguration: () => {
416
+ if (!runningRuntime)
417
+ throw new Error("Controller runtime unavailable.");
418
+ runningRuntime.reloadReconciliationInterval();
419
+ }
420
+ }),
384
421
  surface: webSurface,
422
+ conversation: createWebConversationSurface(store, webSurface, (owner, id) => planner.planNativeControl(owner, id), undefined, (owner) => inspectAgentHost({ home, ...owner }), {
423
+ hasWriter: owner => tmux.hasWritableClient(owner.scope === "task" ? owner.taskId : "operator", owner.scope === "task" ? owner.roleName : undefined),
424
+ respond: async (owner, id, requestId, nativeTurnId, result) => {
425
+ const receipt = await sendAgentHostNativeControl({
426
+ home, ...owner, control: { protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "native-respond",
427
+ nativeSessionId: id, requestId, nativeTurnId, result }
428
+ });
429
+ if (receipt.outcome !== "accepted")
430
+ throw new Error(receipt.snapshot.detail ?? "Native answer was not confirmed.");
431
+ return { outcome: receipt.outcome };
432
+ },
433
+ setModel: async (owner, id, model) => {
434
+ const result = await sendAgentHostNativeControl({
435
+ home, ...owner, control: { protocol: AGENT_HOST_CONTROL_PROTOCOL,
436
+ type: "native-model", nativeSessionId: id, model }
437
+ });
438
+ if (result.outcome !== "accepted") {
439
+ const detail = result.snapshot.detail ?? "Model adoption is unconfirmed.";
440
+ if (result.failure?.inputDisposition === "not-accepted")
441
+ throw new WebRequestRejected(detail);
442
+ throw new Error(detail);
443
+ }
444
+ return { outcome: result.outcome, runConfiguration: result.snapshot.runConfiguration };
445
+ }
446
+ }, catalogs),
385
447
  answerInput: async ({ taskId, inputId, answer }) => webSurface.answer(taskId, inputId, answer),
386
448
  panels: {
387
449
  list: (taskId) => surfaces.listPanels(kernel.capabilities.authenticateWebQuery(taskId)),
@@ -390,6 +452,26 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
390
452
  terminal: new TmuxWebTerminalService({
391
453
  yuiHome: home, tmuxBin: resolveTmuxBin(store.getConfig().tmuxBin), tmux,
392
454
  prepareGlobalRole: (roleName) => webWorkflow.prepareGlobalRoleEnter(roleName),
455
+ prepareNative: async (request) => {
456
+ if (!request.nativeSessionId)
457
+ return false;
458
+ const sessions = request.scope === "task" ? store.getTaskRoleSessionSet(request.taskId, request.roleName)
459
+ : store.getGlobalRoleSessionSet(request.roleName);
460
+ const selected = sessions?.sessions[sessions.activeAgentId];
461
+ if (request.scope === "task" && (request.roleName !== "leader" || selected?.adapterId !== "codex"))
462
+ return false;
463
+ validateNativeWrite(request);
464
+ // Preserve ordinary global native access on other adapters and the
465
+ // existing uncontrolled Codex TUI. No lifecycle operation on a switch.
466
+ if (selected?.adapterId !== "codex" || !sessions?.providerBinding)
467
+ return request.scope === "global";
468
+ const result = await sendAgentHostNativeControl({ home, ...request,
469
+ control: { protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "native-open", nativeSessionId: request.nativeSessionId } });
470
+ if (result.outcome !== "accepted" || !result.snapshot.nativeTui)
471
+ throw new Error("Native TUI could not be opened.");
472
+ return true;
473
+ },
474
+ validateWrite: validateNativeWrite,
393
475
  environment: options.environment ?? process.env, onError: options.onError
394
476
  })
395
477
  });
@@ -612,6 +694,12 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
612
694
  throw applicationError("INVALID_PARAMS", error instanceof Error ? error.message : String(error));
613
695
  }
614
696
  }
697
+ if (method === "runtime.native-steer-prepare") {
698
+ const value = providerTurnControlParams(params);
699
+ return prepareNativeHostSteer(store, { ...value,
700
+ nativeTurnId: requiredParam(params.nativeTurnId),
701
+ boundedText: requiredParam(value.boundedText) });
702
+ }
615
703
  if (method === "runtime.provider-turn-begin") {
616
704
  const value = providerTurnControlParams(params);
617
705
  try {
@@ -927,6 +1015,7 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
927
1015
  function resolveRuntimeDesiredEffective(store, request, role) {
928
1016
  if (request.scope === "global") {
929
1017
  return resolveEffectiveLaunch({
1018
+ store,
930
1019
  role: role,
931
1020
  purpose: "execution"
932
1021
  });
@@ -940,6 +1029,7 @@ function resolveRuntimeDesiredEffective(store, request, role) {
940
1029
  ?? store.getTaskWorkspace(request.taskId)
941
1030
  ?? undefined;
942
1031
  return resolveEffectiveLaunch({
1032
+ store,
943
1033
  role: taskRole,
944
1034
  purpose: store.getTask(request.taskId)?.status === "draft" && request.roleName === "leader"
945
1035
  ? "planning" : "execution",
@@ -1018,7 +1108,7 @@ function assertRuntimeLaunchRequestCurrent(store, request) {
1018
1108
  }
1019
1109
  function currentDesiredEffective(store, request, role) {
1020
1110
  if (request.owner.scope === "global") {
1021
- return resolveEffectiveLaunch({ role: role, purpose: "execution" });
1111
+ return resolveEffectiveLaunch({ store, role: role, purpose: "execution" });
1022
1112
  }
1023
1113
  const item = store.listWorkItems(request.owner.taskId).find((candidate) => (candidate.assignee === request.owner.roleName
1024
1114
  && !["accepted", "retired"].includes(candidate.status))) ?? null;
@@ -1028,6 +1118,7 @@ function currentDesiredEffective(store, request, role) {
1028
1118
  ?? store.getTaskWorkspace(request.owner.taskId)
1029
1119
  ?? undefined;
1030
1120
  return resolveEffectiveLaunch({
1121
+ store,
1031
1122
  role: role,
1032
1123
  purpose: store.getTask(request.owner.taskId)?.status === "draft" && request.owner.roleName === "leader"
1033
1124
  ? "planning" : "execution",
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { closeSync, constants, existsSync, fchmodSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
- import { createRuntimeObservation, isRuntimeTokenEvidence } from "../runtime/runtimeObservation.js";
5
+ import { createRuntimeObservation, isRuntimeUsageEvidence } from "../runtime/runtimeObservation.js";
6
6
  import { boundedRunFailureDiagnostic, transportAgentResult } from "../domain/agentResultTransport.js";
7
7
  import { readAgentHostObservationSource } from "../runtime/agentHostProtocol.js";
8
8
  export const MAX_RUNTIME_EVENT_FILE_BYTES = 16 * 1024 * 1024;
@@ -174,7 +174,7 @@ export class FileRuntimeEventInbox {
174
174
  publish(event) {
175
175
  this.hooks.afterAdmission?.();
176
176
  if (event.type === "runtime-observation"
177
- && isRuntimeTokenEvidence(event.observation)) {
177
+ && isRuntimeUsageEvidence(event.observation)) {
178
178
  const existing = this.list().find((candidate) => (candidate.type === "runtime-observation"
179
179
  && candidate.taskId === event.taskId
180
180
  && candidate.observation.eventId === event.observation.eventId));
@@ -1,4 +1,4 @@
1
- import { isRuntimeTokenEvidence } from "../runtime/runtimeObservation.js";
1
+ import { isRuntimeUsageEvidence } from "../runtime/runtimeObservation.js";
2
2
  const DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN = 64;
3
3
  /** Folds immutable Hook facts in one bounded transaction before acknowledging them. */
4
4
  export class FileRuntimeEventProcessor {
@@ -299,7 +299,7 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
299
299
  }
300
300
  const representedByIndex = new Map();
301
301
  for (const indices of indicesByStream.values()) {
302
- const hasUsage = isRuntimeTokenEvidence(segment[indices[0]].observation);
302
+ const hasUsage = isRuntimeUsageEvidence(segment[indices[0]].observation);
303
303
  // Usage facts and incomplete boundaries are authoritative history for
304
304
  // read-only Session token projection, so ingress never coalesces them.
305
305
  const retainedPositions = hasUsage ? indices.map((_, position) => position) : [];
@@ -403,7 +403,7 @@ function progressStreamKey(event) {
403
403
  fence.nativeTurnId ?? null,
404
404
  event.host ?? null,
405
405
  payload.activity,
406
- isRuntimeTokenEvidence(event.observation) ? "usage" : "signal"
406
+ isRuntimeUsageEvidence(event.observation) ? "usage" : "signal"
407
407
  ]);
408
408
  }
409
409
  function isRuntimeActivityEvent(event) {
@@ -2,6 +2,8 @@ import { accessSync, constants, existsSync, lstatSync, realpathSync } from "node
2
2
  import { isAbsolute, join } from "node:path";
3
3
  import Database from "better-sqlite3";
4
4
  import { inspectPty } from "./ptyProbe.js";
5
+ import { gitVersionSupport } from "../repository/gitCompatibility.js";
6
+ import { resolveExecutable } from "../external/pinnedCommandRunner.js";
5
7
  import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
6
8
  import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
7
9
  import { resolveTmuxBin } from "../config/yuiConfig.js";
@@ -684,6 +686,14 @@ function checkReviewerDispatch(_policy, role, binding, agent, launch, command, c
684
686
  function checkExecutable(name, command, args, executor) {
685
687
  try {
686
688
  const output = firstLine(executor.run(command, args));
689
+ if (name === "git") {
690
+ const support = gitVersionSupport(output);
691
+ return {
692
+ name, status: support.supported ? "ok" : "invalid",
693
+ detail: `${resolveExecutable(command, process.env.PATH) ?? command}: ${support.detail}`
694
+ + (support.supported ? "" : " Select a supported Git in PATH and rerun doctor; no upgrade was performed.")
695
+ };
696
+ }
687
697
  return {
688
698
  name,
689
699
  status: "ok",
@@ -65,6 +65,7 @@ export function dispatchReviewSynthesis(store, taskId, reviewRoundId, sourceRunI
65
65
  throw new Error(`Review main workspace is missing: ${taskId}/${round.id}.`);
66
66
  }
67
67
  const effective = resolveEffectiveLaunch({
68
+ store,
68
69
  role,
69
70
  purpose: "review",
70
71
  workspace,
@@ -78,6 +78,7 @@ export function dispatchWorkItemSynthesis(store, taskId, workItemId, sourceRunId
78
78
  throw new Error(`WorkItem main workspace does not match its approved scope: ${item.id}.`);
79
79
  }
80
80
  const effective = resolveEffectiveLaunch({
81
+ store,
81
82
  role,
82
83
  purpose: "execution",
83
84
  workspace,
@@ -196,13 +196,15 @@ function exactTrust(directory, configured) {
196
196
  return undefined;
197
197
  }
198
198
  function resolveRepositoryRootForTrust(workspace) {
199
- const result = spawnSync("git", ["-C", workspace, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", timeout: 1_000, windowsHide: true });
199
+ const result = spawnSync("git", ["-C", workspace, "rev-parse", "--git-common-dir"], { encoding: "utf8", timeout: 1_000, windowsHide: true });
200
200
  if (result.status !== 0 || result.error !== undefined)
201
201
  return undefined;
202
- const commonDirectory = result.stdout.trim();
203
- if (!isAbsolute(commonDirectory) || commonDirectory.includes("\0"))
202
+ if (!result.stdout.endsWith("\n"))
204
203
  return undefined;
205
- const normalized = resolve(commonDirectory);
204
+ const commonDirectory = result.stdout.slice(0, -1);
205
+ if (commonDirectory.length === 0 || commonDirectory.includes("\0"))
206
+ return undefined;
207
+ const normalized = resolve(workspace, commonDirectory);
206
208
  return basename(normalized) === ".git" ? dirname(normalized) : undefined;
207
209
  }
208
210
  function inspectProjectDiscovery(contents) {
@@ -2,7 +2,8 @@ import { isDeepStrictEqual } from "node:util";
2
2
  import { validateExecutionEnvironmentSnapshot } from "../resources/projectResource.js";
3
3
  import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
4
4
  import { resolveAgentAdapter } from "./agentAdapter.js";
5
- import { roleSessionKind } from "../context/roleSessionContext.js";
5
+ import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
6
+ import { validateSkillPackageRef } from "../context/skillPackage.js";
6
7
  import { isAgentAdapterId } from "../agent/adapterCatalog.js";
7
8
  import { adapterIdForExecutionComponent, isAgentExecutionComponentId, resolveAgentExecutionComponent } from "../agent/executionComponents.js";
8
9
  import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, sessionManifestCompatibilityDigest } from "../context/sessionProtocolIdentity.js";
@@ -15,6 +16,15 @@ export function resolveEffectiveLaunch(input) {
15
16
  const workspace = snapshotWorkspace(input.role.workspace, input.workspace);
16
17
  const writeProjectIds = effectiveWriteProjects(input, workspace);
17
18
  const config = resolveAgentAdapter(binding.adapterId).canonicalizeConfig(clone(binding.config));
19
+ const home = input.store?.rootDirectory() ?? input.yuiHome;
20
+ const sessions = "taskId" in input.role
21
+ ? input.store?.getTaskRoleSessionSet(input.role.taskId, input.role.name)
22
+ : input.store?.getGlobalRoleSessionSet(input.role.name);
23
+ const existing = sessions?.sessions[binding.agentId];
24
+ const continuing = existing?.status === "active";
25
+ const skillPackages = continuing ? existing.effective.skillPackages
26
+ : home === undefined ? undefined
27
+ : compileRoleSessionContext(home, input.role, "taskId" in input.role ? { scope: "task", taskId: input.role.taskId } : { scope: "global" }, { purpose: input.purpose }).skills.map(skill => skill.package);
18
28
  return snapshotFromConfig({
19
29
  executionAuthority: input.executionAuthority ?? (input.purpose === "planning" ? "planning" : "delivery"),
20
30
  sourceDesiredRevision: input.role.launchRevision,
@@ -28,6 +38,7 @@ export function resolveEffectiveLaunch(input) {
28
38
  ? { executionEnvironment: input.role.executionEnvironment }
29
39
  : {}),
30
40
  context: snapshotContext(input.role),
41
+ ...(skillPackages === undefined ? {} : { skillPackages }),
31
42
  contextProtocolVersion: SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION,
32
43
  sessionManifestCompatibilityDigest: sessionManifestCompatibilityDigest(input.role.name, roleSessionKind(input.role, "taskId" in input.role
33
44
  ? { scope: "task", taskId: input.role.taskId }
@@ -216,6 +227,17 @@ export function validateEffectiveLaunchSnapshot(snapshot) {
216
227
  throw new Error("Effective launch Session Manifest compatibility digest is invalid.");
217
228
  }
218
229
  validateWorkspace(snapshot.workspace);
230
+ if (snapshot.skillPackages !== undefined) {
231
+ if (!Array.isArray(snapshot.skillPackages))
232
+ throw new Error("Effective Skill packages must be an array.");
233
+ const ids = new Set();
234
+ for (const ref of snapshot.skillPackages) {
235
+ validateSkillPackageRef(ref);
236
+ if (ids.has(ref.id))
237
+ throw new Error("Effective Skill packages must be unique.");
238
+ ids.add(ref.id);
239
+ }
240
+ }
219
241
  if (snapshot.executionEnvironment !== undefined) {
220
242
  validateExecutionEnvironmentSnapshot(snapshot.executionEnvironment);
221
243
  if (snapshot.executionEnvironment.access === "read") {
@@ -299,6 +321,7 @@ function snapshotFromConfig(input) {
299
321
  ? {}
300
322
  : { executionEnvironment: clone(input.executionEnvironment) }),
301
323
  context: cloneContext(input.context),
324
+ ...(input.skillPackages === undefined ? {} : { skillPackages: clone(input.skillPackages) }),
302
325
  contextProtocolVersion: input.contextProtocolVersion,
303
326
  sessionManifestCompatibilityDigest: input.sessionManifestCompatibilityDigest,
304
327
  ...review
@@ -70,11 +70,13 @@ export class FileRoleLaunchPlanner {
70
70
  }
71
71
  /** Recovery addresses the recorded Agent/Session, independent of delivery
72
72
  * admission, a missing worktree, or the old Endpoint code generation. */
73
- planNativeControl(owner) {
73
+ planNativeControl(owner, nativeSessionId) {
74
74
  const set = owner.scope === "task"
75
75
  ? this.store.getTaskRoleSessionSet(owner.taskId, owner.roleName)
76
76
  : this.store.getGlobalRoleSessionSet(owner.roleName);
77
- const session = roleSessionControlTarget(set);
77
+ const session = nativeSessionId === undefined ? roleSessionControlTarget(set)
78
+ : [...Object.values(set?.sessions ?? {}), ...Object.values(set?.history ?? {})]
79
+ .find(entry => entry.nativeSessionId === nativeSessionId);
78
80
  if (session?.adapterId !== "codex")
79
81
  throw new Error("Native metadata control requires a recorded Codex Session.");
80
82
  const configured = this.store.getConfiguredAgent(session.agentId);
@@ -229,7 +231,7 @@ export class FileRoleLaunchPlanner {
229
231
  throw new Error("Global Role has an unmanaged live Session. Stop that exact Session before opening controlled input; it will not be silently replaced.");
230
232
  }
231
233
  const resolvedEffective = activeLiveRoleAgentSession(sessionSet)?.effective
232
- ?? resolveEffectiveLaunch({ role, purpose: "execution" });
234
+ ?? resolveEffectiveLaunch({ store: this.store, role, purpose: "execution" });
233
235
  if (input.effective !== undefined
234
236
  && !isDeepStrictEqual(resolvedEffective, input.effective)) {
235
237
  throw new Error(`Global Role launch effective snapshot changed: ${role.name}.`);
@@ -335,7 +337,12 @@ export class FileRoleLaunchPlanner {
335
337
  Object.assign(launchEnvironment, runtimeIsolation === undefined
336
338
  ? {}
337
339
  : taskRuntimeIsolationEnvironment(runtimeIsolation));
338
- const baseSessionContext = compileRoleSessionContext(this.home, launchRole, owner, sessionPolicy);
340
+ const skillPackages = input.mode === "resume"
341
+ ? existingSession?.effective.skillPackages : effective.skillPackages;
342
+ if (skillPackages === undefined) {
343
+ throw new Error("This launch has no frozen Skill package evidence (pre-1.5 or externally recorded Session). Create a new Session from current Role configuration; historical Skill versions cannot be reconstructed.");
344
+ }
345
+ const baseSessionContext = compileRoleSessionContext(this.home, launchRole, owner, { ...sessionPolicy, skillPackages });
339
346
  const bootstrap = materializeSessionBootstrap({
340
347
  yuiHome: this.home,
341
348
  role: launchRole,
@@ -686,7 +693,7 @@ function resolveTaskRoleEffectiveLaunch(store, role, purpose = "execution") {
686
693
  if (purpose === "planning") {
687
694
  // Planning has no WorkItem assignment and no managed workspace, so it
688
695
  // resolves the Role's own configured workspace with no writable Project.
689
- return resolveEffectiveLaunch({ role, purpose: "planning" });
696
+ return resolveEffectiveLaunch({ store, role, purpose: "planning" });
690
697
  }
691
698
  const item = store.listWorkItems(role.taskId).find((candidate) => (candidate.assignee === role.name
692
699
  && !["accepted", "retired"].includes(candidate.status))) ?? null;
@@ -696,6 +703,7 @@ function resolveTaskRoleEffectiveLaunch(store, role, purpose = "execution") {
696
703
  ?? store.getTaskWorkspace(role.taskId)
697
704
  ?? undefined;
698
705
  return resolveEffectiveLaunch({
706
+ store,
699
707
  role,
700
708
  purpose: "execution",
701
709
  ...(workspace === undefined ? {} : { workspace }),