@zq-silk/yui 1.1.2 → 2.1.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 (115) hide show
  1. package/ARCHITECTURE.md +2 -1
  2. package/ARCHITECTURE.zh-CN.md +2 -1
  3. package/README.md +2 -0
  4. package/dist/cli/commandCatalog.js +344 -324
  5. package/dist/cli/commandDiscovery.js +69 -0
  6. package/dist/cli/completion.js +22 -227
  7. package/dist/cli/dynamicCompletion.js +12 -6
  8. package/dist/cli/invocationAuthority.js +2 -2
  9. package/dist/cli/invocationRouter.js +18 -7
  10. package/dist/cli/managedDiagnostics.js +2 -1
  11. package/dist/cli/updatePorts.js +0 -5
  12. package/dist/cli.js +184 -130
  13. package/dist/commands/capabilityCommands.js +5 -5
  14. package/dist/commands/controllerCommands.js +2 -1
  15. package/dist/commands/globalRoleCommands.js +86 -44
  16. package/dist/commands/grantCommands.js +48 -8
  17. package/dist/commands/taskCommands.js +157 -196
  18. package/dist/commands/taskContextCommand.js +29 -6
  19. package/dist/commands/taskFactCommands.js +10 -69
  20. package/dist/commands/taskInputCommands.js +26 -35
  21. package/dist/commands/taskPublicationCommands.js +2 -35
  22. package/dist/commands/taskPublicationVerifyCommand.js +1 -2
  23. package/dist/commands/taskUpstreamCommands.js +2 -1
  24. package/dist/context/runContextPack.js +8 -29
  25. package/dist/context/sessionBootstrapManifest.js +5 -101
  26. package/dist/context/taskContext.js +104 -41
  27. package/dist/controller/clientRuntime.js +16 -8
  28. package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
  29. package/dist/controller/runtime.js +6 -0
  30. package/dist/core/controllerClient.js +46 -21
  31. package/dist/core/protocol.js +12 -7
  32. package/dist/doctor/doctor.js +4 -3
  33. package/dist/errors/cliError.js +4 -4
  34. package/dist/errors/cliFailure.js +213 -0
  35. package/dist/executor/fileRoleLaunchPlanner.js +1 -4
  36. package/dist/grant/capabilityGrant.js +13 -0
  37. package/dist/grant/taskAuthorization.js +127 -0
  38. package/dist/kernel/accessAssessment.js +1 -0
  39. package/dist/kernel/builtinCapabilities.js +88 -9
  40. package/dist/kernel/capabilityRegistry.js +64 -19
  41. package/dist/kernel/kernelPorts.js +2 -2
  42. package/dist/nativeAgent/agent.js +173 -0
  43. package/dist/nativeAgent/contracts.js +1 -0
  44. package/dist/nativeAgent/demo.js +29 -0
  45. package/dist/nativeAgent/index.js +3 -0
  46. package/dist/nativeAgent/mockProvider.js +43 -0
  47. package/dist/nativeAgent/textTools.js +156 -0
  48. package/dist/nativeAgent/validation.js +95 -0
  49. package/dist/output/boundedRead.js +118 -0
  50. package/dist/plugins/pluginService.js +98 -8
  51. package/dist/release/releaseWorkflowEngine.js +8 -1
  52. package/dist/resources/projectResourceService.js +23 -1
  53. package/dist/runtime/managedCaller.js +2 -2
  54. package/dist/runtime/managedIdentity.js +6 -0
  55. package/dist/runtime/runtimeCoherence.js +10 -8
  56. package/dist/storage/sqliteSchema.js +10 -0
  57. package/dist/storage/storageVersions.js +1 -1
  58. package/dist/storage/taskStore.js +3 -0
  59. package/dist/surface/surfaceContributions.js +3 -0
  60. package/dist/task/leaderArchive.js +111 -0
  61. package/dist/task/leaderArchiveAuthority.js +24 -0
  62. package/dist/task/taskAuthority.js +5 -10
  63. package/dist/tmux/commandExecutor.js +7 -5
  64. package/dist/web/assets/assetManifest.js +38 -22
  65. package/dist/web/assets/client/api.js +115 -0
  66. package/dist/web/assets/client/app.js +555 -863
  67. package/dist/web/assets/client/components.js +323 -897
  68. package/dist/web/assets/client/detail.js +333 -0
  69. package/dist/web/assets/client/dock.js +225 -0
  70. package/dist/web/assets/client/dom.js +55 -3
  71. package/dist/web/assets/client/evidence.js +172 -0
  72. package/dist/web/assets/client/format.js +25 -14
  73. package/dist/web/assets/client/forms.js +230 -0
  74. package/dist/web/assets/client/i18n.js +1020 -791
  75. package/dist/web/assets/client/overview.js +115 -0
  76. package/dist/web/assets/client/records.js +63 -0
  77. package/dist/web/assets/client/sections.js +313 -0
  78. package/dist/web/assets/client/sidebar.js +117 -0
  79. package/dist/web/assets/client/theme.js +61 -22
  80. package/dist/web/assets/icons.js +39 -0
  81. package/dist/web/assets/shell.js +124 -118
  82. package/dist/web/assets/styles/base.js +41 -0
  83. package/dist/web/assets/styles/components.js +167 -0
  84. package/dist/web/assets/styles/layout.js +49 -77
  85. package/dist/web/assets/styles/markdown.js +16 -24
  86. package/dist/web/assets/styles/responsive.js +33 -44
  87. package/dist/web/assets/styles/tokens.js +65 -89
  88. package/dist/web/assets/styles/views.js +289 -0
  89. package/dist/web/webServer.js +2 -1
  90. package/docs/architecture/README.md +1 -0
  91. package/docs/architecture/README.zh-CN.md +1 -0
  92. package/docs/cli-information-contract.md +107 -0
  93. package/docs/cli-information-contract.zh-CN.md +81 -0
  94. package/docs/managed-turn-and-session-runtime.md +9 -0
  95. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  96. package/docs/plugin-sdk.md +7 -3
  97. package/docs/plugin-sdk.zh-CN.md +5 -2
  98. package/docs/release-workflow.md +42 -9
  99. package/docs/release-workflow.zh-CN.md +30 -7
  100. package/docs/storage-baseline.md +5 -0
  101. package/docs/testing/verification-levels.md +2 -2
  102. package/i18n/README.zh-CN.md +2 -0
  103. package/package.json +1 -1
  104. package/skills/yui-leader/SKILL.md +8 -1
  105. package/skills/yui-leader/references/authorization.md +64 -0
  106. package/skills/yui-leader/references/execution.md +18 -4
  107. package/skills/yui-leader/references/task-plugins.md +4 -2
  108. package/skills/yui-operator/SKILL.md +9 -2
  109. package/skills/yui-runtime/SKILL.md +77 -9
  110. package/skills/yui-runtime/references/publication.md +3 -1
  111. package/dist/web/assets/client/taskSummary.js +0 -350
  112. package/dist/web/assets/client/taskSurface.js +0 -616
  113. package/dist/web/assets/client/view.js +0 -608
  114. package/dist/web/assets/styles/cards.js +0 -247
  115. package/dist/web/assets/styles/widgets.js +0 -168
package/dist/cli.js CHANGED
@@ -12,6 +12,8 @@ import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
12
12
  import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "./artifacts/artifactCapability.js";
13
13
  import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
14
14
  import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
15
+ import { discoveryCommandTree } from "./cli/commandDiscovery.js";
16
+ import { contextRecordReader } from "./context/taskContext.js";
15
17
  import { renderCompletion } from "./cli/completion.js";
16
18
  import { runCompletionWizard } from "./cli/completionWizard.js";
17
19
  import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
@@ -20,7 +22,8 @@ import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/i
20
22
  import { routeInvocation } from "./cli/invocationRouter.js";
21
23
  import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
22
24
  import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
23
- import { requireManagedGlobalCaller } from "./runtime/managedCaller.js";
25
+ import { requireManagedGlobalCaller, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
26
+ import { hasManagedIdentity } from "./runtime/managedIdentity.js";
24
27
  import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
25
28
  import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
26
29
  import { runUpdateCommand } from "./cli/updateCommand.js";
@@ -41,7 +44,7 @@ import { runProjectCommand } from "./commands/projectCommands.js";
41
44
  import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
42
45
  import { runResourcesCommand } from "./commands/resourcesCommands.js";
43
46
  import { parseSessionReconcileOptions, parseSessionStopOptions, runSessionReconcileCommand, runSessionStopCommand } from "./commands/sessionCommands.js";
44
- import { assertTaskDeliveryAuthority, taskLocalActor } from "./task/taskAuthority.js";
47
+ import { assertTaskDeliveryAuthority, taskLocalActor, resolveJobCaller } from "./task/taskAuthority.js";
45
48
  import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
46
49
  import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
47
50
  import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, parseTaskArchiveArguments, parseTaskCompletionRequest, planReplicatedWorkItemLanes, preflightTaskCompletion, preserveReviewRoundWorkspace, previewTaskRoleAgentConfigurationMutation, requireWorkItemAssignee, runTaskCommand, validateTaskArchiveRequest } from "./commands/taskCommands.js";
@@ -58,7 +61,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
58
61
  import { FileCompletionManager } from "./completion/fileCompletionManager.js";
59
62
  import { CONFIG_DOMAINS } from "./config/configCatalog.js";
60
63
  import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
61
- import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
64
+ import { readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
62
65
  import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
63
66
  import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
64
67
  import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
@@ -70,6 +73,7 @@ import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliat
70
73
  import { callController, ControllerClientError } from "./core/controllerClient.js";
71
74
  import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
72
75
  import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
76
+ import { describeCliFailure, renderCliFailure, sanitizeFailureText, sanitizeFailureDetails } from "./errors/cliFailure.js";
73
77
  import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
74
78
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
75
79
  import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
@@ -82,7 +86,7 @@ import { parseTaskAgentCapabilityQuery } from "./commands/taskAgentCapabilities.
82
86
  import { capabilityReaderIdentity } from "./controller/agentCapabilities.js";
83
87
  import { formatTimestamp } from "./output/timePresentation.js";
84
88
  import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
85
- import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
89
+ import { assertCliHomeReleaseFence } from "./release/cliHomeReleaseFence.js";
86
90
  import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
87
91
  import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
88
92
  import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
@@ -102,7 +106,6 @@ import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/ma
102
106
  import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
103
107
  import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
104
108
  import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
105
- import { SqliteSchemaError } from "./storage/sqliteSchema.js";
106
109
  import { inspectStorageSchema } from "./storage/storageSchema.js";
107
110
  import { resolveYuiHome } from "./storage/taskStore.js";
108
111
  import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
@@ -123,51 +126,44 @@ const taskFinalReviewInvocation = extractTaskFinalReviewRequest(process.argv.sli
123
126
  const rawArgs = [...taskFinalReviewInvocation.args];
124
127
  const jsonOutput = rawArgs.includes("--json");
125
128
  const args = normalizeAliases(jsonOutput ? rawArgs.filter((argument) => argument !== "--json") : rawArgs);
129
+ let invocationHome;
126
130
  void main().catch((error) => {
127
131
  if (error instanceof CleanupInspectionError) {
128
132
  error = cleanupCliError(error, error.checks[0]?.resource ?? "workspace");
129
133
  }
130
- if (error instanceof CliError) {
131
- const rendered = jsonOutput
132
- ? JSON.stringify({ ok: false, code: error.code, message: error.message, details: error.details })
133
- : `${error.code}: ${error.message}${error.helpText === undefined ? "" : `\n\n${error.helpText.trimEnd()}`}`;
134
- process.stderr.write(`${rendered}\n`);
135
- process.exitCode = error.exitCode;
136
- return;
137
- }
138
- const message = runtimeFailureMessage(error);
139
- process.stderr.write(`${jsonOutput
140
- ? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message, details: {} })
141
- : `RUNTIME_ERROR: ${message}`}\n`);
142
- process.exitCode = 5;
134
+ const failure = describeCliFailure(error, failureContext());
135
+ process.stderr.write(`${renderCliFailure(failure, jsonOutput)}\n`);
136
+ process.exitCode = failure.exitCode;
143
137
  });
144
- function runtimeFailureMessage(error) {
145
- const message = error instanceof Error ? error.message : String(error);
146
- if (error instanceof ControllerClientError
147
- && ["CONTROLLER_NOT_RUNNING", "CONTROLLER_UNAVAILABLE", "CONTROLLER_DISCOVERY_INVALID"].includes(error.code)) {
148
- return `${message} Run \`yui doctor\` with this Session's YUI_HOME and CLI; `
149
- + "if the Controller is stopped, run `yui start`; if its process is present but unreachable, "
150
- + "inspect it before `yui controller restart`. The Operator or user can perform recovery.";
151
- }
152
- if (!(error instanceof SqliteSchemaError))
153
- return message;
154
- try {
155
- return `${message}\n${describeCliHomeInvocation({
156
- home: resolveYuiHome(process.env),
157
- packageRoot: fileURLToPath(new URL("../", import.meta.url)),
158
- entryPath: fileURLToPath(import.meta.url)
159
- })}`;
160
- }
161
- catch {
162
- return message;
163
- }
138
+ function failureContext() {
139
+ const route = routeInvocation(args);
140
+ const node = "node" in route ? route.node : route.helpNode;
141
+ const operands = args.slice(node.path.length - 1);
142
+ const publicValues = new Set([
143
+ ...Object.values(node.argumentValues).flat(),
144
+ ...Object.values(node.optionValues).flat()
145
+ ]);
146
+ const recordId = /^(?:task|project|message|run|work|wake|operation|review|input|job|integration|publication)-\d+(?:\/(?:message|run|work|wake|review|input|job|integration|publication)-\d+)?$/;
147
+ return {
148
+ operation: node.path.join(" "),
149
+ targets: operands.filter(value => recordId.test(value)),
150
+ // Do not replay argv: operands include Message bodies, config values and credentials.
151
+ privateValues: operands.filter(value => !/^--[a-z][a-z-]*$/.test(value) && !recordId.test(value) && !publicValues.has(value)),
152
+ home: invocationHome ?? process.env.YUI_HOME ?? "(default Home unresolved)",
153
+ cli: fileURLToPath(import.meta.url),
154
+ environment: process.env
155
+ };
164
156
  }
165
157
  export async function main() {
166
158
  const home = resolveYuiHome(process.env);
159
+ const discovery = discoveryCommandTree(process.env);
160
+ invocationHome = home;
167
161
  const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
168
162
  const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
169
163
  || process.env.YUI_SESSION_SCOPE === "global";
170
164
  const homeFreeInvocation = args.length === 0
165
+ || (args[0] === "config" && (args[1] === "describe"
166
+ || (args[1] === "completion" && args[2] === "candidates")))
171
167
  || (args[0] === "version" && args.length === 1)
172
168
  || routedForFence?.kind === "help"
173
169
  || routedForFence?.kind === "path-error"
@@ -188,7 +184,7 @@ export async function main() {
188
184
  stdio: "inherit"
189
185
  });
190
186
  if (delegated.error !== undefined) {
191
- throw runtimeError(`Target release activation driver could not start: ${delegated.error.message}`);
187
+ throw runtimeError("Target release activation driver could not start.", { cause: delegated.error });
192
188
  }
193
189
  if (delegated.signal !== null) {
194
190
  throw runtimeError(`Target release activation driver stopped by signal ${delegated.signal}.`);
@@ -197,16 +193,16 @@ export async function main() {
197
193
  return;
198
194
  }
199
195
  if (args.length === 0) {
200
- emit(renderCommandHelp((await import("./cli/commandCatalog.js")).ROOT_COMMAND, VERSION));
196
+ emit(renderCommandHelp(discovery, VERSION), false, jsonOutput ? describeCommandTree(discovery) : undefined);
201
197
  return;
202
198
  }
203
199
  if (args[0] === "version" && args.length === 1) {
204
200
  emit(VERSION, true, yuiVersionIdentity());
205
201
  return;
206
202
  }
207
- const invocation = routeInvocation(args);
203
+ const invocation = routeInvocation(args, discovery);
208
204
  if (invocation.kind === "help") {
209
- emit(renderCommandHelp(invocation.node, VERSION), true);
205
+ emit(renderCommandHelp(invocation.node, VERSION), true, jsonOutput ? describeCommandTree(invocation.node) : undefined);
210
206
  return;
211
207
  }
212
208
  if (invocation.kind === "path-error") {
@@ -217,11 +213,22 @@ export async function main() {
217
213
  }
218
214
  if (args[0] === "version")
219
215
  throw usageError("Version usage: yui version");
216
+ // Offline discovery is not an execution credential. Target selectors enter
217
+ // the original authenticated path lazily, only when stored data is needed.
218
+ if (args[0] === "config" && args[1] === "completion" && args[2] === "candidates") {
219
+ await completionCommand(home, invocation.node);
220
+ return;
221
+ }
222
+ if (args[0] === "config" && args[1] === "describe") {
223
+ const target = findCommandNode(args[2] === undefined ? ["config"] : ["config", args[2]], discovery);
224
+ if (args.length > 3 || !target)
225
+ throw usageError("Config describe usage: yui config describe [visible-domain].");
226
+ emit(renderCommandHelp(target, VERSION), false, describeCommandTree(target));
227
+ return;
228
+ }
220
229
  const { contract: taskFinalReviewContract, verifiedStore } = await preflightManagedTaskControlPlane();
221
230
  assertTaskInvocationScope(args, process.env);
222
- if (["config", "resources"].includes(args[0] ?? "") && (managedInvocation
223
- || process.env.YUI_ROLE !== undefined || process.env.YUI_AGENT_ID !== undefined
224
- || process.env.YUI_NATIVE_SESSION_ID !== undefined)) {
231
+ if (["config", "resources"].includes(args[0] ?? "") && hasManagedIdentity(process.env)) {
225
232
  const ownedStore = verifiedStore === undefined ? openCurrentTaskStore(home) : undefined;
226
233
  try {
227
234
  assertConfigurationAuthority(args, verifiedStore ?? ownedStore, process.env);
@@ -240,7 +247,10 @@ export async function main() {
240
247
  const subcommand = args[1];
241
248
  if (subcommand === "install" && args.length === 3) {
242
249
  const result = runReleaseInstall(home, args[2]);
243
- emit(renderReleaseInstallResult(result), false, result);
250
+ if (result.outcome === "aborted")
251
+ emitFailureResult(renderReleaseInstallResult(result), result);
252
+ else
253
+ emit(renderReleaseInstallResult(result), false, result);
244
254
  if (result.outcome === "aborted")
245
255
  process.exitCode = 5;
246
256
  return;
@@ -259,7 +269,11 @@ export async function main() {
259
269
  + "(or activate the active release when exactly one is installed).");
260
270
  }
261
271
  const result = await runReleaseActivate(home, releaseId);
262
- emit(renderReleaseActivateResult(result), false, result);
272
+ if (result.outcome === "aborted" || result.outcome === "dual-owner") {
273
+ emitFailureResult(renderReleaseActivateResult(result), result);
274
+ }
275
+ else
276
+ emit(renderReleaseActivateResult(result), false, result);
263
277
  if (result.outcome === "aborted" || result.outcome === "dual-owner") {
264
278
  process.exitCode = 5;
265
279
  }
@@ -271,21 +285,6 @@ export async function main() {
271
285
  await completionCommand(home, invocation.node);
272
286
  return;
273
287
  }
274
- if (args[0] === "config" && args[1] === "describe") {
275
- const describeNode = findCommandNode(["config", "describe"]);
276
- if (describeNode === undefined)
277
- throw new Error("Config describe command is missing from the catalog.");
278
- const domain = args[2];
279
- const domains = describeNode.argumentValues[0] ?? [];
280
- if (args.length > 3 || (domain !== undefined && !domains.includes(domain))) {
281
- throw usageError(`Config describe usage: ${describeNode.usage.join(" | ")}.`);
282
- }
283
- const target = findCommandNode(domain === undefined ? ["config"] : ["config", domain]);
284
- if (target === undefined)
285
- throw new Error(`Config domain is missing from the catalog: ${domain}.`);
286
- emit(renderCommandHelp(target, VERSION), false, describeCommandTree(target));
287
- return;
288
- }
289
288
  if (args[0] === "capability") {
290
289
  const result = await runCapabilityCommand(args.slice(1), home, process.env);
291
290
  emit(JSON.stringify(result, null, 2), false, result);
@@ -356,7 +355,7 @@ export async function main() {
356
355
  codex = await prepareLocalCodexStartup(startupStore, home, process.env);
357
356
  }
358
357
  catch (error) {
359
- throw runtimeError(`Controller is ready, but Codex shared startup failed: ${error instanceof Error ? error.message : String(error)}`);
358
+ throw runtimeError("Controller is ready, but Codex shared startup failed.", { cause: error });
360
359
  }
361
360
  const lines = ["Yui Controller is ready."];
362
361
  if (codex.daemons.length === 0)
@@ -398,20 +397,13 @@ export async function main() {
398
397
  if (args[0] === "upgrade") {
399
398
  const result = await runUpgradeCommand(args.slice(1), home);
400
399
  process.exitCode = result.exitCode;
401
- emit(result.output, false, result.data);
400
+ if (result.exitCode !== 0)
401
+ emitFailureResult(result.output, result.data);
402
+ else
403
+ emit(result.output, false, result.data);
402
404
  return;
403
405
  }
404
406
  if (args[0] === "internal") {
405
- if (args[1] === "session-cli-refresh" && args.length === 2) {
406
- if (process.env.YUI_SESSION_SCOPE === "task"
407
- || (process.env.YUI_SESSION_SCOPE === "global" && process.env.YUI_ROLE !== "operator")) {
408
- throw usageError("Managed Session CLI refresh may be run only by the user or global Operator.");
409
- }
410
- const result = refreshManagedSessionCliWrappers(home, currentInvocationEntryPoint());
411
- emit(`Refreshed ${result.refreshed} managed Session CLI wrapper(s); `
412
- + `${result.current} already current, ${result.skipped} skipped.`, false, result);
413
- return;
414
- }
415
407
  if (args[1] === "agent-host" && args.length === 3) {
416
408
  process.exitCode = await runAgentHost({
417
409
  home,
@@ -483,17 +475,7 @@ export async function main() {
483
475
  catch (error) {
484
476
  if (!(error instanceof ControllerClientError))
485
477
  throw error;
486
- if (jsonOutput) {
487
- process.stderr.write(`${JSON.stringify({
488
- ok: false,
489
- code: error.code,
490
- message: error.message,
491
- details: {}
492
- })}\n`);
493
- }
494
- else {
495
- process.stderr.write(`RUNTIME_ERROR: ${error.message}\n`);
496
- }
478
+ process.stderr.write(`${renderCliFailure(describeCliFailure(error, failureContext()), jsonOutput, error.code)}\n`);
497
479
  process.exitCode = 5;
498
480
  }
499
481
  return;
@@ -517,17 +499,7 @@ export async function main() {
517
499
  // unknown-active block.
518
500
  if (!(error instanceof ControllerClientError))
519
501
  throw error;
520
- if (jsonOutput) {
521
- process.stderr.write(`${JSON.stringify({
522
- ok: false,
523
- code: error.code,
524
- message: error.message,
525
- details: {}
526
- })}\n`);
527
- }
528
- else {
529
- process.stderr.write(`RUNTIME_ERROR: ${error.message}\n`);
530
- }
502
+ process.stderr.write(`${renderCliFailure(describeCliFailure(error, failureContext()), jsonOutput, error.code)}\n`);
531
503
  process.exitCode = 5;
532
504
  }
533
505
  return;
@@ -674,7 +646,7 @@ export async function main() {
674
646
  yuiHome: home,
675
647
  historyLimit: resolveTmuxHistoryLimit(store.getConfig().tmuxHistoryLimit),
676
648
  terminalInput: process.stdin,
677
- onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
649
+ onWarning: (message) => writeCliWarning(message)
678
650
  });
679
651
  const telemetry = openSchedulerTelemetry(home, store.getConfig());
680
652
  try {
@@ -684,8 +656,11 @@ export async function main() {
684
656
  const runtime = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
685
657
  environment: process.env,
686
658
  onError: (error) => {
687
- const message = error instanceof Error ? error.message : String(error);
688
- process.stderr.write(`Controller runtime error: ${message}\n`);
659
+ // Advisory async failure: preserve the foreground command's exit status.
660
+ const failure = describeCliFailure(error, failureContext());
661
+ process.stderr.write(`${renderCliFailure({
662
+ ...failure, details: { ...failure.details, advisory: true }
663
+ }, jsonOutput)}\n`);
689
664
  }
690
665
  });
691
666
  const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
@@ -845,6 +820,10 @@ export async function main() {
845
820
  ? [resolved[1], ...resolved.slice(2)]
846
821
  : ["session", resolved[1] ?? "", ...resolved.slice(2)];
847
822
  const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
823
+ if (typeof result !== "string" && result.kind === "output") {
824
+ emit(result.output, false, result.data);
825
+ return;
826
+ }
848
827
  if (typeof result === "string") {
849
828
  emit(result);
850
829
  return;
@@ -893,6 +872,14 @@ export async function main() {
893
872
  emit(result);
894
873
  return;
895
874
  }
875
+ if (result.kind === "output") {
876
+ if (globalInputFailure !== undefined) {
877
+ emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
878
+ }
879
+ else
880
+ emit(result.output, false, result.data);
881
+ return;
882
+ }
896
883
  if (result.kind === "input-steer") {
897
884
  // Core already persisted the durable Global Message and proved target +
898
885
  // capability + writer fence from the Global Role's own Session set. This
@@ -1101,8 +1088,7 @@ export async function main() {
1101
1088
  finalizeStoppedTaskExecution(result.taskId, store);
1102
1089
  }
1103
1090
  catch (error) {
1104
- const message = error instanceof Error ? error.message : String(error);
1105
- throw runtimeError(`Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed: ${message}`);
1091
+ throw runtimeError("Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed.", { cause: error });
1106
1092
  }
1107
1093
  emit(result.output, false, result);
1108
1094
  return;
@@ -1317,6 +1303,19 @@ export async function main() {
1317
1303
  let archiveRemoteDeliveryProof;
1318
1304
  let archiveTaskReviewCandidate;
1319
1305
  if (resolved[1] === "archive") {
1306
+ const request = parseTaskArchiveArguments(resolved.slice(2));
1307
+ if (request.sourceMessage !== undefined) {
1308
+ if (request.force || request.disposition !== "integrated") {
1309
+ throw usageError("Leader ordinary archive does not authorize --force or --abandon.");
1310
+ }
1311
+ const result = await callController(home, "capability.call", {
1312
+ taskId: request.taskId, caller: resolveJobCaller(process.env, request.taskId),
1313
+ request: { name: "task.archive", requestId: request.requestId,
1314
+ input: { taskId: request.taskId, sourceMessage: request.sourceMessage, purpose: request.purpose } }
1315
+ });
1316
+ emit("Controller archive result; inspect task events if this Session ends before the receipt.\n", false, result);
1317
+ return;
1318
+ }
1320
1319
  const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1321
1320
  runtime,
1322
1321
  environment: process.env,
@@ -1497,7 +1496,7 @@ export async function main() {
1497
1496
  })
1498
1497
  });
1499
1498
  for (const warning of completionWarnings) {
1500
- process.stderr.write(`Warning: ${warning}\n`);
1499
+ writeCliWarning(warning);
1501
1500
  }
1502
1501
  // Refresh observes remote objects only. Choosing and executing an
1503
1502
  // upstream Integration belongs to its explicit command, never completion.
@@ -1962,6 +1961,9 @@ async function preflightManagedTaskControlPlane() {
1962
1961
  if (process.env.YUI_SESSION_SCOPE === "global") {
1963
1962
  return await preflightManagedGlobalControlPlane();
1964
1963
  }
1964
+ if (hasManagedIdentity(process.env)) {
1965
+ throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
1966
+ }
1965
1967
  if (taskFinalReviewInvocation.request !== undefined) {
1966
1968
  throw new Error("Task final-review contract may only be established from the Task Leader's managed Session.");
1967
1969
  }
@@ -2035,7 +2037,7 @@ async function preflightManagedGlobalControlPlane() {
2035
2037
  const expectedRoleKind = process.env.YUI_ROLE === "operator" ? "operator" : "global";
2036
2038
  if (manifest.owner.scope !== "global"
2037
2039
  || manifest.roleKind !== expectedRoleKind) {
2038
- throw new Error("Managed global invocation does not match its Session Manifest.");
2040
+ throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
2039
2041
  }
2040
2042
  if (expectedRoleKind === "operator" && ["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
2041
2043
  // These commands inspect/adopt the storage contract themselves. Requiring
@@ -2059,28 +2061,24 @@ async function preflightManagedGlobalControlPlane() {
2059
2061
  }
2060
2062
  return { contract: undefined, verifiedStore };
2061
2063
  }
2062
- /**
2063
- * The entry point the current invocation resolves to. Only its executable and
2064
- * CLI path are used, to retarget managed Session wrappers at this installation.
2065
- */
2066
- function currentInvocationEntryPoint() {
2067
- return {
2068
- executable: process.execPath,
2069
- cliEntry: fileURLToPath(import.meta.url)
2070
- };
2071
- }
2072
2064
  function assertManagedSessionManifest(home, scope) {
2073
2065
  const manifestPath = process.env.YUI_SESSION_MANIFEST;
2074
2066
  if (manifestPath === undefined) {
2075
- throw new Error("Managed control-plane invocation requires its Session Manifest.");
2067
+ throw new ManagedRuntimeDriftError("Managed control-plane invocation requires its Session Manifest.");
2068
+ }
2069
+ let manifest;
2070
+ try {
2071
+ manifest = readSessionBootstrapManifest(manifestPath);
2072
+ }
2073
+ catch (error) {
2074
+ throw new ManagedRuntimeDriftError("Managed Session Manifest could not be verified.", { cause: error });
2076
2075
  }
2077
- const manifest = readSessionBootstrapManifest(manifestPath);
2078
2076
  if (manifest.owner.scope !== scope) {
2079
- throw new Error("Managed invocation scope does not match its Session Manifest.");
2077
+ throw new ManagedRuntimeDriftError("Managed invocation scope does not match its Session Manifest.");
2080
2078
  }
2081
2079
  if (scope === "task" && (manifest.owner.scope !== "task"
2082
2080
  || manifest.owner.taskId !== process.env.YUI_TASK_ID)) {
2083
- throw new Error("Managed Task invocation does not match its Session Manifest owner.");
2081
+ throw new ManagedRuntimeDriftError("Managed Task invocation does not match its Session Manifest owner.");
2084
2082
  }
2085
2083
  const expectedPath = resolve(home, "runtime", "session-manifests", `${manifest.digest}.json`);
2086
2084
  // YUI_HOME is canonicalized (its macOS /var -> /private/var prefix is a
@@ -2093,7 +2091,7 @@ function assertManagedSessionManifest(home, scope) {
2093
2091
  canonicalManifestPath = resolve(manifestPath);
2094
2092
  }
2095
2093
  if (canonicalManifestPath !== expectedPath) {
2096
- throw new Error("Managed Session Manifest path is outside this YUI_HOME.");
2094
+ throw new ManagedRuntimeDriftError("Managed Session Manifest path is outside this YUI_HOME.");
2097
2095
  }
2098
2096
  return manifest;
2099
2097
  }
@@ -2104,7 +2102,7 @@ function cleanupCliError(error, fallbackResource) {
2104
2102
  if (error instanceof WorkspaceCleanupBlockedError) {
2105
2103
  return usageError(error.message, undefined, cleanupBlockedDetails(error.reason, error.resource, error.retryable));
2106
2104
  }
2107
- return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true));
2105
+ return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true), { cause: error });
2108
2106
  }
2109
2107
  function cleanupBlockedDetails(reason, resource, retryable, checks) {
2110
2108
  return {
@@ -2540,7 +2538,7 @@ async function prepareCodexForCurrentRoles(store, home, targetAgentId) {
2540
2538
  }
2541
2539
  }
2542
2540
  catch (error) {
2543
- const detail = error instanceof Error ? error.message : String(error);
2541
+ const detail = describeCliFailure(error, failureContext()).message;
2544
2542
  const permissionDenied = isCodexInspectionPermissionError(error);
2545
2543
  codexAppNotice(`${permissionDenied
2546
2544
  ? "Codex sharing could not be prepared or verified from this process"
@@ -2559,7 +2557,7 @@ function assertHomeStartupAuthority(store) {
2559
2557
  requireManagedGlobalCaller(store, process.env);
2560
2558
  }
2561
2559
  function codexAppNotice(message, notification) {
2562
- process.stderr.write(`\nYui: ${message}\n`);
2560
+ writeCliWarning(message);
2563
2561
  if (process.platform !== "darwin")
2564
2562
  return;
2565
2563
  // The native TUI clears terminal output on attach, so keep the actionable
@@ -2569,6 +2567,12 @@ function codexAppNotice(message, notification) {
2569
2567
  `display notification "${notification}" with title "Yui"`
2570
2568
  ], { stdio: "ignore", timeout: 3_000 });
2571
2569
  }
2570
+ function writeCliWarning(message) {
2571
+ const safe = sanitizeFailureText(message, failureContext());
2572
+ process.stderr.write(`${jsonOutput
2573
+ ? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message: safe, details: { advisory: true } })
2574
+ : `Warning: ${safe}`}\n`);
2575
+ }
2572
2576
  function renderControllerResult(method, value) {
2573
2577
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2574
2578
  return JSON.stringify(value);
@@ -2595,10 +2599,23 @@ async function completionCommand(home, node) {
2595
2599
  if (prefix === undefined || separator !== 4) {
2596
2600
  throw usageError("Completion candidates usage: yui config completion candidates <prefix> -- <words...>");
2597
2601
  }
2602
+ let selection;
2598
2603
  const candidates = await resolveCompletionCandidates({
2599
2604
  current: prefix,
2600
2605
  words: args.slice(separator + 1),
2601
- ports: completionSelectionPorts(home)
2606
+ root: discoveryCommandTree(process.env),
2607
+ ports: { call: async (method, params) => {
2608
+ if (!selection) {
2609
+ assertCliHomeReleaseFence({
2610
+ home, packageRoot: fileURLToPath(new URL("../", import.meta.url)),
2611
+ entryPath: fileURLToPath(import.meta.url), args
2612
+ });
2613
+ await preflightManagedTaskControlPlane();
2614
+ assertTaskInvocationScope(args.slice(separator + 1), process.env);
2615
+ selection = completionSelectionPorts(home);
2616
+ }
2617
+ return selection.call(method, params);
2618
+ } }
2602
2619
  });
2603
2620
  process.stdout.write(candidates.length === 0 ? "" : `${candidates.join("\n")}\n`);
2604
2621
  return;
@@ -2771,13 +2788,12 @@ async function warmConfigurationMutationCatalog(commandArgs, store, catalogs) {
2771
2788
  async function liveHostObservationsForTaskCommand(args, store, home) {
2772
2789
  if (args[0] !== "task" || args[1] !== "role")
2773
2790
  return undefined;
2774
- const inspect = args[2] === "session" && args[3] === "inspect" && args.length === 6;
2775
- const status = args[2] === "status" && args.length === 5;
2776
- const list = args[2] === "list" && args.length === 4;
2777
- if (!inspect && !status && !list)
2791
+ const inspect = args[2] === "session" && args[3] === "inspect" && args.length >= 6;
2792
+ const status = args[2] === "status" && args.length >= 5;
2793
+ if (!inspect && !status)
2778
2794
  return undefined;
2779
2795
  const taskId = args[inspect ? 4 : 3];
2780
- const roles = list ? store.listRoles(taskId).map(role => role.name) : [args[inspect ? 5 : 4]];
2796
+ const roles = [args[inspect ? 5 : 4]];
2781
2797
  const entries = await Promise.all(roles.map(async (roleName) => [
2782
2798
  roleName, await readLiveHostObservation(store, home, taskId, roleName)
2783
2799
  ]));
@@ -2942,7 +2958,37 @@ function completionSelectionPorts(home) {
2942
2958
  return { call: () => [] };
2943
2959
  }
2944
2960
  const store = readableStore(home);
2945
- return selectionPorts(store, new AgentConfigurationCatalogService(home, { environment: process.env }));
2961
+ const ports = selectionPorts(store, new AgentConfigurationCatalogService(home, { environment: process.env }));
2962
+ if (process.env.YUI_SESSION_SCOPE !== "task")
2963
+ return ports;
2964
+ const taskId = process.env.YUI_TASK_ID;
2965
+ const readable = contextRecordReader(store, taskId, process.env);
2966
+ const families = {
2967
+ "task.list": "task", "project.list": "project-policy", "task.role.list": "role",
2968
+ "task.work.list": "work-item", "task.message.list": "task-message", "task.input.list": "input-request",
2969
+ "task.turn.list": "run", "task.decision.list": "task-decision", "task.milestone.list": "task-milestone",
2970
+ "task.event.list": "task-event", "task.change-set.list": "change-set",
2971
+ "task.integration.list": "integration-attempt"
2972
+ };
2973
+ return { call: async (method, params) => {
2974
+ if (params.taskId !== undefined && params.taskId !== taskId)
2975
+ return [];
2976
+ const family = families[method];
2977
+ if (!family)
2978
+ return []; // Completion never needs broader authority than Context.
2979
+ const scopedParams = method === "task.input.list" ? { ...params, taskId } : params;
2980
+ const values = await ports.call(method, scopedParams);
2981
+ if (!Array.isArray(values))
2982
+ return [];
2983
+ return values.filter(value => {
2984
+ const id = String(value.id ?? value.name ?? "");
2985
+ if (method === "task.list" && id !== taskId)
2986
+ return false;
2987
+ if (method === "project.list" && !store.getTask(taskId)?.projectBindings.some(binding => binding.projectId === id))
2988
+ return false;
2989
+ return readable(family, id);
2990
+ });
2991
+ } };
2946
2992
  }
2947
2993
  function emit(output, literal = false, data) {
2948
2994
  const normalized = literal ? output.trimEnd() : output.trimEnd();
@@ -2954,9 +3000,17 @@ function emit(output, literal = false, data) {
2954
3000
  }
2955
3001
  function emitControlFailure(message, code, details) {
2956
3002
  process.exitCode = 2;
3003
+ const context = failureContext();
3004
+ const safeMessage = sanitizeFailureText(message.trim(), context);
3005
+ const safeDetails = sanitizeFailureDetails({ value: details }, context).value;
2957
3006
  process.stdout.write(`${jsonOutput
2958
- ? JSON.stringify({ ok: false, code, message: message.trim(), details })
2959
- : message.trim()}\n`);
3007
+ ? JSON.stringify({ ok: false, code, message: safeMessage, details: safeDetails })
3008
+ : safeMessage}\n`);
3009
+ }
3010
+ /** Domain-owned failure reports retain their output envelope and effect receipts. */
3011
+ function emitFailureResult(output, data) {
3012
+ const context = failureContext();
3013
+ emit(sanitizeFailureText(output, context), false, sanitizeFailureDetails({ value: data }, context).value);
2960
3014
  }
2961
3015
  /**
2962
3016
  * The human line for a live steer, corrected to the *actual* live acceptance
@@ -3,7 +3,7 @@ import { resolveJobCaller } from "../task/taskAuthority.js";
3
3
  import { callFileTaskController } from "../controller/clientRuntime.js";
4
4
  export async function runCapabilityCommand(args, home, environment) {
5
5
  const [action, ...rest] = args;
6
- const usage = "yui capability search [query] --task <id> | commands --task <id> | panels --task <id> | describe <name> --task <id> [--provider <id>] [--version <version>] | call <name> --task <id> --input <json> [--provider <id>] [--version <version>] [--request-id <id>]";
6
+ const usage = "yui capability search [query] --task <id> | commands --task <id> | panels --task <id> | describe <name> --task <id> [--input <json>] [--provider <id>] [--version <version>] | call <name> --task <id> --input <json> [--provider <id>] [--version <version>] [--request-id <id>]";
7
7
  if (!["search", "commands", "panels", "describe", "call"].includes(action))
8
8
  throw usageError(usage);
9
9
  const surfaceList = action === "commands" || action === "panels";
@@ -29,12 +29,12 @@ export async function runCapabilityCommand(args, home, environment) {
29
29
  throw usageError(usage);
30
30
  if ((action === "search" || surfaceList) && [...flags.keys()].some((key) => key !== "--task"))
31
31
  throw usageError(usage);
32
- if (action === "describe" && (flags.has("--input") || flags.has("--request-id")))
32
+ if (action === "describe" && flags.has("--request-id"))
33
33
  throw usageError(usage);
34
34
  let input;
35
- if (action === "call") {
36
- if (!flags.has("--input"))
37
- throw usageError("Capability call requires --input <json>.", usage);
35
+ if (action === "call" && !flags.has("--input"))
36
+ throw usageError("Capability call requires --input <json>.", usage);
37
+ if (flags.has("--input")) {
38
38
  try {
39
39
  input = JSON.parse(flags.get("--input"));
40
40
  }
@@ -1,4 +1,5 @@
1
1
  import { usageError } from "../errors/cliError.js";
2
+ import { describeCliFailure } from "../errors/cliFailure.js";
2
3
  import { defaultTableWidth, renderTable } from "../output/table.js";
3
4
  import { DEFAULT_RECONCILIATION_INTERVAL_SECONDS } from "../config/yuiConfig.js";
4
5
  import { evaluateStorageHealth, UNSUPPORTED } from "../observability/runtimeIdentity.js";
@@ -280,7 +281,7 @@ export async function runInteractiveControllerCleanup(input) {
280
281
  catch (error) {
281
282
  failed.push({
282
283
  id: resource.id,
283
- message: error instanceof Error ? error.message : String(error)
284
+ message: describeCliFailure(error).message
284
285
  });
285
286
  }
286
287
  }