@zq-silk/yui 0.16.0 → 0.16.2

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 (114) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +3 -0
  4. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  5. package/dist/agentRun/agentRun.js +7 -17
  6. package/dist/cli/agentConfigurationPicker.js +24 -21
  7. package/dist/cli/commandCatalog.js +18 -10
  8. package/dist/cli/roleWizard.js +41 -30
  9. package/dist/cli/updateCommand.js +35 -13
  10. package/dist/cli/updateOrchestrator.js +73 -33
  11. package/dist/cli/updatePorts.js +51 -20
  12. package/dist/cli.js +35 -26
  13. package/dist/commands/controllerCommands.js +1 -1
  14. package/dist/commands/executionAuditCommands.js +2 -2
  15. package/dist/commands/projectCommands.js +1 -1
  16. package/dist/commands/releaseCommands.js +9 -38
  17. package/dist/commands/taskCommands.js +84 -81
  18. package/dist/commands/taskCompletionGate.js +0 -117
  19. package/dist/commands/taskRoleRuntimeStatus.js +1 -1
  20. package/dist/commands/taskUpstreamCommands.js +93 -54
  21. package/dist/context/runContextPack.js +30 -54
  22. package/dist/context/runInputContract.js +9 -0
  23. package/dist/context/sessionBootstrapManifest.js +11 -19
  24. package/dist/controller/agentHostObservation.js +4 -2
  25. package/dist/controller/controller.js +8 -6
  26. package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
  27. package/dist/controller/globalInputDelivery.js +28 -7
  28. package/dist/controller/globalRuntimeAttention.js +34 -0
  29. package/dist/controller/operatorNotification.js +31 -0
  30. package/dist/controller/providerRetryAdmission.js +3 -1
  31. package/dist/controller/providerRetryDelivery.js +10 -3
  32. package/dist/controller/runtime.js +1 -27
  33. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  34. package/dist/controller/structuredProviderObservation.js +27 -0
  35. package/dist/controller/updateReconciliation.js +72 -20
  36. package/dist/doctor/doctor.js +2 -1
  37. package/dist/executor/agentAdapter.js +28 -96
  38. package/dist/executor/agentConfigurationCatalog.js +13 -68
  39. package/dist/executor/agentConfigurationFields.js +120 -0
  40. package/dist/executor/agentConfigurationProbe.js +55 -80
  41. package/dist/executor/agentExecutor.js +5 -2
  42. package/dist/executor/executorRegistry.js +0 -9
  43. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  44. package/dist/interaction/operatorPresentation.js +2 -1
  45. package/dist/message/messageContinuation.js +8 -9
  46. package/dist/observability/executionAudit.js +1 -10
  47. package/dist/observability/runtimeIdentity.js +0 -23
  48. package/dist/output/agentConfigurationPresentation.js +4 -2
  49. package/dist/release/releaseHandover.js +2 -2
  50. package/dist/release/releaseIdempotencyStore.js +0 -23
  51. package/dist/release/releaseWorkflowPorts.js +8 -4
  52. package/dist/repository/gitWorkspace.js +66 -19
  53. package/dist/repository/taskWorkspacePreparer.js +75 -68
  54. package/dist/resources/resourceRegistry.js +90 -44
  55. package/dist/resources/sqliteResourceRegistry.js +3 -5
  56. package/dist/runtime/acpSession.js +21 -5
  57. package/dist/runtime/agentEndpoint.js +4 -1
  58. package/dist/runtime/agentHost.js +22 -62
  59. package/dist/runtime/agentHostCleanup.js +85 -0
  60. package/dist/runtime/agentHostProtocol.js +1 -1
  61. package/dist/runtime/builtinAgentDrivers.js +46 -119
  62. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  63. package/dist/runtime/codexAppServerRuntime.js +55 -89
  64. package/dist/runtime/jsonLineChannel.js +35 -7
  65. package/dist/runtime/providerContinuationReconciliationService.js +42 -3
  66. package/dist/runtime/providerRetry.js +15 -0
  67. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  68. package/dist/runtime/structuredProviderHost.js +106 -81
  69. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  70. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  71. package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
  72. package/dist/task/nextAction.js +33 -23
  73. package/dist/task/task.js +2 -1
  74. package/dist/web/assets/assetManifest.js +2 -0
  75. package/dist/web/assets/client/app.js +72 -3
  76. package/dist/web/assets/client/components.js +2 -1
  77. package/dist/web/assets/client/i18n.js +2 -0
  78. package/dist/web/assets/client/taskSummary.js +345 -0
  79. package/dist/web/assets/client/taskSurface.js +44 -26
  80. package/dist/web/assets/client/view.js +33 -0
  81. package/dist/web/assets/shell.js +1 -0
  82. package/dist/web/assets/styles/cards.js +21 -0
  83. package/dist/web/webServer.js +39 -5
  84. package/dist/web/webSessions.js +165 -0
  85. package/dist/web/webSnapshot.js +23 -0
  86. package/dist/web/webTaskSurface.js +23 -0
  87. package/docs/managed-turn-and-session-runtime.md +31 -0
  88. package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
  89. package/docs/observability/README.md +47 -0
  90. package/docs/observability/README.zh-CN.md +37 -0
  91. package/docs/project-refresh.md +9 -0
  92. package/docs/project-refresh.zh-CN.md +8 -0
  93. package/docs/provider-retry.md +35 -0
  94. package/docs/release-workflow.md +139 -20
  95. package/docs/release-workflow.zh-CN.md +115 -14
  96. package/docs/roles-and-configuration.md +30 -0
  97. package/docs/roles-and-configuration.zh-CN.md +20 -0
  98. package/docs/task-delivery.md +109 -0
  99. package/docs/task-delivery.zh-CN.md +80 -0
  100. package/docs/task-discovery.md +9 -0
  101. package/docs/task-discovery.zh-CN.md +6 -0
  102. package/docs/testing/verification-levels.md +33 -12
  103. package/docs/testing/verification-levels.zh-CN.md +28 -8
  104. package/i18n/README.zh-CN.md +2 -0
  105. package/package.json +1 -1
  106. package/skills/yui-leader/SKILL.md +11 -0
  107. package/skills/yui-leader/references/execution.md +22 -0
  108. package/skills/yui-leader/references/planning.md +6 -2
  109. package/skills/yui-operator/SKILL.md +45 -13
  110. package/skills/yui-operator/references/task-delivery.md +99 -0
  111. package/skills/yui-runtime/SKILL.md +5 -3
  112. package/skills/yui-runtime/references/publication.md +56 -5
  113. package/skills/yui-runtime/references/recovery.md +12 -2
  114. package/dist/runtime/agentHostCompatibility.js +0 -127
@@ -9,6 +9,7 @@ import { codexClientInitialization } from "../runtime/codexAppServerRuntime.js";
9
9
  import { acpInitializeRequest, readAcpInitializeResult } from "../runtime/acpProtocol.js";
10
10
  import { handshakeObservationFrom } from "../runtime/agentRunConfiguration.js";
11
11
  import { YUI_VERSION } from "../version.js";
12
+ import { configurationFieldsFromHelp, configurationFieldWarnings, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
12
13
  /**
13
14
  * Codex and Claude Code are configured by flags and their own files; neither
14
15
  * plan exchanges capabilities on connect. Saying so explicitly keeps a caller
@@ -20,8 +21,6 @@ const NO_HANDSHAKE = Object.freeze({
20
21
  });
21
22
  const MAX_OUTPUT_BYTES = 1024 * 1024;
22
23
  const PROCESS_TERMINATION_GRACE_MS = 100;
23
- const CODEX_SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
24
- const CODEX_APPROVALS = ["untrusted", "on-request", "never"];
25
24
  const ACP_PROBE_REQUEST_ID = 1;
26
25
  export async function discoverCodexConfiguration(input) {
27
26
  const environment = configuredAgentLaunchEnvironment(input.agent, input.environment);
@@ -43,17 +42,35 @@ export async function discoverCodexConfiguration(input) {
43
42
  client.request("configRequirements/read", {}),
44
43
  client.request("modelProvider/capabilities/read", {})
45
44
  ]);
46
- const requirements = object(requirementsResult)?.requirements;
47
- const requirementRecord = object(requirements);
45
+ const requirements = requiredObject(requirementsResult, "Codex configuration requirements response").requirements;
46
+ const requirementRecord = requirements === null ? undefined
47
+ : requiredObject(requirements, "Codex configuration requirements");
48
48
  const allowedSandboxes = optionalStrings(requirementRecord?.allowedSandboxModes);
49
- const allowedApprovals = optionalStrings(requirementRecord?.allowedApprovalPolicies);
49
+ const allowedApprovals = optionalApprovalChoices(requirementRecord?.allowedApprovalPolicies);
50
50
  const allowedWebSearchModes = optionalStrings(requirementRecord?.allowedWebSearchModes);
51
- const capabilityRecord = object(providerCapabilities);
52
- const webSearch = capabilityRecord?.webSearch === true
51
+ const capabilityRecord = requiredObject(providerCapabilities, "Codex Provider capabilities");
52
+ if (typeof capabilityRecord.webSearch !== "boolean") {
53
+ throw new Error("Codex Provider capabilities did not report webSearch.");
54
+ }
55
+ const webSearch = capabilityRecord.webSearch
53
56
  && (allowedWebSearchModes === undefined || allowedWebSearchModes.includes("live"));
54
- const bypass = configurationFlagAvailable(help, "--dangerously-bypass-approvals-and-sandbox");
55
- const sandboxChoices = intersectChoices(configurationHelpChoices(help, "--sandbox", CODEX_SANDBOXES), allowedSandboxes);
56
- const approvalChoices = intersectChoices(configurationHelpChoices(help, "--ask-for-approval", CODEX_APPROVALS), allowedApprovals);
57
+ const fields = configurationFieldsFromHelp("codex", help).map(candidate => {
58
+ const allowed = candidate.key === "permission.sandbox" ? allowedSandboxes
59
+ : candidate.key === "permission.approval" ? allowedApprovals : undefined;
60
+ if (allowed !== undefined) {
61
+ const choices = candidate.choices.filter(choice => allowed.includes(choice.value));
62
+ return {
63
+ ...candidate, choices, available: choices.length > 0,
64
+ reason: `${candidate.reason} Native configuration requirements applied.${choices.length === 0 ? " No usable values were confirmed." : ""}`
65
+ };
66
+ }
67
+ if (candidate.key === "search")
68
+ return field("search", webSearch ? [choice("true")] : [], false, webSearch, webSearch ? "Native Provider capability and configuration requirements permit live web search."
69
+ : "Live web search is unavailable or disallowed.");
70
+ if (candidate.key === "profile")
71
+ return codexProfileField(environment);
72
+ return candidate;
73
+ });
57
74
  return {
58
75
  schemaVersion: 1,
59
76
  agentId: input.agent.id,
@@ -61,21 +78,8 @@ export async function discoverCodexConfiguration(input) {
61
78
  ...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
62
79
  handshake: NO_HANDSHAKE,
63
80
  models,
64
- fields: [
65
- field("model", [], true),
66
- field("effort", [], true),
67
- field("permission.strategy", [
68
- choice("default"),
69
- ...(bypass ? [choice("bypass")] : []),
70
- choice("configured")
71
- ], false, true, bypass ? undefined : "Codex bypass strategy is unavailable."),
72
- field("permission.sandbox", sandboxChoices.map(choice), false),
73
- field("permission.approval", approvalChoices.map(choice), false),
74
- field("search", webSearch ? [choice("true")] : [], false, webSearch, webSearch ? undefined : "Live web search is unavailable or disallowed."),
75
- field("profile", codexProfileChoices(environment).map(choice), true),
76
- field("additionalDirectories", [], true)
77
- ],
78
- warnings: client.warnings()
81
+ fields,
82
+ warnings: [...client.warnings(), ...configurationFieldWarnings(fields)]
79
83
  };
80
84
  }
81
85
  finally {
@@ -105,9 +109,7 @@ export async function discoverClaudeConfiguration(input) {
105
109
  ]);
106
110
  const initialized = object(initialization);
107
111
  const models = array(initialized?.models, "Claude model catalog").map(claudeModel);
108
- const permissionModes = configurationHelpChoices(help, "--permission-mode", []);
109
- const bypass = configurationFlagAvailable(help, "--dangerously-skip-permissions");
110
- const settingsSources = configurationHelpChoices(help, "--setting-sources", ["user", "project", "local"]);
112
+ const fields = configurationFieldsFromHelp("claude", help);
111
113
  return {
112
114
  schemaVersion: 1,
113
115
  agentId: input.agent.id,
@@ -115,22 +117,8 @@ export async function discoverClaudeConfiguration(input) {
115
117
  ...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
116
118
  handshake: NO_HANDSHAKE,
117
119
  models,
118
- fields: [
119
- field("model", [], true),
120
- field("effort", [], true),
121
- field("permission.strategy", [
122
- choice("default"),
123
- ...(bypass ? [choice("bypass")] : []),
124
- choice("configured")
125
- ], false, true, bypass ? undefined : "Claude bypass strategy is unavailable."),
126
- field("permission.mode", permissionModes.map(choice), true),
127
- field("permission.allowedTools", [], true),
128
- field("permission.disallowedTools", [], true),
129
- field("settingsSources", settingsSources.map(choice), false),
130
- field("settingsFile", [], true),
131
- field("additionalDirectories", [], true)
132
- ],
133
- warnings: []
120
+ fields,
121
+ warnings: configurationFieldWarnings(fields)
134
122
  };
135
123
  }
136
124
  async function listCodexModels(client) {
@@ -283,7 +271,7 @@ async function requestClaudeInitialization(command, args, cwd, environment, sign
283
271
  export async function discoverAcpConfiguration(input) {
284
272
  const environment = configuredAgentLaunchEnvironment(input.agent, input.environment);
285
273
  const negotiated = await requestAcpInitialization(input.agent.command, [...input.agent.baseArgs], input.cwd, environment, input.signal);
286
- const warnings = [];
274
+ const warnings = [STATIC_CONFIGURATION_NOTICE];
287
275
  if (!negotiated.capabilities.loadSession) {
288
276
  warnings.push("This ACP Agent does not support `session/load`, so a managed Yui Turn "
289
277
  + "cannot survive a Provider restart.");
@@ -331,7 +319,7 @@ export async function discoverAcpConfiguration(input) {
331
319
  field("effort", [], true, true, "ACP selects reasoning effort with session/set_config_option, under the "
332
320
  + "`thought_level` category. As with the model, the accepted values are "
333
321
  + "negotiated per Session and verified at launch rather than listed here."),
334
- field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false, true, "`default` sends no mode, so the Agent's own default stands. `configured` "
322
+ field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false, true, "Static Yui strategies: `default` sends no mode, so the Agent's own default stands. `configured` "
335
323
  + "selects one exact mode the Agent offers. `bypass` applies the mode that "
336
324
  + "grants unattended action, and only for an execution component Yui can "
337
325
  + "identify — it is never guessed from a mode's name. None of these change "
@@ -573,47 +561,34 @@ function terminateProcess(child) {
573
561
  child.once("exit", () => { clearTimeout(force); });
574
562
  child.kill("SIGTERM");
575
563
  }
576
- export function codexProfileChoices(environment) {
564
+ function codexProfileField(environment) {
577
565
  const root = environment.CODEX_HOME
578
566
  ?? join(environment.HOME ?? homedir(), ".codex");
579
567
  try {
580
568
  const profiles = object(parse(readFileSync(join(root, "config.toml"), "utf8")).profiles);
581
- return Object.keys(profiles ?? {}).filter((name) => name.trim().length > 0 && !name.includes("\0")).sort();
582
- }
583
- catch {
584
- return [];
569
+ const names = Object.keys(profiles ?? {}).filter((name) => name.trim().length > 0 && !name.includes("\0")).sort();
570
+ return field("profile", names.map(choice), true, undefined, "Local config.toml profile names, not a Provider enumeration; custom names remain explicit input.");
585
571
  }
586
- }
587
- export function configurationHelpChoices(help, flag, fallback) {
588
- const lines = help.replace(/\r\n/g, "\n").split("\n");
589
- const start = lines.findIndex((line) => line.includes(flag));
590
- if (start < 0)
591
- return [...fallback];
592
- let end = start + 1;
593
- while (end < lines.length && !/^\s{2,}(?:-[A-Za-z](?:,\s*)?|--)[\w-]/.test(lines[end] ?? "")) {
594
- end += 1;
572
+ catch (error) {
573
+ const absent = error instanceof Error && "code" in error && error.code === "ENOENT";
574
+ return field("profile", [], true, absent ? undefined : false, absent
575
+ ? "No local config.toml; no profile names observed. Custom names remain explicit input."
576
+ : "Local config.toml could not be read or parsed; profile enumeration unavailable.");
595
577
  }
596
- const section = lines.slice(start, end).join("\n");
597
- const inline = /(?:\[possible values:\s*([^\]]+)\]|\(choices:\s*([^)]*)\))/i
598
- .exec(section);
599
- const declared = inline?.[1] ?? inline?.[2];
600
- const bulletValues = declared === undefined && /Possible values:/i.test(section)
601
- ? [...section.matchAll(/^\s*-\s*([\w.+-]+)\s*:/gm)].map((match) => match[1] ?? "")
602
- : [];
603
- const parsed = [...new Set((declared ?? bulletValues.join(","))
604
- .replace(/["']/g, "")
605
- .split(",")
606
- .map((value) => value.trim().replace(/\.$/, ""))
607
- .filter((value) => /^[\w.+-]+$/.test(value)))];
608
- return parsed.length === 0 ? [...fallback] : parsed;
609
- }
610
- function configurationFlagAvailable(help, flag) {
611
- return help.replace(/\r\n/g, "\n").split("\n").some((line) => line.includes(flag));
612
578
  }
613
- function intersectChoices(choices, allowed) {
614
- return allowed === undefined
615
- ? [...choices]
616
- : choices.filter((candidate) => allowed.includes(candidate));
579
+ function optionalApprovalChoices(value) {
580
+ if (value === null || value === undefined)
581
+ return undefined;
582
+ // Current Codex also reports structured granular policies. They constrain
583
+ // configuration but cannot be selected by Yui's scalar approval input; do not
584
+ // reject the whole legitimate response or expand them into guessed aliases.
585
+ return array(value, "configuration approval requirements").flatMap(entry => {
586
+ if (typeof entry === "string")
587
+ return [requiredString(entry, "approval requirement")];
588
+ if (object(object(entry)?.granular) !== undefined)
589
+ return [];
590
+ throw new Error("Codex configuration approval requirement is invalid.");
591
+ });
617
592
  }
618
593
  function optionalStrings(value) {
619
594
  if (value === null || value === undefined)
@@ -7,7 +7,7 @@ import { builtinAgentEndpointImplementation, validateAgentEndpointImplementation
7
7
  /** Recovery addresses retained execution identity, not a live Session grant.
8
8
  * A released Session cache can be absent while its native binding still needs
9
9
  * to be stopped. No active Session record is fabricated by this projection. */
10
- export function taskRoleControlTarget(set) {
10
+ export function roleSessionControlTarget(set) {
11
11
  if (set == null)
12
12
  return undefined;
13
13
  const current = set.sessions[set.activeAgentId];
@@ -19,7 +19,10 @@ export function taskRoleControlTarget(set) {
19
19
  const agentId = fromBinding ? binding.accountScope : current?.agentId;
20
20
  if (nativeSessionId === undefined || agentId === undefined)
21
21
  return undefined;
22
- const session = [...Object.values(set.sessions), ...(set.history ?? [])]
22
+ const history = set.owner.scope === "global"
23
+ ? Object.values(set.history ?? {})
24
+ : set.history ?? [];
25
+ const session = [...Object.values(set.sessions), ...history]
23
26
  .find(entry => entry.nativeSessionId === nativeSessionId && entry.agentId === agentId);
24
27
  const adapterId = session?.adapterId ?? (binding === null ? undefined : builtinAgentDriverRegistry().find(binding.providerNamespace)?.adapterId);
25
28
  return { nativeSessionId, agentId, adapterId, status: session?.status,
@@ -176,15 +176,6 @@ export class ExecutorRegistry {
176
176
  });
177
177
  return deliveryReport(outcome);
178
178
  }
179
- async notifyOperatorInputOnce(input) {
180
- if (this.runtimePorts?.notifyOperatorInputOnce !== undefined) {
181
- return this.runtimePorts.notifyOperatorInputOnce(input);
182
- }
183
- const probe = this.readiness(input.adapterId, "operator");
184
- return this.tmux.sendRoleInputOnceIfReadyAsync === undefined
185
- ? this.tmux.sendRoleInputOnceIfReady("operator", input.roleName, input.receiptId, input.text, probe)
186
- : this.tmux.sendRoleInputOnceIfReadyAsync("operator", input.roleName, input.receiptId, input.text, probe);
187
- }
188
179
  forgetPrepared(input) {
189
180
  for (const [deliveryId, prepared] of this.#prepared) {
190
181
  const delivery = prepared.delivery;
@@ -21,7 +21,7 @@ import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
21
21
  import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
22
22
  import { planningRuntimeCwd } from "../storage/homeLayout.js";
23
23
  import { resolveAgentAdapter } from "./agentAdapter.js";
24
- import { activeLiveRoleAgentSession, taskRoleControlTarget } from "./agentExecutor.js";
24
+ import { activeLiveRoleAgentSession, roleSessionControlTarget } from "./agentExecutor.js";
25
25
  import { assertCodexLaunchOverridesAvailable, inspectCodexLaunchConfig } from "./codexConfigConflict.js";
26
26
  import { effectiveRoleForLaunch, resolveEffectiveLaunch, roleSessionMayContinue } from "./effectiveLaunch.js";
27
27
  import { classifyWorkspacePreflight, formatWorkspacePreflightError } from "./workspacePreflightClassification.js";
@@ -69,9 +69,11 @@ export class FileRoleLaunchPlanner {
69
69
  }
70
70
  /** Recovery addresses the recorded Agent/Session, independent of delivery
71
71
  * admission, a missing worktree, or the old Endpoint code generation. */
72
- planNativeControl(taskId, roleName) {
73
- const set = this.store.getTaskRoleSessionSet(taskId, roleName);
74
- const session = taskRoleControlTarget(set);
72
+ planNativeControl(owner) {
73
+ const set = owner.scope === "task"
74
+ ? this.store.getTaskRoleSessionSet(owner.taskId, owner.roleName)
75
+ : this.store.getGlobalRoleSessionSet(owner.roleName);
76
+ const session = roleSessionControlTarget(set);
75
77
  if (session?.adapterId !== "codex")
76
78
  throw new Error("Native metadata control requires a recorded Codex Session.");
77
79
  const configured = this.store.getConfiguredAgent(session.agentId);
@@ -79,9 +81,11 @@ export class FileRoleLaunchPlanner {
79
81
  throw new Error("The recorded native Agent connection is unavailable.");
80
82
  }
81
83
  const agent = configuredAgentToDefinition(configured);
82
- const connection = this.store.listEvents(taskId).find(event => event.type === "runtime.native-connection-bound"
83
- && event.payload.roleName === roleName && event.payload.agentId === session.agentId
84
- && event.payload.nativeSessionId === session.nativeSessionId)?.payload;
84
+ const connection = owner.scope === "task"
85
+ ? this.store.listEvents(owner.taskId).find(event => event.type === "runtime.native-connection-bound"
86
+ && event.payload.roleName === owner.roleName && event.payload.agentId === session.agentId
87
+ && event.payload.nativeSessionId === session.nativeSessionId)?.payload
88
+ : undefined;
85
89
  return {
86
90
  command: configured.command, args: [...agent.baseArgs, "app-server", "proxy"], cwd: this.home,
87
91
  expectedAccountHome: connection?.nativeAccountHome,
@@ -758,10 +762,7 @@ function managedClaudeControlPlaneConfig(config, taskId, workItemId, runId) {
758
762
  }
759
763
  function isManagedYuiBashRule(rule) {
760
764
  const normalized = rule.trim();
761
- return /^Bash\(yui(?:\s|:\*|\*|\))/u.test(normalized)
762
- // Yui no longer writes a control-plane digest into a managed rule; this
763
- // shape only clears one an earlier release left in a Provider config.
764
- || /^Bash\(.*\s--yui-control\s/u.test(normalized);
765
+ return /^Bash\(yui(?:\s|:\*|\*|\))/u.test(normalized);
765
766
  }
766
767
  function canonicalPath(path) {
767
768
  const absolute = resolve(path);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * One mailbox batch becomes one short synthetic user message. The durable
2
+ * One mailbox batch becomes one short system notification. The durable
3
3
  * records remain the context authority; this wake text carries only stable
4
4
  * identities and exact CLI reads, never copied Task or Provider narrative.
5
5
  */
@@ -11,6 +11,7 @@ export function createOperatorBatchPresentation(batchId, items) {
11
11
  text: [
12
12
  "[Yui updates]",
13
13
  "Yui recorded the following durable updates. Inspect the referenced records and present only information that changes the user's understanding, authorization, or next action.",
14
+ "This notification is not a new execution assignment or authorization to replay prior operations.",
14
15
  ...items.flatMap(renderItem)
15
16
  ].join("\n")
16
17
  };
@@ -1,7 +1,8 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
- import { createRun, withRunContextSnapshot } from "../agentRun/agentRun.js";
2
+ import { createRun } from "../agentRun/agentRun.js";
3
3
  import { createRunInput } from "../context/runInputContract.js";
4
4
  import { contextContentDigest, contextSnapshotRef, createContextSnapshot } from "../context/contextSnapshot.js";
5
+ import { readRunContextSnapshot } from "../context/runContextPack.js";
5
6
  import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
6
7
  import { enqueueRoleRunDispatch } from "../coordination/workMailboxQueue.js";
7
8
  import { createTaskEvent } from "../event/taskEvent.js";
@@ -265,12 +266,9 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
265
266
  continue;
266
267
  const previous = store.listRuns(taskId).filter((run) => run.roleName === owner.roleName
267
268
  && run.workItemId === owner.workItemId && run.reviewRoundId === owner.reviewRoundId).at(-1);
268
- const baselineRef = previous.inputs[0]?.input.contextSnapshotRef;
269
- const baseline = baselineRef === undefined ? null : store.getContextSnapshot(taskId, baselineRef.id);
270
- if (baseline === null || baseline.digest !== baselineRef.digest) {
271
- markNotDelivered(store, message, "assignment-context-unavailable");
272
- continue;
273
- }
269
+ // Missing or drifted evidence is diagnosed by the caller; never reconstruct
270
+ // an Assignment from today's Task facts.
271
+ const baseline = readRunContextSnapshot(store, previous);
274
272
  const runId = store.nextRunId(taskId);
275
273
  const round = owner.reviewRoundId === undefined ? null : store.getReviewRound(taskId, owner.reviewRoundId);
276
274
  const continuingRound = round === null ? null : (() => {
@@ -300,9 +298,10 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
300
298
  resources, refs: resources.map(({ ref }) => ref)
301
299
  });
302
300
  store.saveContextSnapshot(snapshot);
303
- const run = withRunContextSnapshot(createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
301
+ const run = createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
304
302
  directive: `Continue the same Assignment in its existing workspace. Read Messages ${batch.map((m) => m.id).join(", ")} from this exact Context. Message receipt is not implementation or acceptance.`,
305
- deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner)), contextSnapshotRef(snapshot), batch.map((entry) => entry.id));
303
+ contextSnapshotRef: contextSnapshotRef(snapshot),
304
+ deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner));
306
305
  store.saveRun(run);
307
306
  store.saveActiveRun(run);
308
307
  if (continuingRound !== null)
@@ -678,13 +678,6 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
678
678
  })();
679
679
  const storage = (() => {
680
680
  try {
681
- let stateJsonBytes = UNSUPPORTED;
682
- try {
683
- stateJsonBytes = statSync(join(home, "state.json")).size;
684
- }
685
- catch {
686
- stateJsonBytes = UNSUPPORTED;
687
- }
688
681
  let databaseBytes = UNSUPPORTED;
689
682
  try {
690
683
  databaseBytes = statSync(join(home, "yui.db")).size;
@@ -693,15 +686,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
693
686
  databaseBytes = UNSUPPORTED;
694
687
  }
695
688
  return ok({
696
- stateJsonBytes,
697
689
  databaseBytes,
698
690
  // The audit aggregates history; live db integrity is the status
699
691
  // command's job (it runs PRAGMA quick_check). Report presence/size
700
692
  // and the authoritative backend, not a duplicate health probe.
701
693
  databaseHealth: UNSUPPORTED,
702
694
  backend: resolveTaskStoreBackendForHome(home),
703
- runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED,
704
- deploymentsBytes: ports.directorySize(join(home, "runtime", "deployments")) ?? UNSUPPORTED
695
+ runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
705
696
  });
706
697
  }
707
698
  catch (error) {
@@ -165,9 +165,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
165
165
  || storage.status === "unsupported"
166
166
  ? storage.currentVersion
167
167
  : UNSUPPORTED;
168
- const statePath = join(home, "state.json");
169
- const statePresent = ports.exists(statePath);
170
- const stateBytes = ports.fileSize(statePath);
171
168
  const dbPath = join(home, "yui.db");
172
169
  const dbPresent = ports.exists(dbPath);
173
170
  const dbBytes = ports.fileSize(dbPath);
@@ -221,14 +218,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
221
218
  remediation: "Preserve this Home for diagnosis and restore a known-good backup."
222
219
  });
223
220
  }
224
- if (ports.exists(join(home, "schema.json"))) {
225
- findings.push({
226
- code: "ignored-legacy-storage-manifest",
227
- severity: "warning",
228
- message: "schema.json is legacy metadata and is not a storage authority.",
229
- remediation: "This file is not used. Preserve or archive it separately after inspecting its contents."
230
- });
231
- }
232
221
  if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
233
222
  findings.push({
234
223
  code: "database-unhealthy",
@@ -237,14 +226,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
237
226
  remediation: "Preserve this Home for diagnosis and initialize a new Home."
238
227
  });
239
228
  }
240
- if (statePresent) {
241
- findings.push({
242
- code: "ignored-historical-store",
243
- severity: "warning",
244
- message: "state.json is present but is not read or written by the current store.",
245
- remediation: "Keep it only as read-only historical evidence or archive it outside the active Home."
246
- });
247
- }
248
229
  const rawWorkerFlag = ports.env.YUI_STORE_WORKER;
249
230
  if (rawWorkerFlag !== undefined && !["0", "1", "false", "true"].includes(rawWorkerFlag.toLowerCase())) {
250
231
  findings.push({
@@ -261,10 +242,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
261
242
  minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION,
262
243
  configuredBackend,
263
244
  workerEnabled,
264
- physicalStateJson: {
265
- present: statePresent,
266
- bytes: stateBytes ?? UNSUPPORTED
267
- },
268
245
  physicalDatabase: {
269
246
  present: dbPresent,
270
247
  bytes: dbBytes ?? UNSUPPORTED,
@@ -23,7 +23,9 @@ export function renderAgentConfigurationCatalog(resolved) {
23
23
  // query rather than a deliberate boundary, so it is shown either way.
24
24
  field.available === false
25
25
  ? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
26
- : field.reason === undefined ? "available" : `available: ${field.reason}`,
26
+ : field.available === true
27
+ ? `available${field.reason === undefined ? "" : `: ${field.reason}`}`
28
+ : field.reason ?? "Native support not confirmed",
27
29
  field.allowCustom ? "yes" : "no"
28
30
  ]);
29
31
  const sections = [
@@ -39,7 +41,7 @@ export function renderAgentConfigurationCatalog(resolved) {
39
41
  agentModelOptionsSummary(catalog),
40
42
  ...(fieldRows.length === 0
41
43
  ? []
42
- : [renderTable("Other runtime configuration", [
44
+ : [renderTable("Configuration fields (native observations and static adapter contracts)", [
43
45
  { header: "Field", minWidth: 12, maxWidth: 30 },
44
46
  { header: "Values", minWidth: 18, maxWidth: 58 },
45
47
  { header: "Status", minWidth: 10, maxWidth: 38 },
@@ -131,14 +131,14 @@ async function activateLocked(ports, locked) {
131
131
  catch (rollbackError) {
132
132
  return {
133
133
  outcome: "aborted", phase: "preflight",
134
- message: `Host compatibility failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
134
+ message: `Storage preflight failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
135
135
  action: "No candidate was started and the release pointer is unchanged. Preserve the fence and inspect the old Controller.",
136
136
  recoverable: true
137
137
  };
138
138
  }
139
139
  return {
140
140
  outcome: "aborted", phase: "preflight",
141
- message: `Fenced compatibility preflight failed: ${messageOf(error)}`,
141
+ message: `Fenced storage preflight failed: ${messageOf(error)}`,
142
142
  action: "The old Controller is serving again; the active release and Home are unchanged.",
143
143
  recoverable: true
144
144
  };
@@ -38,41 +38,19 @@ import { writeTextFileAtomically } from "../storage/durableFile.js";
38
38
  * an unpersisted success into an ambiguous/fail-closed outcome; reporting
39
39
  * success for an effect whose dedup record did not land would let a crash
40
40
  * re-run the effect.
41
- *
42
- * Unsupported layouts
43
- * -------------------
44
- * The per-key `schemaVersion: 1` layout is the only supported format. A
45
- * legacy whole-map file (`release-idempotency.json`) from an unsupported
46
- * older layout is never auto-migrated: it fails closed with a bounded
47
- * diagnostic so the operator can inspect and clean it up deliberately.
48
41
  */
49
42
  export const RELEASE_IDEMPOTENCY_SCHEMA_VERSION = 1;
50
43
  const DIRECTORY_NAME = "release-idempotency";
51
- const LEGACY_FILE_NAME = "release-idempotency.json";
52
44
  export function createFileReleaseIdempotencyStore(home, now = () => new Date()) {
53
45
  const directory = join(home, DIRECTORY_NAME);
54
- const legacyPath = join(home, LEGACY_FILE_NAME);
55
46
  function recordPath(key) {
56
47
  // encodeURIComponent is injective and leaves only Linux-filename-safe
57
48
  // characters unescaped, so distinct keys never collide on disk.
58
49
  return join(directory, `${encodeURIComponent(key)}.json`);
59
50
  }
60
- /**
61
- * Fail closed when an unsupported legacy whole-map file is present.
62
- * The per-key layout is the only supported format; a whole-map file
63
- * from an unsupported older layout must not be silently trusted.
64
- */
65
- function rejectLegacyLayout() {
66
- if (existsSync(legacyPath)) {
67
- throw new Error(`unsupported legacy release idempotency layout: ${legacyPath} ` +
68
- `(whole-map file). Remove it after inspecting its contents; ` +
69
- `the per-key schemaVersion 1 layout is the only supported format.`);
70
- }
71
- }
72
51
  return {
73
52
  async load(key) {
74
53
  try {
75
- rejectLegacyLayout();
76
54
  const path = recordPath(key);
77
55
  if (!existsSync(path))
78
56
  return undefined;
@@ -88,7 +66,6 @@ export function createFileReleaseIdempotencyStore(home, now = () => new Date())
88
66
  throw new Error(`release idempotency store only records succeeded effects: ${effect.outcome}`);
89
67
  }
90
68
  try {
91
- rejectLegacyLayout();
92
69
  const record = Object.freeze({
93
70
  schemaVersion: RELEASE_IDEMPOTENCY_SCHEMA_VERSION,
94
71
  key,
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { runUpdate } from "../cli/updateOrchestrator.js";
5
+ import { renderUpdateResult } from "../cli/updateCommand.js";
5
6
  import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
6
7
  import { runProjectCommand } from "../commands/projectCommands.js";
7
8
  import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
@@ -116,11 +117,14 @@ export function createReleaseWorkflowPorts(deps) {
116
117
  // Best-effort persistence; the effect may still succeed.
117
118
  }
118
119
  const result = runUpdate(inspectedPorts, { home: deps.home });
120
+ // Preserve cleanup/restore receipts in the existing step log, even
121
+ // when the binary result is success or a later phase is blocked.
122
+ const updateLog = renderUpdateResult(result);
119
123
  if (result.outcome === "updated" || result.outcome === "already-current") {
120
124
  return {
121
125
  outcome: "succeeded",
122
126
  externalId: result.version,
123
- logs: [`cli-update: ${result.outcome} (${result.version})`]
127
+ logs: [updateLog]
124
128
  };
125
129
  }
126
130
  if (result.outcome === "aborted" && !result.recoverable) {
@@ -143,7 +147,7 @@ export function createReleaseWorkflowPorts(deps) {
143
147
  if (result.controllerOwnershipUnknown === true) {
144
148
  return {
145
149
  outcome: "timeout",
146
- logs: [`cli-update Controller ownership unknown at phase ${result.phase}: ${result.message}`]
150
+ logs: [updateLog]
147
151
  };
148
152
  }
149
153
  // P1-2 (rr22): persist the EXACT activation target (the resolved
@@ -154,13 +158,13 @@ export function createReleaseWorkflowPorts(deps) {
154
158
  return {
155
159
  outcome: "timeout",
156
160
  ...(identity === undefined ? {} : { externalIdentity: identity }),
157
- logs: [`cli-update ambiguous at phase ${result.phase}: ${result.message}`]
161
+ logs: [updateLog]
158
162
  };
159
163
  }
160
164
  return {
161
165
  outcome: "failed",
162
166
  error: `cli-update aborted at phase ${result.phase}: ${result.message}`,
163
- logs: [`cli-update: ${result.message}`]
167
+ logs: [updateLog]
164
168
  };
165
169
  }
166
170
  case "controller-replace": {