@zq-silk/yui 0.15.11 → 0.16.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 (280) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/ARCHITECTURE.zh-CN.md +8 -4
  3. package/README.md +13 -5
  4. package/dist/agentRun/agentRun.js +3 -0
  5. package/dist/artifacts/artifactCommitLock.js +16 -55
  6. package/dist/artifacts/taskArtifactRepository.js +3 -3
  7. package/dist/cli/agentConfigurationPicker.js +1 -1
  8. package/dist/cli/commandCatalog.js +92 -48
  9. package/dist/cli/completion.js +18 -18
  10. package/dist/cli/completionWizard.js +1 -1
  11. package/dist/cli/dynamicCompletion.js +1 -1
  12. package/dist/cli/interactionPolicy.js +11 -3
  13. package/dist/cli/invocationAuthority.js +64 -0
  14. package/dist/cli/managedDiagnostics.js +2 -2
  15. package/dist/cli/parseRepeatable.js +35 -0
  16. package/dist/cli/updatePorts.js +26 -5
  17. package/dist/cli.js +1303 -1235
  18. package/dist/commands/agentCommands.js +4 -4
  19. package/dist/commands/capabilityCommands.js +1 -1
  20. package/dist/commands/configCommands.js +6 -57
  21. package/dist/commands/configOverview.js +2 -2
  22. package/dist/commands/durableJobCommands.js +12 -6
  23. package/dist/commands/executionAuditCommands.js +10 -0
  24. package/dist/commands/globalRoleCommands.js +78 -29
  25. package/dist/commands/grantCommands.js +0 -3
  26. package/dist/commands/jobCommands.js +1 -4
  27. package/dist/commands/operatorCommands.js +3 -3
  28. package/dist/commands/projectCommands.js +45 -25
  29. package/dist/commands/resourcesCommands.js +10 -40
  30. package/dist/commands/roleConfiguration.js +2 -6
  31. package/dist/commands/taskActivationCommands.js +4 -8
  32. package/dist/commands/taskAgentCapabilities.js +9 -0
  33. package/dist/commands/taskCommandSupport.js +155 -0
  34. package/dist/commands/taskCommandTypes.js +1 -0
  35. package/dist/commands/taskCommands.js +355 -732
  36. package/dist/commands/taskCompletionGate.js +1 -1
  37. package/dist/commands/taskExecutionCommands.js +1 -1
  38. package/dist/commands/taskFactCommands.js +325 -0
  39. package/dist/commands/taskInputCommands.js +12 -42
  40. package/dist/commands/taskIntegrationCommands.js +20 -58
  41. package/dist/commands/taskNextActionCommand.js +7 -6
  42. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  43. package/dist/commands/taskPublicationCommands.js +12 -3
  44. package/dist/commands/taskPublicationVerifyCommand.js +24 -40
  45. package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
  46. package/dist/commands/taskUpstreamCommands.js +19 -8
  47. package/dist/commands/workflowCommands.js +6 -1
  48. package/dist/completion/completionInstaller.js +26 -26
  49. package/dist/completion/completionState.js +26 -26
  50. package/dist/completion/fileCompletionManager.js +6 -11
  51. package/dist/config/configCatalog.js +0 -2
  52. package/dist/config/timeZone.js +14 -0
  53. package/dist/config/yuiConfig.js +0 -29
  54. package/dist/context/runContextPack.js +5 -3
  55. package/dist/context/taskCatalog.js +185 -0
  56. package/dist/context/taskContext.js +152 -151
  57. package/dist/context/wakeRunReferences.js +11 -0
  58. package/dist/controller/agentCapabilities.js +58 -0
  59. package/dist/controller/agentRuntimeObserver.js +6 -11
  60. package/dist/controller/clientRuntime.js +22 -14
  61. package/dist/controller/controller.js +20 -24
  62. package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
  63. package/dist/controller/globalInputDelivery.js +13 -0
  64. package/dist/controller/jobClient.js +4 -4
  65. package/dist/controller/jobControl.js +60 -25
  66. package/dist/controller/jobSupervisor.js +4 -4
  67. package/dist/controller/providerRetryAdmission.js +100 -0
  68. package/dist/controller/providerRetryDelivery.js +218 -0
  69. package/dist/controller/runtime.js +85 -43
  70. package/dist/controller/runtimeEventProcessor.js +0 -5
  71. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  72. package/dist/controller/taskAgentError.js +35 -0
  73. package/dist/coordination/workMailboxQueue.js +2 -24
  74. package/dist/core/controllerClient.js +2 -2
  75. package/dist/core/fileLockOwner.js +74 -0
  76. package/dist/decision/decision.js +17 -0
  77. package/dist/doctor/doctor.js +18 -9
  78. package/dist/event/taskEvent.js +12 -0
  79. package/dist/execution/workItemExecutionProjection.js +2 -2
  80. package/dist/executor/agentCapabilityConfig.js +43 -0
  81. package/dist/executor/agentConfigurationCatalog.js +49 -11
  82. package/dist/executor/agentExecutor.js +5 -8
  83. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  84. package/dist/executor/taskAgentCapabilities.js +64 -0
  85. package/dist/integration/deliveryObligation.js +1 -69
  86. package/dist/integration/gitIntegrationService.js +79 -90
  87. package/dist/integration/integrationAttempt.js +3 -14
  88. package/dist/integration/integrationSourceApplication.js +4 -12
  89. package/dist/integration/manifestTags.js +2 -2
  90. package/dist/job/durableJob.js +4 -25
  91. package/dist/job/jobAssignmentScope.js +22 -0
  92. package/dist/job/jobOperation.js +16 -0
  93. package/dist/job/jobRunner.js +2 -1
  94. package/dist/job/stepDirectory.js +15 -0
  95. package/dist/kernel/builtinCapabilities.js +15 -11
  96. package/dist/kernel/kernelPorts.js +1 -17
  97. package/dist/lifecycle/exactRunTerminalization.js +5 -10
  98. package/dist/message/globalProviderRetry.js +15 -0
  99. package/dist/message/inputControlResolution.js +3 -4
  100. package/dist/message/message.js +17 -20
  101. package/dist/message/messageContinuation.js +1 -1
  102. package/dist/milestone/milestone.js +11 -0
  103. package/dist/observability/executionAudit.js +19 -0
  104. package/dist/observability/orchestrationMetrics.js +24 -1
  105. package/dist/observability/runtimeIdentity.js +1 -17
  106. package/dist/output/agentConfigurationPresentation.js +2 -0
  107. package/dist/output/timePresentation.js +1 -14
  108. package/dist/plugins/pluginService.js +12 -4
  109. package/dist/release/releaseWorkflowPorts.js +45 -96
  110. package/dist/release/runtimeRelease.js +9 -15
  111. package/dist/repository/gitWorkspace.js +371 -105
  112. package/dist/repository/projectMaintenanceLock.js +98 -82
  113. package/dist/repository/taskBaseFreshness.js +1 -1
  114. package/dist/repository/taskWorkspaceCoordinator.js +82 -144
  115. package/dist/repository/taskWorkspacePreparer.js +113 -47
  116. package/dist/repository/workspaceCleanupInspection.js +187 -0
  117. package/dist/resources/autoResourceGc.js +4 -77
  118. package/dist/resources/liveReferences.js +7 -54
  119. package/dist/resources/resourceDiscovery.js +5 -118
  120. package/dist/resources/resourceGc.js +233 -181
  121. package/dist/resources/resourceRegistrar.js +5 -4
  122. package/dist/resources/resourceRegistry.js +3 -37
  123. package/dist/resources/resourceRegistryStore.js +13 -36
  124. package/dist/resources/sqliteResourceRegistry.js +43 -26
  125. package/dist/review/deltaRecheck.js +1 -1
  126. package/dist/review/reviewAcceptance.js +1 -1
  127. package/dist/review/reviewDecision.js +1 -1
  128. package/dist/review/reviewRound.js +8 -7
  129. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  130. package/dist/runtime/acpProtocol.js +4 -51
  131. package/dist/runtime/acpSession.js +10 -75
  132. package/dist/runtime/acpSessionConfiguration.js +1 -8
  133. package/dist/runtime/agentEndpoint.js +3 -2
  134. package/dist/runtime/agentError.js +5 -3
  135. package/dist/runtime/agentFailureContext.js +43 -0
  136. package/dist/runtime/agentHost.js +42 -25
  137. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  138. package/dist/runtime/codexAppServerRuntime.js +34 -3
  139. package/dist/runtime/codexInteractiveHost.js +2 -2
  140. package/dist/runtime/index.js +0 -1
  141. package/dist/runtime/managedCaller.js +21 -1
  142. package/dist/runtime/providerControl.js +5 -1
  143. package/dist/runtime/providerErrors.js +38 -0
  144. package/dist/runtime/providerRetry.js +198 -0
  145. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  146. package/dist/runtime/runtimeObservation.js +0 -5
  147. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  148. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  149. package/dist/runtime/sessionTokenMetrics.js +15 -5
  150. package/dist/runtime/structuredProviderHost.js +10 -43
  151. package/dist/runtime/taskUsageMetrics.js +275 -0
  152. package/dist/runtime/tmuxAdapters.js +6 -8
  153. package/dist/scheduler/activeRoleRunDelivery.js +19 -7
  154. package/dist/scheduler/leaderWakeupProcessor.js +24 -6
  155. package/dist/scheduler/operatorEvent.js +12 -20
  156. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  157. package/dist/scheduler/ports.js +5 -8
  158. package/dist/scheduler/roleRunLiveness.js +4 -4
  159. package/dist/scheduler/roleRunStall.js +16 -23
  160. package/dist/scheduler/taskExecutionProjection.js +49 -36
  161. package/dist/scheduler/taskObservabilityProjection.js +6 -45
  162. package/dist/scheduler/taskWake.js +5 -10
  163. package/dist/scheduler/wakeReason.js +2 -0
  164. package/dist/scheduler/wakeupQueue.js +2 -4
  165. package/dist/setup/setupCommand.js +1 -1
  166. package/dist/storage/contextRecords.js +106 -0
  167. package/dist/storage/currentTaskStore.js +1 -1
  168. package/dist/storage/homeLayout.js +13 -17
  169. package/dist/storage/migrations/agentFailureContext.js +22 -0
  170. package/dist/storage/migrations/currentInputContract.js +86 -0
  171. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  172. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  173. package/dist/storage/migrations/integrationContinuation.js +5 -4
  174. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  175. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  176. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  177. package/dist/storage/migrations/verificationPolicy.js +74 -0
  178. package/dist/storage/migrations/workItemHistory.js +46 -0
  179. package/dist/storage/persistenceWorker.js +12 -4
  180. package/dist/storage/recordValidation.js +87 -0
  181. package/dist/storage/sqliteSchema.js +173 -1
  182. package/dist/storage/sqliteStore.js +172 -161
  183. package/dist/storage/storageSchema.js +2 -14
  184. package/dist/storage/storageVersions.js +1 -1
  185. package/dist/storage/storeRpc.js +16 -7
  186. package/dist/storage/taskCatalog.js +123 -0
  187. package/dist/storage/taskStore.js +5 -7
  188. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  189. package/dist/task/archiveDiagnostics.js +1 -0
  190. package/dist/task/archivePreflight.js +124 -0
  191. package/dist/task/completionReadiness.js +5 -24
  192. package/dist/task/draftPlan.js +0 -53
  193. package/dist/task/nextAction.js +7 -13
  194. package/dist/task/publicationAdoption.js +56 -0
  195. package/dist/task/publicationReference.js +10 -0
  196. package/dist/task/remoteDelivery.js +31 -16
  197. package/dist/task/remoteDeliveryService.js +89 -0
  198. package/dist/task/taskActivation.js +2 -25
  199. package/dist/task/taskActivationService.js +0 -2
  200. package/dist/task/taskRecordReference.js +0 -1
  201. package/dist/task/taskSubmission.js +3 -14
  202. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  203. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  204. package/dist/telemetry/telemetryWiring.js +4 -3
  205. package/dist/tmux/tmuxManager.js +29 -20
  206. package/dist/verification/gateArtifact.js +15 -24
  207. package/dist/verification/gateArtifactStore.js +14 -7
  208. package/dist/verification/verificationGateService.js +29 -88
  209. package/dist/verification/verificationPlan.js +27 -72
  210. package/dist/web/assets/client/app.js +92 -17
  211. package/dist/web/assets/client/components.js +55 -13
  212. package/dist/web/assets/client/i18n.js +72 -4
  213. package/dist/web/assets/client/taskSurface.js +6 -5
  214. package/dist/web/assets/client/view.js +35 -7
  215. package/dist/web/assets/shell.js +6 -0
  216. package/dist/web/assets/styles/layout.js +7 -0
  217. package/dist/web/webServer.js +12 -3
  218. package/dist/web/webSnapshot.js +14 -85
  219. package/dist/web/webTaskSurface.js +17 -46
  220. package/dist/workItem/workItem.js +1 -12
  221. package/dist/workspace/cleanupInspection.js +63 -0
  222. package/dist/workspace/workItemChangeSetManager.js +110 -50
  223. package/docs/agent-result-consumption.md +4 -0
  224. package/docs/agent-result-consumption.zh-CN.md +3 -0
  225. package/docs/agent-runtime-drivers.md +7 -0
  226. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  227. package/docs/architecture/README.md +2 -0
  228. package/docs/architecture/README.zh-CN.md +3 -1
  229. package/docs/architecture/capabilities-and-resources.md +42 -5
  230. package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
  231. package/docs/managed-turn-and-session-runtime.md +121 -1
  232. package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
  233. package/docs/observability/README.md +62 -0
  234. package/docs/observability/README.zh-CN.md +47 -0
  235. package/docs/plugin-sdk.md +4 -2
  236. package/docs/project-refresh.md +77 -0
  237. package/docs/project-refresh.zh-CN.md +59 -0
  238. package/docs/provider-retry.md +70 -0
  239. package/docs/provider-runtime.md +3 -1
  240. package/docs/provider-runtime.zh-CN.md +4 -2
  241. package/docs/release-workflow.md +190 -7
  242. package/docs/release-workflow.zh-CN.md +139 -5
  243. package/docs/roles-and-configuration.md +29 -0
  244. package/docs/roles-and-configuration.zh-CN.md +21 -0
  245. package/docs/sqlite-control-plane-design.md +16 -3
  246. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  247. package/docs/task-dag-semantics.md +3 -3
  248. package/docs/task-dag-semantics.zh-CN.md +2 -2
  249. package/docs/task-delivery.md +223 -17
  250. package/docs/task-delivery.zh-CN.md +171 -14
  251. package/docs/task-discovery.md +101 -0
  252. package/docs/task-discovery.zh-CN.md +85 -0
  253. package/docs/testing/verification-levels.md +161 -9
  254. package/docs/testing/verification-levels.zh-CN.md +116 -9
  255. package/i18n/README.zh-CN.md +13 -7
  256. package/package.json +1 -1
  257. package/skills/yui-leader/SKILL.md +5 -0
  258. package/skills/yui-leader/references/execution.md +3 -2
  259. package/skills/yui-leader/references/integration.md +9 -5
  260. package/skills/yui-leader/references/planning.md +9 -3
  261. package/skills/yui-operator/SKILL.md +24 -8
  262. package/skills/yui-reviewer/SKILL.md +4 -0
  263. package/skills/yui-runtime/SKILL.md +17 -2
  264. package/skills/yui-runtime/references/publication.md +26 -4
  265. package/skills/yui-runtime/references/recovery.md +71 -0
  266. package/dist/agent/agentRegistry.js +0 -10
  267. package/dist/agentRun/runIdentity.js +0 -22
  268. package/dist/commands/deliveryGuardPreflight.js +0 -30
  269. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  270. package/dist/commands/taskOverviewCommand.js +0 -376
  271. package/dist/completion/completionWizard.js +0 -125
  272. package/dist/context/dispatchContext.js +0 -110
  273. package/dist/context/wakeNotification.js +0 -144
  274. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  275. package/dist/integration/integrationQueueEntry.js +0 -191
  276. package/dist/integration/integrationQueueService.js +0 -810
  277. package/dist/release/fakeReleasePorts.js +0 -55
  278. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  279. package/dist/task/deliveryGuard.js +0 -226
  280. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -66,12 +66,17 @@ export function renderFilters(container, state, t, onFilter) {
66
66
  if (!btn) return;
67
67
  btn.classList.toggle("is-active", state.filter === status);
68
68
  const label = btn.querySelector(".filter-label");
69
- if (label) label.textContent = status === "all" ? t("filter.unarchived") : translatedStatus(t, "status", status);
69
+ if (label) label.textContent = status === "all"
70
+ ? t(state.catalogAll ? "catalog.all" : "filter.unarchived") : translatedStatus(t, "status", status);
70
71
  const count = status === "all"
71
- ? (counts.total === undefined ? undefined : counts.total - (counts.archived || 0))
72
+ ? (counts.total === undefined ? undefined : counts.total - (state.catalogAll ? 0 : counts.archived || 0))
72
73
  : counts[status];
73
74
  const badge = btn.querySelector(".filter-count");
74
75
  if (badge) {
76
+ if (status === "archived" && state.catalogScope && state.catalogScope.archived === "excluded") {
77
+ badge.hidden = true;
78
+ return;
79
+ }
75
80
  if (count !== undefined && count !== null) {
76
81
  badge.textContent = String(count);
77
82
  badge.hidden = false;
@@ -83,7 +88,7 @@ export function renderFilters(container, state, t, onFilter) {
83
88
  }
84
89
 
85
90
  function taskGroupOf(task, attentionIds) {
86
- if (attentionIds.has(task.id)) return "attention";
91
+ if (attentionIds.has(task.id) || (task.attention && (task.attention.openInputs > 0 || task.attention.unknownOperations > 0))) return "attention";
87
92
  if (task.status === "active") return "active";
88
93
  if (task.status === "draft") return "draft";
89
94
  if (task.status === "cancelled") return "cancelled";
@@ -107,9 +112,9 @@ export function renderTasks(container, state, t, locale, onSelect) {
107
112
  const attentionIds = new Set((state.attention || []).map(function (item) { return item.taskId; }));
108
113
  const groups = { attention: [], active: [], draft: [], finished: [], cancelled: [], archived: [] };
109
114
  (state.tasks || []).forEach(function (task) {
110
- if (state.filter === "all" && task.status === "archived") return;
115
+ if (state.filter === "all" && !state.catalogAll && task.status === "archived") return;
111
116
  if (state.filter !== "all" && task.status !== state.filter) return;
112
- if (!taskMatchesQuery(task, query, locale)) return;
117
+ if (!state.catalogScope && !taskMatchesQuery(task, query, locale)) return;
113
118
  groups[taskGroupOf(task, attentionIds)].push(task);
114
119
  });
115
120
 
@@ -166,15 +171,33 @@ export function renderOverview(detail, state, t, locale, onSelect) {
166
171
  );
167
172
  wrap.append(rail);
168
173
 
174
+ if (state.catalogAttention) {
175
+ const catalog = node("section", "overview-block");
176
+ catalog.append(sectionHead(t("catalog.attention")));
177
+ catalog.append(node("p", "", t("catalog.signalsHelp")));
178
+ const groups = node("div", "overview-list");
179
+ Object.entries(state.catalogAttention).forEach(function (entry) {
180
+ const kind = entry[0], value = entry[1];
181
+ const button = node("button", "overview-row",
182
+ t("catalog." + kind) + " · " + value.count + " / " + value.taskCount + " " + t("catalog.tasks"));
183
+ button.type = "button";
184
+ button.dataset.catalogAttention = kind;
185
+ button.disabled = value.taskCount === 0;
186
+ groups.append(button);
187
+ });
188
+ catalog.append(groups);
189
+ wrap.append(catalog);
190
+ }
169
191
  const inbox = node("section", "overview-block");
170
192
  const inboxHead = sectionHead(t("overview.inbox"), {
171
- count: (state.attention || []).length
193
+ count: counts ? counts.openInputs : (state.attention || []).length
172
194
  });
173
195
  inbox.append(inboxHead);
174
196
  const items = state.attention || [];
175
197
  if (!items.length) {
176
198
  const empty = node("div", "inbox-empty");
177
- empty.append(node("span", "dot"), node("span", "", t("overview.inboxEmpty")));
199
+ empty.append(node("span", "dot"), node("span", "", counts && counts.openInputs > 0
200
+ ? t("catalog.inspectInputs") : t("overview.inboxEmpty")));
178
201
  inbox.append(empty);
179
202
  } else {
180
203
  const list = node("div", "inbox-list");
@@ -184,6 +207,7 @@ export function renderOverview(detail, state, t, locale, onSelect) {
184
207
  inbox.append(list);
185
208
  }
186
209
  wrap.append(inbox);
210
+ if (state.catalogScope) wrap.append(node("p", "", t("catalog.pageScope")));
187
211
 
188
212
  // Tasks whose Task-first projection says they need attention: blocked,
189
213
  // recovering, or in an attention state. This replaces the raw stalled-AgentRun
@@ -335,9 +359,13 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
335
359
  "muted mono",
336
360
  project.directory + " · " + project.coverage + " · "
337
361
  + (project.expectedLocalCommit ? project.expectedLocalCommit.slice(0, 12) : "unknown")
362
+ + " → " + (project.deliveryLocalCommit ? project.deliveryLocalCommit.slice(0, 12) : "unknown")
338
363
  + " → "
339
364
  + (project.remoteCommit ? project.remoteCommit.slice(0, 12) : "unknown")
365
+ + " · PR/MR: " + (project.state || "none")
366
+ + (project.adoption ? " · " + project.adoption.id : "")
340
367
  ));
368
+ card.append(node("p", "record-copy", project.reason));
341
369
  });
342
370
  summaryBody.append(card);
343
371
  }
@@ -38,6 +38,12 @@ export const DASHBOARD_HTML = `<!doctype html>
38
38
  </label>
39
39
  <div id="status-filters" class="filters" role="group" aria-label="Filter by status" data-i18n-aria-label="filters.label"></div>
40
40
  <div id="task-list" class="task-list" aria-label="Tasks" data-i18n-aria-label="board.title" aria-live="polite"><div class="loading" data-i18n="loading.dashboard">Reading local state…</div></div>
41
+ <div id="catalog-controls" class="catalog-pager" aria-live="polite">
42
+ <button id="catalog-reset" type="button" data-i18n="catalog.first">First page</button>
43
+ <span id="catalog-count"></span>
44
+ <button id="catalog-next" type="button" data-i18n="catalog.next" disabled>Next page</button>
45
+ <button id="catalog-attention-reset" type="button" data-i18n="catalog.clearAttention" hidden>Clear attention filter</button>
46
+ </div>
41
47
  <div class="sidebar-foot">
42
48
  <div class="sidebar-controls">
43
49
  <label class="select-control">
@@ -19,6 +19,13 @@ body.terminal-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,
19
19
  .task-group-head{display:flex;align-items:baseline;gap:8px;padding:3px 4px 2px}
20
20
  .sidebar-foot{flex:none;padding-top:10px;margin-top:8px;display:grid;gap:8px}
21
21
  .sidebar-controls{display:grid;grid-template-columns:1fr 1fr;gap:8px;align-items:center}
22
+ .catalog-pager{display:grid;grid-template-columns:1fr 1fr;gap:6px;flex:none;padding-top:10px}
23
+ .catalog-pager span{grid-column:1/-1;grid-row:1;text-align:center;font-size:11px;color:var(--muted)}
24
+ .catalog-pager button{min-height:32px;min-width:0;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font:inherit;font-size:11px;cursor:pointer}
25
+ .catalog-pager button:hover:not(:disabled){border-color:var(--accent)}
26
+ .catalog-pager button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
27
+ .catalog-pager button:disabled{opacity:.45;cursor:default}
28
+ #catalog-attention-reset{grid-column:1/-1}
22
29
  /* Main column = top bar + optional section nav + reading surface */
23
30
  .main-col{display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;overflow-y:auto;scrollbar-gutter:stable}
24
31
  .topbar{position:sticky;top:0;z-index:30;flex:none;display:flex;align-items:center;justify-content:space-between;gap:18px;padding:11px max(var(--page-space),calc((100% - 1100px) / 2));background:color-mix(in srgb,var(--bg) 92%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--border)}
@@ -1,11 +1,12 @@
1
1
  import { randomBytes, timingSafeEqual } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
3
  import WebSocket, { WebSocketServer } from "ws";
4
- import { usageError } from "../errors/cliError.js";
4
+ import { CliError, usageError } from "../errors/cliError.js";
5
+ import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
5
6
  import { WebRequestRejected } from "./webMutation.js";
6
7
  import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
7
8
  import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
8
- import { buildWebDashboardSnapshot, buildWebTaskDetail } from "./webSnapshot.js";
9
+ import { buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
9
10
  const MAX_JSON_BODY_BYTES = 16 * 1024;
10
11
  const MAX_TERMINAL_MESSAGE_BYTES = 64 * 1024;
11
12
  const MAX_TERMINAL_BUFFERED_BYTES = 1024 * 1024;
@@ -236,7 +237,11 @@ async function handleHttpRequest(request, response, store, dependencies, token,
236
237
  sendAsset(response, asset, method === "HEAD");
237
238
  }
238
239
  else if (pathname === "/api/dashboard") {
239
- sendJson(response, 200, buildWebDashboardSnapshot(store, now()), method === "HEAD");
240
+ const query = new URL(request.url, "http://localhost").searchParams;
241
+ const snapshot = buildWebTaskCatalog(store, parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
242
+ ? (value === "true" ? ["--all"] : ["--invalid-all"])
243
+ : [`--${key}`, value])));
244
+ sendJson(response, 200, snapshot, method === "HEAD");
240
245
  }
241
246
  else if (pathname.startsWith("/api/tasks/")) {
242
247
  const taskId = decodeURIComponent(pathname.slice("/api/tasks/".length));
@@ -250,6 +255,10 @@ async function handleHttpRequest(request, response, store, dependencies, token,
250
255
  }
251
256
  }
252
257
  catch (error) {
258
+ if (error instanceof CliError) {
259
+ sendJson(response, 400, { error: error.message }, method === "HEAD");
260
+ return;
261
+ }
253
262
  if (error instanceof URIError) {
254
263
  sendJson(response, 400, { error: "Invalid URL encoding." }, method === "HEAD");
255
264
  return;
@@ -1,3 +1,5 @@
1
+ import { providerRetryProjection } from "../runtime/providerRetry.js";
2
+ import { readTaskCatalog } from "../context/taskCatalog.js";
1
3
  import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
2
4
  import { retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
3
5
  import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
@@ -9,56 +11,8 @@ import { runExecutionObservation } from "../agentRun/agentRun.js";
9
11
  import { resolveRuntimeHealth } from "../config/yuiConfig.js";
10
12
  import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
11
13
  import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
12
- export function buildWebDashboardSnapshot(store, now = new Date()) {
13
- return store.transaction((reader) => {
14
- const statusCounts = {
15
- draft: 0,
16
- active: 0,
17
- completed: 0,
18
- cancelled: 0,
19
- archived: 0
20
- };
21
- const projectNames = new Map(reader.listProjects().map((project) => [project.id, project.name]));
22
- let openInputs = 0;
23
- const attention = [];
24
- const tasks = reader.listTasks().map((task) => {
25
- statusCounts[task.status] += 1;
26
- const taskOpenInputs = reader.listInputRequests(task.id)
27
- .filter((request) => request.status === "open").length;
28
- if (task.status !== "archived")
29
- openInputs += taskOpenInputs;
30
- const taskOpen = reader.listInputRequests(task.id)
31
- .filter((request) => request.status === "open");
32
- for (const request of task.status === "archived" ? [] : taskOpen) {
33
- attention.push({ taskId: task.id, taskTitle: task.title, request });
34
- }
35
- const events = reader.listEvents?.(task.id) ?? [];
36
- const needsAttentionCount = task.status === "archived" ? 0 : reader.listRuns(task.id)
37
- .filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
38
- .length;
39
- const execution = buildTaskExecutionProjection(reader, task.id, task, now);
40
- const names = task.projectBindings.flatMap(({ projectId }) => {
41
- const name = projectNames.get(projectId);
42
- return name === undefined ? [] : [name];
43
- });
44
- return {
45
- ...task,
46
- ...(names.length === 0 ? {} : { projectNames: names }),
47
- workItems: countWorkItems(reader.listWorkItems(task.id)),
48
- roleCount: reader.listRoles(task.id).length,
49
- openInputCount: taskOpenInputs,
50
- needsAttentionCount,
51
- execution,
52
- executionStatus: execution?.status ?? null
53
- };
54
- }).sort(compareDashboardTasks);
55
- return {
56
- generatedAt: now.toISOString(),
57
- counts: { total: tasks.length, ...statusCounts, openInputs },
58
- attention: attention.sort(compareAttention),
59
- tasks
60
- };
61
- });
14
+ export function buildWebTaskCatalog(store, options) {
15
+ return store.transaction(reader => readTaskCatalog(reader, options));
62
16
  }
63
17
  export function buildWebTaskDetail(store, taskId, now = new Date()) {
64
18
  return store.transaction((reader) => {
@@ -72,7 +26,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
72
26
  return name === undefined ? [] : [name];
73
27
  });
74
28
  const runs = reader.listRuns(taskId);
75
- const events = reader.listEvents?.(taskId) ?? [];
29
+ const events = reader.listEvents(taskId);
76
30
  const retiredMessageIds = retiredTaskRecordIds(events, "message");
77
31
  const needsAttentionRuns = runs
78
32
  .filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
@@ -95,12 +49,15 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
95
49
  const activeSession = sessions?.sessions[activeRun?.effective.agentId ?? sessions.activeAgentId];
96
50
  const effectiveLaunch = activeRun?.effective ?? activeSession?.effective ?? null;
97
51
  const delivery = sessions?.providerBinding?.run?.status;
52
+ const providerRetry = providerRetryProjection(sessions?.providerBinding);
98
53
  return {
99
54
  ...role,
55
+ providerRetry,
100
56
  // An open record is not proof that its Agent is running.
101
- status: delivery === "accepted" ? "running"
102
- : delivery === "submitting" || delivery === "deferred" ? "waiting"
103
- : activeSession === undefined && activeRun === undefined ? "idle" : "unknown",
57
+ status: providerRetry?.status === "waiting" ? "waiting"
58
+ : delivery === "accepted" ? "running"
59
+ : delivery === "submitting" || delivery === "deferred" ? "waiting"
60
+ : activeSession === undefined && activeRun === undefined ? "idle" : "unknown",
104
61
  sessionTokens: projectSessionTokenMetrics(events, resolveSessionTokenIdentity(activeSession === undefined
105
62
  ? null
106
63
  : { taskId, roleName: role.name, ...activeSession })),
@@ -120,7 +77,7 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
120
77
  && effectiveLaunch.sourceDesiredRevision !== role.launchRevision
121
78
  };
122
79
  });
123
- const execution = buildTaskExecutionProjection(reader, taskId, task, now);
80
+ const execution = buildTaskExecutionProjection(reader, taskId, now);
124
81
  if (execution === null)
125
82
  return null;
126
83
  const remoteDelivery = webRemoteDelivery(reader, task);
@@ -158,10 +115,11 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
158
115
  function webRemoteDelivery(reader, task) {
159
116
  return projectTaskRemoteDelivery({
160
117
  task,
161
- events: reader.listEvents?.(task.id) ?? [],
118
+ events: reader.listEvents(task.id),
162
119
  publications: reader.listPublicationReferences(task.id),
163
120
  managedWorkspaces: reader.listManagedWorkspaces(task.id),
164
121
  runs: reader.listRuns(task.id),
122
+ integrations: reader.listIntegrationAttempts(task.id),
165
123
  currentCandidate: null
166
124
  });
167
125
  }
@@ -258,32 +216,3 @@ function latestStallField(events, runId, field) {
258
216
  .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
259
217
  return stalled?.payload[field];
260
218
  }
261
- function countWorkItems(items) {
262
- const counts = {
263
- total: items.length,
264
- open: 0,
265
- accepted: 0,
266
- retired: 0
267
- };
268
- const mutable = counts;
269
- for (const item of items) {
270
- mutable[item.status] += 1;
271
- }
272
- return counts;
273
- }
274
- function compareDashboardTasks(left, right) {
275
- const statusOrder = {
276
- active: 0,
277
- draft: 1,
278
- completed: 2,
279
- cancelled: 3,
280
- archived: 4
281
- };
282
- return statusOrder[left.status] - statusOrder[right.status]
283
- || Date.parse(right.updatedAt) - Date.parse(left.updatedAt)
284
- || left.id.localeCompare(right.id);
285
- }
286
- function compareAttention(left, right) {
287
- return Date.parse(left.request.createdAt) - Date.parse(right.request.createdAt)
288
- || left.request.id.localeCompare(right.request.id);
289
- }
@@ -1,37 +1,17 @@
1
- import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
2
- import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
3
- import { runGlobalRoleCommand } from "../commands/globalRoleCommands.js";
4
- import { readTaskContext, readTaskContextDelta, inspectTaskContext, withContextObservations } from "../context/taskContext.js";
1
+ import { applyGlobalInputControl } from "../commands/globalRoleCommands.js";
2
+ import { applyTaskInputControl, sendTaskMessageCommand, updateTaskMetadataCommand } from "../commands/taskCommands.js";
3
+ import { runTaskInputCommand } from "../commands/taskInputCommands.js";
4
+ import { inspectTaskContext, readTaskContext, readTaskContextDelta, withContextObservations } from "../context/taskContext.js";
5
5
  import { BUILTIN_CAPABILITIES } from "../kernel/builtinCapabilities.js";
6
6
  import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
7
- import { updateTaskMetadataCommand, sendTaskMessageCommand, runTaskCommand } from "../commands/taskCommands.js";
7
+ import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
8
+ import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
9
+ import { AGENT_HOST_CONTROL_PROTOCOL, foldInterruptLiveReceipt, foldSteerLiveReceipt, sendAgentHostCancelControl, sendAgentHostSteerControl } from "../runtime/agentHost.js";
8
10
  import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
9
- import { runTaskInputCommand } from "../commands/taskInputCommands.js";
10
- import { sendAgentHostSteerControl, sendAgentHostCancelControl, AGENT_HOST_CONTROL_PROTOCOL, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "../runtime/agentHost.js";
11
11
  const DEFAULT_WEB_HOST_CONTROL = {
12
12
  steer: sendAgentHostSteerControl,
13
13
  cancel: sendAgentHostCancelControl
14
14
  };
15
- /** The store-only CLI argv for the shared application-layer primitive
16
- * (decision-3 §7). The Web surface never re-implements the queue/steer/interrupt
17
- * decisions; it drives the exact same command the CLI drives. */
18
- function controlArgv(taskId, input) {
19
- if (input.action === "queue") {
20
- return ["message", "queue", taskId, input.body, "--request-id", input.requestId,
21
- ...(input.to === undefined ? [] : ["--to", input.to]),
22
- ...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
23
- ...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
24
- }
25
- if (input.action === "steer") {
26
- return ["message", "steer", taskId, input.body, "--request-id", input.requestId,
27
- "--expected-target", input.expectedTarget, "--to", input.to,
28
- ...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
29
- ...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
30
- }
31
- return ["role", "interrupt", taskId, input.role, "--expected-target", input.expectedTarget,
32
- ...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage]),
33
- "--request-id", input.requestId];
34
- }
35
15
  /** A queue is delivered to the Leader mailbox only when it is unaddressed or
36
16
  * addressed to the Leader; an addressed Worker/Reviewer queue goes to the Task
37
17
  * mailbox. A steer/interrupt is a live control op, so it only reconciles the
@@ -53,12 +33,8 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
53
33
  // Notifications are after the outer transaction. Their failure must not be
54
34
  // reclassified as a rejection of a mutation that already committed.
55
35
  const notify = (taskId, leader = false) => {
56
- if (options.runtime?.notifyMailboxChanged) {
57
- void options.runtime.notifyMailboxChanged(leader
58
- ? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
59
- }
60
- else
61
- options.runtime?.notifyStateChanged(taskId);
36
+ void options.runtime?.notifyMailboxChanged(leader
37
+ ? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
62
38
  };
63
39
  return {
64
40
  globalState: (roleName) => {
@@ -79,20 +55,15 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
79
55
  };
80
56
  },
81
57
  globalControl: async (roleName, input) => {
82
- const argv = input.action === "interrupt"
83
- ? ["interrupt", roleName, "--request-id", input.requestId, "--expected-target", input.expectedTarget,
84
- ...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage])]
85
- : ["message", input.action, roleName, input.body, "--request-id", input.requestId,
86
- ...(input.action === "steer" ? ["--expected-target", input.expectedTarget] : [])];
87
- const result = webLocalMutation(store, tx => runGlobalRoleCommand(argv, tx, {
88
- env: {}, yuiHome: options.yuiHome, jsonOutput: true
58
+ const result = webLocalMutation(store, tx => applyGlobalInputControl(roleName, input, tx, {
59
+ env: {}, yuiHome: options.yuiHome
89
60
  }));
90
- if (typeof result === "string") {
61
+ if (result.kind === "output") {
91
62
  if (input.action === "queue")
92
- void options.runtime?.notifyMailboxChanged?.({
63
+ void options.runtime?.notifyMailboxChanged({
93
64
  kind: "global-role-runtime", roleName
94
65
  });
95
- return { action: input.action, ...JSON.parse(result) };
66
+ return { action: input.action, ...result.data };
96
67
  }
97
68
  if (result.kind !== "input-steer" && result.kind !== "input-interrupt") {
98
69
  throw new WebRequestRejected("Global input cannot perform a Session lifecycle operation.");
@@ -138,7 +109,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
138
109
  message: (taskId, body, intent, requestId) => {
139
110
  // Preserve the submission's intent and frozen receipt separately from
140
111
  // queue/steer controls; an omitted intent still means discuss.
141
- const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, undefined, commandOptions, undefined, intent, requestId));
112
+ const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, commandOptions, undefined, intent, requestId));
142
113
  notify(taskId, queuedForLeader);
143
114
  return { record: message, revision: message.createdAt,
144
115
  disposition: queuedForLeader ? "queued" : "saved",
@@ -149,7 +120,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
149
120
  /**
150
121
  * The decision-3 three-action input-control path for the local-user Web
151
122
  * surface. Its store-only phase is the identical shared application-layer
152
- * primitive the CLI uses (`runTaskCommand`), run inside `webLocalMutation`
123
+ * typed primitive the CLI uses, run inside `webLocalMutation`
153
124
  * so a rejected input is provably not-submitted. A ready steer/interrupt
154
125
  * returns a live intent; the single Agent Host edge then runs OUTSIDE the
155
126
  * transaction exactly as cli.ts performs it — never a fallback, retarget, or
@@ -158,7 +129,7 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
158
129
  * the durable Message is retained (decision-3 §1/§3/§5, message-5 gap F).
159
130
  */
160
131
  control: async (taskId, input) => {
161
- const execution = webLocalMutation(store, (tx) => runTaskCommand(controlArgv(taskId, input), tx, commandOptions));
132
+ const execution = webLocalMutation(store, (tx) => applyTaskInputControl(taskId, input, tx, commandOptions));
162
133
  if (execution.kind === "output") {
163
134
  // A queue receipt, or a steer/interrupt that was saved-but-not-delivered
164
135
  // or an idempotent replay: fully durable, no live edge, exact disposition.
@@ -319,8 +319,7 @@ export function validateWorkItem(workItem) {
319
319
  "updatedAt",
320
320
  "endedAt",
321
321
  "acceptedCandidateId",
322
- "currentCandidateId",
323
- "historicalState"
322
+ "currentCandidateId"
324
323
  ], "WorkItem");
325
324
  if (workItem.schemaVersion !== 15)
326
325
  throw new Error("WorkItem must use schemaVersion 15.");
@@ -397,16 +396,6 @@ export function validateWorkItem(workItem) {
397
396
  if (workItem.currentCandidateId !== undefined
398
397
  && !candidateIds.has(workItem.currentCandidateId))
399
398
  throw new Error("Current Candidate is missing.");
400
- if (workItem.historicalState !== undefined) {
401
- const historical = workItem.historicalState;
402
- if (!["pending", "running", "awaiting_acceptance", "completed", "failed", "retired"].includes(historical.status)) {
403
- throw new Error("Historical Work Item status is invalid.");
404
- }
405
- if (historical.outcome !== undefined)
406
- requireText(historical.outcome, "Historical outcome");
407
- if (historical.endedAt !== undefined)
408
- requireTimestamp(historical.endedAt, "Historical endedAt");
409
- }
410
399
  if (workItem.outcome !== undefined)
411
400
  requireText(workItem.outcome, "Work item outcome");
412
401
  requireTimestamp(workItem.createdAt, "Work Item createdAt");
@@ -0,0 +1,63 @@
1
+ import { isAbsolute, join, relative } from "node:path";
2
+ export class CleanupInspectionError extends Error {
3
+ checks;
4
+ constructor(checks) {
5
+ super(checks.map(renderCleanupCheck).join("\n"));
6
+ this.checks = checks;
7
+ this.name = "CleanupInspectionError";
8
+ }
9
+ }
10
+ export function renderCleanupCheck(check) {
11
+ return `[${check.reason}] ${check.resource}: ${check.detail}`
12
+ + ` Expected=${JSON.stringify(check.expected)}; observed=${JSON.stringify(check.observed)}.`
13
+ + (check.actions.length === 0 ? "" : ` Inspect/resolve: ${check.actions.join("; ")}.`);
14
+ }
15
+ /** Do not forward arbitrary Git stderr, native arguments, or foreign paths. */
16
+ export function cleanupCheckFromError(error, resource, sources, actions) {
17
+ if (error instanceof CleanupInspectionError) {
18
+ return error.checks.map(check => ({ ...check, resource, sources, actions }));
19
+ }
20
+ let cause = error;
21
+ let errorCode;
22
+ for (let depth = 0; depth < 4 && cause instanceof Error; depth += 1) {
23
+ const code = cause.code;
24
+ if ((typeof code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(code))
25
+ || (typeof code === "number" && Number.isSafeInteger(code))) {
26
+ errorCode = code;
27
+ break;
28
+ }
29
+ cause = cause.cause;
30
+ }
31
+ return [{ resource, reason: "inspection-unavailable", status: "unknown",
32
+ detail: "The resource could not be inspected; no safe cleanup conclusion is available.",
33
+ expected: "readable owned resource", observed: errorCode === undefined ? "unavailable" : { errorCode }, sources, actions }];
34
+ }
35
+ export function cleanupFailure(reason, detail, expected, observed, status = "blocked") {
36
+ throw new CleanupInspectionError([{ resource: "git-workspace", reason, status,
37
+ detail, expected, observed, sources: [], actions: [] }]);
38
+ }
39
+ /** Describe exact differences without disclosing paths outside this Task.
40
+ * Legacy locations are displayed only for this Task's exact branch identity;
41
+ * displaying a historical location is not accepting it as migration proof.
42
+ */
43
+ export function workspacePathValue(workspace, path) {
44
+ const inside = (root) => {
45
+ const value = relative(root, path);
46
+ return value !== ".." && !value.startsWith("../") && !isAbsolute(value) ? value || "." : undefined;
47
+ };
48
+ const marker = `/workspaces/tasks/${workspace.owner.taskId}/`;
49
+ const index = workspace.root.lastIndexOf(marker);
50
+ if (index >= 0) {
51
+ const home = workspace.root.slice(0, index);
52
+ const taskPath = inside(join(home, "workspaces", "tasks", workspace.owner.taskId));
53
+ if (taskPath !== undefined)
54
+ return `<task>/${taskPath}`;
55
+ const legacyPath = inside(join(home, "workspaces", "worktree"));
56
+ const parts = legacyPath?.split("/");
57
+ const taskSegment = workspace.entries.find(e => e.access === "write")?.branch.split("/")[1];
58
+ if (parts?.length === 3 && parts[1] === taskSegment)
59
+ return `<legacy-worktree>/${legacyPath}`;
60
+ }
61
+ const local = inside(workspace.root);
62
+ return local === undefined ? "[outside authorized Task paths]" : `<owner>/${local}`;
63
+ }