@zq-silk/yui 1.1.0 → 2.0.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 (67) hide show
  1. package/ARCHITECTURE.md +2 -1
  2. package/ARCHITECTURE.zh-CN.md +2 -1
  3. package/README.md +3 -1
  4. package/dist/cli/commandCatalog.js +66 -54
  5. package/dist/cli/managedDiagnostics.js +2 -1
  6. package/dist/cli/updatePorts.js +0 -5
  7. package/dist/cli.js +113 -107
  8. package/dist/commands/controllerCommands.js +2 -1
  9. package/dist/commands/globalRoleCommands.js +84 -43
  10. package/dist/commands/grantCommands.js +48 -8
  11. package/dist/commands/taskCommands.js +146 -196
  12. package/dist/commands/taskContextCommand.js +29 -6
  13. package/dist/commands/taskFactCommands.js +10 -69
  14. package/dist/commands/taskInputCommands.js +26 -35
  15. package/dist/commands/taskPublicationCommands.js +2 -35
  16. package/dist/commands/taskPublicationVerifyCommand.js +1 -2
  17. package/dist/commands/taskUpstreamCommands.js +2 -1
  18. package/dist/context/runContextPack.js +8 -29
  19. package/dist/context/sessionBootstrapManifest.js +5 -101
  20. package/dist/context/taskContext.js +98 -39
  21. package/dist/controller/clientRuntime.js +16 -8
  22. package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
  23. package/dist/controller/runtime.js +6 -0
  24. package/dist/core/controllerClient.js +46 -21
  25. package/dist/core/protocol.js +12 -7
  26. package/dist/doctor/doctor.js +6 -3
  27. package/dist/doctor/ptyProbe.js +74 -0
  28. package/dist/doctor/ptyProbeChild.js +90 -0
  29. package/dist/errors/cliError.js +4 -4
  30. package/dist/errors/cliFailure.js +213 -0
  31. package/dist/executor/fileRoleLaunchPlanner.js +1 -4
  32. package/dist/grant/capabilityGrant.js +13 -0
  33. package/dist/grant/taskAuthorization.js +127 -0
  34. package/dist/kernel/builtinCapabilities.js +60 -8
  35. package/dist/kernel/kernelPorts.js +2 -2
  36. package/dist/output/boundedRead.js +118 -0
  37. package/dist/release/releaseWorkflowEngine.js +8 -1
  38. package/dist/runtime/managedCaller.js +2 -2
  39. package/dist/runtime/runtimeCoherence.js +10 -8
  40. package/dist/storage/sqliteSchema.js +10 -0
  41. package/dist/storage/storageVersions.js +1 -1
  42. package/dist/storage/taskStore.js +3 -0
  43. package/dist/task/leaderArchive.js +111 -0
  44. package/dist/task/leaderArchiveAuthority.js +24 -0
  45. package/dist/tmux/commandExecutor.js +7 -5
  46. package/docs/architecture/README.md +1 -0
  47. package/docs/architecture/README.zh-CN.md +1 -0
  48. package/docs/cli-information-contract.md +107 -0
  49. package/docs/cli-information-contract.zh-CN.md +81 -0
  50. package/docs/managed-turn-and-session-runtime.md +9 -0
  51. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  52. package/docs/plugin-sdk.md +7 -3
  53. package/docs/plugin-sdk.zh-CN.md +5 -2
  54. package/docs/release-workflow.md +19 -9
  55. package/docs/release-workflow.zh-CN.md +14 -7
  56. package/docs/storage-baseline.md +5 -0
  57. package/docs/testing/verification-levels.md +12 -2
  58. package/docs/testing/verification-levels.zh-CN.md +8 -0
  59. package/i18n/README.zh-CN.md +3 -1
  60. package/package.json +3 -3
  61. package/skills/yui-leader/SKILL.md +8 -1
  62. package/skills/yui-leader/references/authorization.md +64 -0
  63. package/skills/yui-leader/references/execution.md +18 -4
  64. package/skills/yui-leader/references/task-plugins.md +4 -2
  65. package/skills/yui-operator/SKILL.md +9 -2
  66. package/skills/yui-runtime/SKILL.md +51 -9
  67. package/skills/yui-runtime/references/publication.md +3 -1
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/i
20
20
  import { routeInvocation } from "./cli/invocationRouter.js";
21
21
  import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
22
22
  import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
23
- import { requireManagedGlobalCaller } from "./runtime/managedCaller.js";
23
+ import { requireManagedGlobalCaller, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
24
24
  import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
25
25
  import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
26
26
  import { runUpdateCommand } from "./cli/updateCommand.js";
@@ -41,7 +41,7 @@ import { runProjectCommand } from "./commands/projectCommands.js";
41
41
  import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
42
42
  import { runResourcesCommand } from "./commands/resourcesCommands.js";
43
43
  import { parseSessionReconcileOptions, parseSessionStopOptions, runSessionReconcileCommand, runSessionStopCommand } from "./commands/sessionCommands.js";
44
- import { assertTaskDeliveryAuthority, taskLocalActor } from "./task/taskAuthority.js";
44
+ import { assertTaskDeliveryAuthority, taskLocalActor, resolveJobCaller } from "./task/taskAuthority.js";
45
45
  import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
46
46
  import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
47
47
  import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, parseTaskArchiveArguments, parseTaskCompletionRequest, planReplicatedWorkItemLanes, preflightTaskCompletion, preserveReviewRoundWorkspace, previewTaskRoleAgentConfigurationMutation, requireWorkItemAssignee, runTaskCommand, validateTaskArchiveRequest } from "./commands/taskCommands.js";
@@ -58,7 +58,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
58
58
  import { FileCompletionManager } from "./completion/fileCompletionManager.js";
59
59
  import { CONFIG_DOMAINS } from "./config/configCatalog.js";
60
60
  import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
61
- import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
61
+ import { readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
62
62
  import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
63
63
  import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
64
64
  import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
@@ -70,6 +70,7 @@ import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliat
70
70
  import { callController, ControllerClientError } from "./core/controllerClient.js";
71
71
  import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
72
72
  import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
73
+ import { describeCliFailure, renderCliFailure, sanitizeFailureText, sanitizeFailureDetails } from "./errors/cliFailure.js";
73
74
  import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
74
75
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
75
76
  import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
@@ -82,7 +83,7 @@ import { parseTaskAgentCapabilityQuery } from "./commands/taskAgentCapabilities.
82
83
  import { capabilityReaderIdentity } from "./controller/agentCapabilities.js";
83
84
  import { formatTimestamp } from "./output/timePresentation.js";
84
85
  import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
85
- import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
86
+ import { assertCliHomeReleaseFence } from "./release/cliHomeReleaseFence.js";
86
87
  import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
87
88
  import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
88
89
  import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
@@ -102,7 +103,6 @@ import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/ma
102
103
  import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
103
104
  import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
104
105
  import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
105
- import { SqliteSchemaError } from "./storage/sqliteSchema.js";
106
106
  import { inspectStorageSchema } from "./storage/storageSchema.js";
107
107
  import { resolveYuiHome } from "./storage/taskStore.js";
108
108
  import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
@@ -123,47 +123,37 @@ const taskFinalReviewInvocation = extractTaskFinalReviewRequest(process.argv.sli
123
123
  const rawArgs = [...taskFinalReviewInvocation.args];
124
124
  const jsonOutput = rawArgs.includes("--json");
125
125
  const args = normalizeAliases(jsonOutput ? rawArgs.filter((argument) => argument !== "--json") : rawArgs);
126
+ let invocationHome;
126
127
  void main().catch((error) => {
127
128
  if (error instanceof CleanupInspectionError) {
128
129
  error = cleanupCliError(error, error.checks[0]?.resource ?? "workspace");
129
130
  }
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;
131
+ const failure = describeCliFailure(error, failureContext());
132
+ process.stderr.write(`${renderCliFailure(failure, jsonOutput)}\n`);
133
+ process.exitCode = failure.exitCode;
143
134
  });
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
- }
135
+ function failureContext() {
136
+ const route = routeInvocation(args);
137
+ const node = "node" in route ? route.node : route.helpNode;
138
+ const operands = args.slice(node.path.length - 1);
139
+ const publicValues = new Set([
140
+ ...Object.values(node.argumentValues).flat(),
141
+ ...Object.values(node.optionValues).flat()
142
+ ]);
143
+ 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+)?$/;
144
+ return {
145
+ operation: node.path.join(" "),
146
+ targets: operands.filter(value => recordId.test(value)),
147
+ // Do not replay argv: operands include Message bodies, config values and credentials.
148
+ privateValues: operands.filter(value => !/^--[a-z][a-z-]*$/.test(value) && !recordId.test(value) && !publicValues.has(value)),
149
+ home: invocationHome ?? process.env.YUI_HOME ?? "(default Home unresolved)",
150
+ cli: fileURLToPath(import.meta.url),
151
+ environment: process.env
152
+ };
164
153
  }
165
154
  export async function main() {
166
155
  const home = resolveYuiHome(process.env);
156
+ invocationHome = home;
167
157
  const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
168
158
  const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
169
159
  || process.env.YUI_SESSION_SCOPE === "global";
@@ -188,7 +178,7 @@ export async function main() {
188
178
  stdio: "inherit"
189
179
  });
190
180
  if (delegated.error !== undefined) {
191
- throw runtimeError(`Target release activation driver could not start: ${delegated.error.message}`);
181
+ throw runtimeError("Target release activation driver could not start.", { cause: delegated.error });
192
182
  }
193
183
  if (delegated.signal !== null) {
194
184
  throw runtimeError(`Target release activation driver stopped by signal ${delegated.signal}.`);
@@ -240,7 +230,10 @@ export async function main() {
240
230
  const subcommand = args[1];
241
231
  if (subcommand === "install" && args.length === 3) {
242
232
  const result = runReleaseInstall(home, args[2]);
243
- emit(renderReleaseInstallResult(result), false, result);
233
+ if (result.outcome === "aborted")
234
+ emitFailureResult(renderReleaseInstallResult(result), result);
235
+ else
236
+ emit(renderReleaseInstallResult(result), false, result);
244
237
  if (result.outcome === "aborted")
245
238
  process.exitCode = 5;
246
239
  return;
@@ -259,7 +252,11 @@ export async function main() {
259
252
  + "(or activate the active release when exactly one is installed).");
260
253
  }
261
254
  const result = await runReleaseActivate(home, releaseId);
262
- emit(renderReleaseActivateResult(result), false, result);
255
+ if (result.outcome === "aborted" || result.outcome === "dual-owner") {
256
+ emitFailureResult(renderReleaseActivateResult(result), result);
257
+ }
258
+ else
259
+ emit(renderReleaseActivateResult(result), false, result);
263
260
  if (result.outcome === "aborted" || result.outcome === "dual-owner") {
264
261
  process.exitCode = 5;
265
262
  }
@@ -356,7 +353,7 @@ export async function main() {
356
353
  codex = await prepareLocalCodexStartup(startupStore, home, process.env);
357
354
  }
358
355
  catch (error) {
359
- throw runtimeError(`Controller is ready, but Codex shared startup failed: ${error instanceof Error ? error.message : String(error)}`);
356
+ throw runtimeError("Controller is ready, but Codex shared startup failed.", { cause: error });
360
357
  }
361
358
  const lines = ["Yui Controller is ready."];
362
359
  if (codex.daemons.length === 0)
@@ -398,20 +395,13 @@ export async function main() {
398
395
  if (args[0] === "upgrade") {
399
396
  const result = await runUpgradeCommand(args.slice(1), home);
400
397
  process.exitCode = result.exitCode;
401
- emit(result.output, false, result.data);
398
+ if (result.exitCode !== 0)
399
+ emitFailureResult(result.output, result.data);
400
+ else
401
+ emit(result.output, false, result.data);
402
402
  return;
403
403
  }
404
404
  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
405
  if (args[1] === "agent-host" && args.length === 3) {
416
406
  process.exitCode = await runAgentHost({
417
407
  home,
@@ -483,17 +473,7 @@ export async function main() {
483
473
  catch (error) {
484
474
  if (!(error instanceof ControllerClientError))
485
475
  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
- }
476
+ process.stderr.write(`${renderCliFailure(describeCliFailure(error, failureContext()), jsonOutput, error.code)}\n`);
497
477
  process.exitCode = 5;
498
478
  }
499
479
  return;
@@ -517,17 +497,7 @@ export async function main() {
517
497
  // unknown-active block.
518
498
  if (!(error instanceof ControllerClientError))
519
499
  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
- }
500
+ process.stderr.write(`${renderCliFailure(describeCliFailure(error, failureContext()), jsonOutput, error.code)}\n`);
531
501
  process.exitCode = 5;
532
502
  }
533
503
  return;
@@ -674,7 +644,7 @@ export async function main() {
674
644
  yuiHome: home,
675
645
  historyLimit: resolveTmuxHistoryLimit(store.getConfig().tmuxHistoryLimit),
676
646
  terminalInput: process.stdin,
677
- onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
647
+ onWarning: (message) => writeCliWarning(message)
678
648
  });
679
649
  const telemetry = openSchedulerTelemetry(home, store.getConfig());
680
650
  try {
@@ -684,8 +654,11 @@ export async function main() {
684
654
  const runtime = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
685
655
  environment: process.env,
686
656
  onError: (error) => {
687
- const message = error instanceof Error ? error.message : String(error);
688
- process.stderr.write(`Controller runtime error: ${message}\n`);
657
+ // Advisory async failure: preserve the foreground command's exit status.
658
+ const failure = describeCliFailure(error, failureContext());
659
+ process.stderr.write(`${renderCliFailure({
660
+ ...failure, details: { ...failure.details, advisory: true }
661
+ }, jsonOutput)}\n`);
689
662
  }
690
663
  });
691
664
  const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
@@ -845,6 +818,10 @@ export async function main() {
845
818
  ? [resolved[1], ...resolved.slice(2)]
846
819
  : ["session", resolved[1] ?? "", ...resolved.slice(2)];
847
820
  const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
821
+ if (typeof result !== "string" && result.kind === "output") {
822
+ emit(result.output, false, result.data);
823
+ return;
824
+ }
848
825
  if (typeof result === "string") {
849
826
  emit(result);
850
827
  return;
@@ -893,6 +870,14 @@ export async function main() {
893
870
  emit(result);
894
871
  return;
895
872
  }
873
+ if (result.kind === "output") {
874
+ if (globalInputFailure !== undefined) {
875
+ emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
876
+ }
877
+ else
878
+ emit(result.output, false, result.data);
879
+ return;
880
+ }
896
881
  if (result.kind === "input-steer") {
897
882
  // Core already persisted the durable Global Message and proved target +
898
883
  // capability + writer fence from the Global Role's own Session set. This
@@ -1101,8 +1086,7 @@ export async function main() {
1101
1086
  finalizeStoppedTaskExecution(result.taskId, store);
1102
1087
  }
1103
1088
  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}`);
1089
+ throw runtimeError("Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed.", { cause: error });
1106
1090
  }
1107
1091
  emit(result.output, false, result);
1108
1092
  return;
@@ -1317,6 +1301,19 @@ export async function main() {
1317
1301
  let archiveRemoteDeliveryProof;
1318
1302
  let archiveTaskReviewCandidate;
1319
1303
  if (resolved[1] === "archive") {
1304
+ const request = parseTaskArchiveArguments(resolved.slice(2));
1305
+ if (request.sourceMessage !== undefined) {
1306
+ if (request.force || request.disposition !== "integrated") {
1307
+ throw usageError("Leader ordinary archive does not authorize --force or --abandon.");
1308
+ }
1309
+ const result = await callController(home, "capability.call", {
1310
+ taskId: request.taskId, caller: resolveJobCaller(process.env, request.taskId),
1311
+ request: { name: "task.archive", requestId: request.requestId,
1312
+ input: { taskId: request.taskId, sourceMessage: request.sourceMessage, purpose: request.purpose } }
1313
+ });
1314
+ emit("Controller archive result; inspect task events if this Session ends before the receipt.\n", false, result);
1315
+ return;
1316
+ }
1320
1317
  const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1321
1318
  runtime,
1322
1319
  environment: process.env,
@@ -1497,7 +1494,7 @@ export async function main() {
1497
1494
  })
1498
1495
  });
1499
1496
  for (const warning of completionWarnings) {
1500
- process.stderr.write(`Warning: ${warning}\n`);
1497
+ writeCliWarning(warning);
1501
1498
  }
1502
1499
  // Refresh observes remote objects only. Choosing and executing an
1503
1500
  // upstream Integration belongs to its explicit command, never completion.
@@ -2035,7 +2032,7 @@ async function preflightManagedGlobalControlPlane() {
2035
2032
  const expectedRoleKind = process.env.YUI_ROLE === "operator" ? "operator" : "global";
2036
2033
  if (manifest.owner.scope !== "global"
2037
2034
  || manifest.roleKind !== expectedRoleKind) {
2038
- throw new Error("Managed global invocation does not match its Session Manifest.");
2035
+ throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
2039
2036
  }
2040
2037
  if (expectedRoleKind === "operator" && ["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
2041
2038
  // These commands inspect/adopt the storage contract themselves. Requiring
@@ -2059,28 +2056,24 @@ async function preflightManagedGlobalControlPlane() {
2059
2056
  }
2060
2057
  return { contract: undefined, verifiedStore };
2061
2058
  }
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
2059
  function assertManagedSessionManifest(home, scope) {
2073
2060
  const manifestPath = process.env.YUI_SESSION_MANIFEST;
2074
2061
  if (manifestPath === undefined) {
2075
- throw new Error("Managed control-plane invocation requires its Session Manifest.");
2062
+ throw new ManagedRuntimeDriftError("Managed control-plane invocation requires its Session Manifest.");
2063
+ }
2064
+ let manifest;
2065
+ try {
2066
+ manifest = readSessionBootstrapManifest(manifestPath);
2067
+ }
2068
+ catch (error) {
2069
+ throw new ManagedRuntimeDriftError("Managed Session Manifest could not be verified.", { cause: error });
2076
2070
  }
2077
- const manifest = readSessionBootstrapManifest(manifestPath);
2078
2071
  if (manifest.owner.scope !== scope) {
2079
- throw new Error("Managed invocation scope does not match its Session Manifest.");
2072
+ throw new ManagedRuntimeDriftError("Managed invocation scope does not match its Session Manifest.");
2080
2073
  }
2081
2074
  if (scope === "task" && (manifest.owner.scope !== "task"
2082
2075
  || manifest.owner.taskId !== process.env.YUI_TASK_ID)) {
2083
- throw new Error("Managed Task invocation does not match its Session Manifest owner.");
2076
+ throw new ManagedRuntimeDriftError("Managed Task invocation does not match its Session Manifest owner.");
2084
2077
  }
2085
2078
  const expectedPath = resolve(home, "runtime", "session-manifests", `${manifest.digest}.json`);
2086
2079
  // YUI_HOME is canonicalized (its macOS /var -> /private/var prefix is a
@@ -2093,7 +2086,7 @@ function assertManagedSessionManifest(home, scope) {
2093
2086
  canonicalManifestPath = resolve(manifestPath);
2094
2087
  }
2095
2088
  if (canonicalManifestPath !== expectedPath) {
2096
- throw new Error("Managed Session Manifest path is outside this YUI_HOME.");
2089
+ throw new ManagedRuntimeDriftError("Managed Session Manifest path is outside this YUI_HOME.");
2097
2090
  }
2098
2091
  return manifest;
2099
2092
  }
@@ -2104,7 +2097,7 @@ function cleanupCliError(error, fallbackResource) {
2104
2097
  if (error instanceof WorkspaceCleanupBlockedError) {
2105
2098
  return usageError(error.message, undefined, cleanupBlockedDetails(error.reason, error.resource, error.retryable));
2106
2099
  }
2107
- return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true));
2100
+ return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true), { cause: error });
2108
2101
  }
2109
2102
  function cleanupBlockedDetails(reason, resource, retryable, checks) {
2110
2103
  return {
@@ -2540,7 +2533,7 @@ async function prepareCodexForCurrentRoles(store, home, targetAgentId) {
2540
2533
  }
2541
2534
  }
2542
2535
  catch (error) {
2543
- const detail = error instanceof Error ? error.message : String(error);
2536
+ const detail = describeCliFailure(error, failureContext()).message;
2544
2537
  const permissionDenied = isCodexInspectionPermissionError(error);
2545
2538
  codexAppNotice(`${permissionDenied
2546
2539
  ? "Codex sharing could not be prepared or verified from this process"
@@ -2559,7 +2552,7 @@ function assertHomeStartupAuthority(store) {
2559
2552
  requireManagedGlobalCaller(store, process.env);
2560
2553
  }
2561
2554
  function codexAppNotice(message, notification) {
2562
- process.stderr.write(`\nYui: ${message}\n`);
2555
+ writeCliWarning(message);
2563
2556
  if (process.platform !== "darwin")
2564
2557
  return;
2565
2558
  // The native TUI clears terminal output on attach, so keep the actionable
@@ -2569,6 +2562,12 @@ function codexAppNotice(message, notification) {
2569
2562
  `display notification "${notification}" with title "Yui"`
2570
2563
  ], { stdio: "ignore", timeout: 3_000 });
2571
2564
  }
2565
+ function writeCliWarning(message) {
2566
+ const safe = sanitizeFailureText(message, failureContext());
2567
+ process.stderr.write(`${jsonOutput
2568
+ ? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message: safe, details: { advisory: true } })
2569
+ : `Warning: ${safe}`}\n`);
2570
+ }
2572
2571
  function renderControllerResult(method, value) {
2573
2572
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2574
2573
  return JSON.stringify(value);
@@ -2771,13 +2770,12 @@ async function warmConfigurationMutationCatalog(commandArgs, store, catalogs) {
2771
2770
  async function liveHostObservationsForTaskCommand(args, store, home) {
2772
2771
  if (args[0] !== "task" || args[1] !== "role")
2773
2772
  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)
2773
+ const inspect = args[2] === "session" && args[3] === "inspect" && args.length >= 6;
2774
+ const status = args[2] === "status" && args.length >= 5;
2775
+ if (!inspect && !status)
2778
2776
  return undefined;
2779
2777
  const taskId = args[inspect ? 4 : 3];
2780
- const roles = list ? store.listRoles(taskId).map(role => role.name) : [args[inspect ? 5 : 4]];
2778
+ const roles = [args[inspect ? 5 : 4]];
2781
2779
  const entries = await Promise.all(roles.map(async (roleName) => [
2782
2780
  roleName, await readLiveHostObservation(store, home, taskId, roleName)
2783
2781
  ]));
@@ -2954,9 +2952,17 @@ function emit(output, literal = false, data) {
2954
2952
  }
2955
2953
  function emitControlFailure(message, code, details) {
2956
2954
  process.exitCode = 2;
2955
+ const context = failureContext();
2956
+ const safeMessage = sanitizeFailureText(message.trim(), context);
2957
+ const safeDetails = sanitizeFailureDetails({ value: details }, context).value;
2957
2958
  process.stdout.write(`${jsonOutput
2958
- ? JSON.stringify({ ok: false, code, message: message.trim(), details })
2959
- : message.trim()}\n`);
2959
+ ? JSON.stringify({ ok: false, code, message: safeMessage, details: safeDetails })
2960
+ : safeMessage}\n`);
2961
+ }
2962
+ /** Domain-owned failure reports retain their output envelope and effect receipts. */
2963
+ function emitFailureResult(output, data) {
2964
+ const context = failureContext();
2965
+ emit(sanitizeFailureText(output, context), false, sanitizeFailureDetails({ value: data }, context).value);
2960
2966
  }
2961
2967
  /**
2962
2968
  * The human line for a live steer, corrected to the *actual* live acceptance
@@ -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
  }