@zq-silk/yui 0.13.7 → 0.13.9

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 (78) hide show
  1. package/ARCHITECTURE.md +40 -4
  2. package/README.md +23 -4
  3. package/dist/cli/commandCatalog.js +21 -2
  4. package/dist/cli/updatePorts.js +4 -4
  5. package/dist/cli.js +43 -14
  6. package/dist/commands/agentCommands.js +1 -1
  7. package/dist/commands/configCommands.js +1 -86
  8. package/dist/commands/executionAuditCommands.js +17 -16
  9. package/dist/commands/globalRoleCommands.js +4 -4
  10. package/dist/commands/sessionCommands.js +2 -6
  11. package/dist/commands/taskActor.js +1 -2
  12. package/dist/commands/taskCommands.js +93 -21
  13. package/dist/commands/taskContextCommand.js +1 -1
  14. package/dist/commands/taskExecutionCommands.js +0 -5
  15. package/dist/commands/taskOverviewCommand.js +5 -1
  16. package/dist/commands/taskRoleRuntimeStatus.js +3 -16
  17. package/dist/config/configCatalog.js +1 -6
  18. package/dist/config/yuiConfig.js +0 -79
  19. package/dist/context/sessionBootstrapManifest.js +26 -23
  20. package/dist/controller/clientRuntime.js +40 -3
  21. package/dist/controller/controller.js +29 -54
  22. package/dist/controller/fileSchedulerStoreAdapter.js +372 -1117
  23. package/dist/controller/runtime.js +12 -5
  24. package/dist/controller/runtimeHookRunFence.js +3 -9
  25. package/dist/controller/runtimeLaunchCoordinator.js +44 -67
  26. package/dist/controller/structuredProviderObservation.js +18 -5
  27. package/dist/coordination/workMailbox.js +4 -4
  28. package/dist/execution/executionHealth.js +1 -1
  29. package/dist/executor/agentExecutor.js +103 -93
  30. package/dist/executor/executorRegistry.js +8 -20
  31. package/dist/executor/fileRoleLaunchPlanner.js +26 -93
  32. package/dist/executor/turnCompletion.js +5 -5
  33. package/dist/lifecycle/exactRunTerminalization.js +2 -4
  34. package/dist/observability/executionAudit.js +40 -94
  35. package/dist/operator/operatorSessionHistory.js +7 -5
  36. package/dist/output/rolePresentation.js +0 -1
  37. package/dist/repository/taskWorkspacePreparer.js +0 -1
  38. package/dist/role/role.js +13 -21
  39. package/dist/run/agentRun.js +4 -54
  40. package/dist/runtime/agentDriver.js +2 -0
  41. package/dist/runtime/agentError.js +114 -0
  42. package/dist/runtime/agentHost.js +55 -82
  43. package/dist/runtime/builtinAgentDrivers.js +21 -9
  44. package/dist/runtime/builtinAgentErrorMappers.js +150 -0
  45. package/dist/runtime/exactControlPlane.js +6 -12
  46. package/dist/runtime/index.js +1 -2
  47. package/dist/runtime/launchBroker.js +5 -19
  48. package/dist/runtime/lifecycleReservation.js +20 -4
  49. package/dist/runtime/providerRuntimeIdentity.js +11 -19
  50. package/dist/runtime/runtimeBinding.js +0 -27
  51. package/dist/runtime/runtimeObservation.js +7 -16
  52. package/dist/runtime/runtimeSessionCandidate.js +3 -10
  53. package/dist/runtime/sessionLaunchRequest.js +1 -2
  54. package/dist/runtime/sessionReconciliation.js +2 -2
  55. package/dist/runtime/structuredProviderHost.js +44 -79
  56. package/dist/runtime/taskRuntimeIsolation.js +0 -7
  57. package/dist/runtime/tmuxAdapters.js +6 -49
  58. package/dist/scheduler/activeRoleRunDelivery.js +220 -178
  59. package/dist/scheduler/activeTaskProgress.js +1 -4
  60. package/dist/scheduler/leaderWakeupProcessor.js +123 -88
  61. package/dist/scheduler/roleRunLiveness.js +4 -1
  62. package/dist/scheduler/roleRunStall.js +10 -14
  63. package/dist/scheduler/wakeReason.js +4 -0
  64. package/dist/storage/migration/productionRegistry.js +475 -0
  65. package/dist/storage/sqliteSchema.js +54 -2
  66. package/dist/storage/sqliteStore.js +11 -44
  67. package/dist/storage/taskStore.js +8 -36
  68. package/dist/web/webSnapshot.js +3 -0
  69. package/i18n/README.zh-CN.md +11 -3
  70. package/package.json +1 -1
  71. package/skills/yui-leader/SKILL.md +30 -8
  72. package/skills/yui-operator/SKILL.md +14 -6
  73. package/skills/yui-runtime/SKILL.md +11 -8
  74. package/dist/lifecycle/providerErrorClass.js +0 -152
  75. package/dist/run/providerRetry.js +0 -226
  76. package/dist/run/providerRetryConfig.js +0 -27
  77. package/dist/runtime/providerErrorCodes.js +0 -278
  78. package/dist/runtime/providerRecoveryDecision.js +0 -55
package/ARCHITECTURE.md CHANGED
@@ -1,9 +1,33 @@
1
1
  # Yui Architecture
2
2
 
3
- Yui is a local control plane for durable work across Projects and native Agent
4
- runtimes. The user talks to one Operator. The Operator routes each request to
5
- the right Project and Task; that Task's Leader owns decomposition, execution
6
- choice, review, integration, and completion.
3
+ Yui is a local control plane for intelligent Agents doing durable work across
4
+ Projects and native runtimes. The user talks to one Operator. The Operator
5
+ routes each request to the right Project and Task; that Task's Leader owns
6
+ decomposition, execution choice, review, integration, and completion.
7
+
8
+ ## Design principles
9
+
10
+ - **Agents own judgment.** Yui exposes current durable context and atomic
11
+ capabilities; the Operator, Leader, and Workers choose plans, execution
12
+ topology, sequencing, retry, and recovery from that context.
13
+ - **Core provides primitives, not a prescribed workflow.** Reads, messages,
14
+ bounded record transitions, workspace ownership, Session lifecycle, and
15
+ acceptance are composable operations. Project Skills and Knowledge provide
16
+ project-specific policy without adding core branches.
17
+ - **Durable intent outranks runtime continuity.** Tasks, WorkItems, Messages,
18
+ Decisions, results, Project Knowledge, and managed workspaces are authority.
19
+ Provider Sessions, transcripts, processes, and observations are execution
20
+ aids that may be resumed or replaced.
21
+ - **Trust explicit Agent actions.** Once identity, authority, and scope are
22
+ established, a valid Agent command is a semantic declaration. Core should not
23
+ reconstruct the same judgment through another status protocol.
24
+ - **Fail visibly and let the Agent adapt.** Preserve pending intent and return
25
+ actionable state. Add automated retry, recovery, leases, or fallback only for
26
+ a normal product path, a hard safety or data-integrity boundary, or a proven
27
+ failure whose cost justifies the machinery.
28
+ - **One question has one authority.** Projections and indexes may summarize
29
+ state, but scheduling and lifecycle decisions must not depend on independently
30
+ writable copies of the same fact.
7
31
 
8
32
  ## One outcome, Leader-chosen execution topology
9
33
 
@@ -254,6 +278,18 @@ Yui's current attachment, not exclusive ownership of the Provider thread. One
254
278
  Turn identifies one provider-native execution. Yui's authority epoch fences
255
279
  only Yui's own submissions and retries.
256
280
 
281
+ `AgentRun` is the single durable scheduling authority for a Role. Provider
282
+ runtime persistence has no independently writable current-Run field; a Turn's
283
+ Run id is correlation evidence for receipts and terminal observations only.
284
+ `TaskRole` likewise stores configuration and identity, not a writable runtime
285
+ status. CLI and Web status views derive activity from the active AgentRun and
286
+ add Session/Driver facts only as lifecycle and diagnostic detail.
287
+ `AgentHost` is the serialized consumer: while a native Turn is active, the next
288
+ AgentRun and mailbox batch remain durable and unsubmitted. When that Turn ends,
289
+ the Host makes the Conversation ready and the retained delivery continues.
290
+ This remains true when the Agent declared the old Run's semantic outcome before
291
+ the Provider emitted its terminal event.
292
+
257
293
  Codex Task threads remain ordinary native sessions and can be opened and used
258
294
  directly in Desktop. If a direct user Turn is active, Yui keeps its pending
259
295
  Run/message until that Turn settles. Global interactive entry remains a native
package/README.md CHANGED
@@ -2,7 +2,16 @@
2
2
 
3
3
  # Yui
4
4
 
5
- Yui is a local control plane for durable Codex and Claude work. It keeps control state and Project knowledge in inspectable JSON, lets tmux own native Agent terminals, and combines reusable Worker Profiles, Leader-owned delegation, explicit acceptance, and isolated Git worktrees for Project-backed Tasks.
5
+ Yui is a local control plane for intelligent Codex and Claude Agents. It keeps
6
+ user intent, Project knowledge, Tasks, handoffs, and results durable and
7
+ inspectable, while exposing small atomic capabilities for context, messaging,
8
+ delegation, workspaces, Sessions, review, and integration. Agents compose those
9
+ capabilities and decide how to plan, sequence, delegate, retry, and recover.
10
+
11
+ Yui deliberately does not turn Agent judgment into a deterministic workflow
12
+ engine. Its core owns durable identity, user authority, workspace isolation,
13
+ and atomic state changes. Provider Sessions and runtime observations support
14
+ execution and continuity, but they are not competing sources of Task truth.
6
15
 
7
16
  The current implementation restores the useful Role/Agent/session and CLI framework without restoring the later data-maintenance, lease, schedule, and recovery-ledger systems.
8
17
 
@@ -39,7 +48,7 @@ behavior, then apply only changes the user confirms.
39
48
 
40
49
  Durable settings are grouped by responsibility: `config system` for Home
41
50
  defaults and presentation, `config runtime` for Controller health, concurrency,
42
- launch, delivery, and Provider retry, `config workflow` for Leader/context/review
51
+ launch, and delivery mechanics, `config workflow` for Leader/context/review
43
52
  policy, `config resources` for quarantine and GC, and `config tools` for tmux
44
53
  and diagnostic telemetry. Configured Agents, global Roles, Profiles, and shell
45
54
  completion remain the sibling `config agent|role|profile|completion` domains.
@@ -883,8 +892,9 @@ creates a fresh proxy attachment.
883
892
  If the proxy disconnects, the Host may attach a bounded replacement client and
884
893
  reconcile the exact owned Turn from native history. A failed fresh attachment
885
894
  is released instead of becoming a cleanup prerequisite for later Runs.
886
- Claude Code keeps its independent stream-json process with exact
887
- user-message replay acknowledgement. A timeout or uncertain write becomes
895
+ Claude Code keeps its independent stream-json process. Agent Host is the sole
896
+ writer to that process, so a completed stream write accepts the Turn; the
897
+ later provider `result` event settles it. An uncertain write becomes
888
898
  `delivery-unknown` and is never automatically retried.
889
899
 
890
900
  Task Role observation and takeover are explicit:
@@ -900,6 +910,15 @@ supported human-control boundary. A Codex Role uses an ordinary shared thread
900
910
  and may be operated directly in Desktop; an active Desktop Turn creates bounded
901
911
  backpressure for Yui rather than a failed Run.
902
912
 
913
+ AgentRun is the only durable Role scheduling state. Conversation state does not
914
+ carry a second current-Run pointer; each Provider Turn records a Run id only to
915
+ correlate its receipt and terminal event. If an Agent finishes a Yui Run before
916
+ the native Turn terminal arrives, the next mailbox intent remains pending until
917
+ that Turn settles. Yui then claims the new AgentRun and submits it through the
918
+ same Session. TaskRole itself stores identity and desired launch configuration,
919
+ not runtime status; Role status shown by CLI/Web is derived from the active
920
+ AgentRun plus Session/Driver lifecycle facts.
921
+
903
922
  Global Operator and global Role sessions remain native interactive CLIs:
904
923
 
905
924
  ```sh
@@ -48,7 +48,7 @@ const CONFIG_KEY_VALUES = CONFIG_DEFINITIONS.map((definition) => ({
48
48
  }));
49
49
  const CONFIG_DOMAIN_SUMMARIES = {
50
50
  system: "Configure Home-wide defaults and human-facing presentation.",
51
- runtime: "Configure Controller recovery, concurrency, health, launch, delivery, and Provider retry policy.",
51
+ runtime: "Configure Controller recovery, concurrency, health, launch, and delivery mechanics.",
52
52
  workflow: "Configure Leader convergence, context, and optional review policy.",
53
53
  resources: "Configure resource garbage collection and quarantine policy.",
54
54
  tools: "Configure tmux and optional diagnostic telemetry."
@@ -559,7 +559,7 @@ const taskChildren = [
559
559
  summary: "Manage Roles within a Task.",
560
560
  sections: [{ id: "manage", title: "Commands", entries: [
561
561
  "add", "list", "status", "show", "update", "remove", "bind", "unbind",
562
- "view", "takeover", "release"
562
+ "session", "view", "takeover", "release"
563
563
  ] }],
564
564
  children: [
565
565
  {
@@ -587,6 +587,25 @@ const taskChildren = [
587
587
  { name: "remove", summary: "Remove a Task Role.", usage: "yui task role remove <task> <role>" },
588
588
  { name: "bind", summary: "Bind and activate an Agent for a Task Role.", usage: "yui task role bind <task> <role> <agent-id>" },
589
589
  { name: "unbind", summary: "Unbind a dormant Agent from a Task Role.", usage: "yui task role unbind <task> <role> <agent-id>" },
590
+ {
591
+ name: "session",
592
+ summary: "Inspect or stop one Task Role's native Session.",
593
+ executable: true,
594
+ sections: [{ id: "manage", title: "Commands", entries: ["inspect", "stop"] }],
595
+ children: [
596
+ {
597
+ name: "inspect",
598
+ summary: "Read the current Session, Host activation, and Turn facts.",
599
+ usage: "yui task role session inspect <task> <role>"
600
+ },
601
+ {
602
+ name: "stop",
603
+ summary: "Stop one idle Session and its exact Host activation.",
604
+ usage: "yui task role session stop <task> <role> --reason <text>",
605
+ options: ["--reason"]
606
+ }
607
+ ]
608
+ },
590
609
  {
591
610
  name: "view",
592
611
  summary: "Attach read-only to an independent Provider presentation surface.",
@@ -226,10 +226,10 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
226
226
  + `but the staged/verified artifact was ${staged.version}. Refusing to trust a `
227
227
  + "different build than the one that passed preflight.");
228
228
  }
229
- // Existing managed Sessions may have been created by a release that
230
- // embedded an exact CLI path/version in its wrapper. Convert those
231
- // authenticated, Manifest-referenced wrappers before the replacement
232
- // Controller starts so the update cannot strand a live Session.
229
+ // Existing managed Sessions may have been created by an earlier release.
230
+ // Retarget those authenticated, Manifest-referenced wrappers to the
231
+ // activated control plane before the replacement Controller starts so
232
+ // the update cannot strand a live Session.
233
233
  const sessionCliRefresh = run(activeBinary, ["--json", "internal", "session-cli-refresh"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
234
234
  assertSpawnOk(sessionCliRefresh, "refresh managed Session CLI wrappers");
235
235
  // Retain the exact path used by both doctor and version verification. The
package/dist/cli.js CHANGED
@@ -348,7 +348,7 @@ export async function main() {
348
348
  || (process.env.YUI_SESSION_SCOPE === "global" && process.env.YUI_ROLE !== "operator")) {
349
349
  throw usageError("Managed Session CLI refresh may be run only by the user or global Operator.");
350
350
  }
351
- const result = refreshManagedSessionCliWrappers(home);
351
+ const result = refreshManagedSessionCliWrappers(home, currentInvocationControlPlane(home));
352
352
  emit(`Refreshed ${result.refreshed} legacy Session CLI wrapper(s); `
353
353
  + `${result.current} already current, ${result.skipped} skipped.`, false, result);
354
354
  return;
@@ -366,7 +366,16 @@ export async function main() {
366
366
  return;
367
367
  }
368
368
  if (args[1] === "runtime-hook" && args.length === 2) {
369
- await runRuntimeObservationHookCommand(readFileSync(0, "utf8"), process.env);
369
+ // Provider lifecycle Hooks are observation channels, never execution
370
+ // gates. A late/stale Hook must not make Claude reject an otherwise
371
+ // valid Session or Turn; its exact fence is revalidated before any
372
+ // inbox fact is written, so dropping an invalid observation is safe.
373
+ try {
374
+ await runRuntimeObservationHookCommand(readFileSync(0, "utf8"), process.env);
375
+ }
376
+ catch {
377
+ return;
378
+ }
370
379
  return;
371
380
  }
372
381
  throw usageError("Internal lifecycle callback usage is invalid.");
@@ -1402,6 +1411,30 @@ export async function main() {
1402
1411
  if (jsonOutput) {
1403
1412
  throw usageError("Task Role view/takeover requires an interactive terminal.");
1404
1413
  }
1414
+ if (result.kind === "session-stop") {
1415
+ await ensureFileTaskController(home, { environment: process.env });
1416
+ try {
1417
+ await runtime.stopExactTaskRoleSession({
1418
+ taskId: result.taskId,
1419
+ roleName: result.roleName,
1420
+ agentId: result.agentId,
1421
+ adapterId: result.adapterId,
1422
+ nativeSessionId: result.nativeSessionId,
1423
+ ...(result.launchId === undefined ? {} : { launchId: result.launchId }),
1424
+ sessionUpdatedAt: result.sessionUpdatedAt
1425
+ });
1426
+ }
1427
+ catch (error) {
1428
+ throw runtimeError(`Session stop was requested but physical Host cleanup did not complete: ${error instanceof Error ? error.message : String(error)}`);
1429
+ }
1430
+ emit(result.output, false, {
1431
+ taskId: result.taskId,
1432
+ roleName: result.roleName,
1433
+ stopped: true,
1434
+ reason: result.reason
1435
+ });
1436
+ return;
1437
+ }
1405
1438
  if (result.kind === "view") {
1406
1439
  if (result.output !== undefined)
1407
1440
  emit(result.output);
@@ -1604,17 +1637,14 @@ async function preflightManagedTaskControlPlane() {
1604
1637
  && process.env.YUI_DRIVER_ID !== undefined
1605
1638
  ? builtinAgentDriverRegistry().require(process.env.YUI_DRIVER_ID)
1606
1639
  : undefined;
1607
- const preallocatedDriverCallback = runtimeDriverCallback
1608
- ?.capabilities.observation.sessionBootstrap === "preallocated";
1609
1640
  const verifiedStore = openCompatibleFileTaskStore(control.yuiHome);
1610
- assertExactTaskRuntimeState(runtime, verifiedStore, preallocatedDriverCallback
1611
- ? {
1612
- preallocatedDriverSessionReservation: {
1613
- yuiHome: control.yuiHome,
1614
- adapterId: runtimeDriverCallback.adapterId
1615
- }
1616
- }
1617
- : {});
1641
+ // Runtime Hooks have their own event-aware fence, including the valid case
1642
+ // where a Provider terminal arrives after its Yui Run has yielded and a
1643
+ // later wake is pending. Ordinary managed commands still require the exact
1644
+ // current mutable runtime before routing.
1645
+ if (runtimeDriverCallback === undefined) {
1646
+ assertExactTaskRuntimeState(runtime, verifiedStore);
1647
+ }
1618
1648
  const request = taskFinalReviewInvocation.request;
1619
1649
  if (request === undefined) {
1620
1650
  return {
@@ -2334,8 +2364,7 @@ async function executeOperatorSessionControl(control, home, store, runtime, tmux
2334
2364
  await ensureFileTaskController(home, { environment: process.env });
2335
2365
  if (paneRunning
2336
2366
  || (active !== undefined
2337
- && active.status !== "stopped"
2338
- && active.status !== "broken")) {
2367
+ && active.status === "active")) {
2339
2368
  await runtime.stopGlobalRoleSession(role.name);
2340
2369
  }
2341
2370
  applyOperatorSessionControl(control, store);
@@ -244,7 +244,7 @@ function findNonStoppedSessionReference(store, agentId) {
244
244
  }
245
245
  function sessionReference(set, agentId, reference) {
246
246
  const session = set.sessions[agentId];
247
- if (session === undefined || session.status === "stopped")
247
+ if (session === undefined || session.status === "ended")
248
248
  return null;
249
249
  return { ...reference, status: session.status };
250
250
  }
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, realpathSync } from "node:fs";
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
3
  import { usageError } from "../errors/cliError.js";
4
- import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
4
+ import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
5
5
  import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
6
6
  import { resolveTimeZone } from "../output/timePresentation.js";
7
7
  import { defaultTableWidth, renderTable } from "../output/table.js";
@@ -73,10 +73,6 @@ export function effectiveConfigData(config, domain) {
73
73
  resourcesGcMode: resolveResourcesGcMode(config.resourcesGcMode),
74
74
  resourcesGcAutoQuarantine: resolveResourcesGcAutoQuarantine(config.resourcesGcAutoQuarantine),
75
75
  resourcesQuarantineTtlHours: resolveResourcesQuarantineTtlHours(config.resourcesQuarantineTtlHours),
76
- providerRetryMode: resolveProviderRetryMode(config.providerRetryMode),
77
- providerRetryAdapters: resolveProviderRetryAdapters(config.providerRetryAdapters),
78
- providerRetryDelaysSeconds: resolveProviderRetryDelaysSeconds(config.providerRetryDelaysSeconds),
79
- providerRetryMaxWindowSeconds: resolveProviderRetryMaxWindowSeconds(config.providerRetryMaxWindowSeconds),
80
76
  runtimeHealth: {
81
77
  quietAfterSeconds: health.quietAfterMs / 1_000,
82
78
  diagnosticAfterSeconds: health.diagnosticAfterMs / 1_000,
@@ -498,87 +494,6 @@ const CONFIG_KEY_HANDLERS = [
498
494
  return `Delivery timeout reset to ${DEFAULT_DELIVERY_TIMEOUT_SECONDS} seconds\n`;
499
495
  }
500
496
  },
501
- {
502
- key: "provider-retry-mode",
503
- showLabel: "Provider retry mode",
504
- showValue: (config) => resolveProviderRetryMode(config.providerRetryMode),
505
- set(args, store) {
506
- if (args.length !== 1)
507
- throw usageError(`Runtime config set usage: yui config runtime set provider-retry-mode <${PROVIDER_RETRY_MODES.join("|")}>.`);
508
- const mode = validatedConfigValue(() => resolveProviderRetryMode(args[0]), `Runtime config set usage: yui config runtime set provider-retry-mode <${PROVIDER_RETRY_MODES.join("|")}>.`);
509
- saveConfigKey(store, (config) => ({ ...config, providerRetryMode: mode }));
510
- return `Provider retry mode set to ${mode}\n`;
511
- },
512
- clear(store) {
513
- saveConfigKey(store, (config) => {
514
- const { providerRetryMode: _removed, ...rest } = config;
515
- return rest;
516
- });
517
- return `Provider retry mode reset to ${DEFAULT_PROVIDER_RETRY_MODE}\n`;
518
- }
519
- },
520
- {
521
- key: "provider-retry-adapters",
522
- showLabel: "Provider retry adapters",
523
- showValue: (config) => resolveProviderRetryAdapters(config.providerRetryAdapters).join(", ") || "none",
524
- set(args, store) {
525
- if (args.length !== 1)
526
- throw usageError("Runtime config set usage: yui config runtime set provider-retry-adapters <all|claude,codex|off>.");
527
- const raw = args[0].trim().toLowerCase();
528
- const adapters = raw === "off" || raw === "" || raw === "0"
529
- ? []
530
- : validatedConfigValue(() => resolveProviderRetryAdapters(raw.split(",")), "Runtime config set usage: yui config runtime set provider-retry-adapters <all|claude,codex|off>.");
531
- saveConfigKey(store, (config) => ({ ...config, providerRetryAdapters: adapters }));
532
- return `Provider retry adapters set to ${adapters.join(", ") || "none"}\n`;
533
- },
534
- clear(store) {
535
- saveConfigKey(store, (config) => {
536
- const { providerRetryAdapters: _removed, ...rest } = config;
537
- return rest;
538
- });
539
- return "Provider retry adapters reset to all supported\n";
540
- }
541
- },
542
- {
543
- key: "provider-retry-delays-seconds",
544
- showLabel: "Provider retry delays",
545
- showValue: (config) => `${resolveProviderRetryDelaysSeconds(config.providerRetryDelaysSeconds).join(", ")} seconds`,
546
- set(args, store) {
547
- const usage = "Runtime config set usage: yui config runtime set provider-retry-delays-seconds <comma-separated-seconds>.";
548
- if (args.length !== 1)
549
- throw usageError(usage);
550
- const providerRetryDelaysSeconds = validatedConfigValue(() => resolveProviderRetryDelaysSeconds(args[0].split(",").map(Number)), usage);
551
- saveConfigKey(store, (config) => ({ ...config, providerRetryDelaysSeconds }));
552
- return `Provider retry delays set to ${providerRetryDelaysSeconds.join(", ")} seconds\n`;
553
- },
554
- clear(store) {
555
- saveConfigKey(store, (config) => {
556
- const { providerRetryDelaysSeconds: _removed, ...rest } = config;
557
- return rest;
558
- });
559
- return `Provider retry delays reset to ${DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS.join(", ")} seconds\n`;
560
- }
561
- },
562
- {
563
- key: "provider-retry-max-window-seconds",
564
- showLabel: "Provider retry max window",
565
- showValue: (config) => `${resolveProviderRetryMaxWindowSeconds(config.providerRetryMaxWindowSeconds)} seconds`,
566
- set(args, store) {
567
- const usage = "Runtime config set usage: yui config runtime set provider-retry-max-window-seconds <positive-seconds>.";
568
- if (args.length !== 1)
569
- throw usageError(usage);
570
- const providerRetryMaxWindowSeconds = validatedConfigValue(() => resolveProviderRetryMaxWindowSeconds(Number(args[0])), usage);
571
- saveConfigKey(store, (config) => ({ ...config, providerRetryMaxWindowSeconds }));
572
- return `Provider retry max window set to ${providerRetryMaxWindowSeconds} seconds\n`;
573
- },
574
- clear(store) {
575
- saveConfigKey(store, (config) => {
576
- const { providerRetryMaxWindowSeconds: _removed, ...rest } = config;
577
- return rest;
578
- });
579
- return `Provider retry max window reset to ${DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS} seconds\n`;
580
- }
581
- },
582
497
  {
583
498
  key: "tmux-bin",
584
499
  showLabel: "Tmux bin",
@@ -183,29 +183,30 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
183
183
  else {
184
184
  lines.push("", ...sectionError("events", report));
185
185
  }
186
- if (report.providerRetries.status === "ok" && report.providerRetries.data !== undefined) {
187
- const retries = report.providerRetries.data;
188
- if (retries.total > 0) {
189
- lines.push("", `Provider retries: ${retries.total} run(s) retried in place · ${retries.terminal} terminal`);
190
- lines.push(renderTable("Provider retry lineages", [
186
+ if (report.agentErrors.status === "ok" && report.agentErrors.data !== undefined) {
187
+ const errors = report.agentErrors.data;
188
+ if (errors.total > 0) {
189
+ lines.push("", `Agent errors: ${errors.total} · ${Object.entries(errors.byCategory)
190
+ .map(([category, count]) => `${category}:${count}`).join(", ")}`);
191
+ lines.push(renderTable("Agent errors", [
191
192
  { header: "Task", minWidth: 8, maxWidth: 14 },
192
193
  { header: "Run", minWidth: 14, maxWidth: 24 },
193
194
  { header: "Role", minWidth: 8, maxWidth: 12 },
194
- { header: "Attempts", minWidth: 8, maxWidth: 10 },
195
- { header: "Error class", minWidth: 14, maxWidth: 24 },
196
- { header: "Decision", minWidth: 14, maxWidth: 26 }
197
- ], retries.entries.map((entry) => [
195
+ { header: "Category", minWidth: 12, maxWidth: 20 },
196
+ { header: "Code", minWidth: 16, maxWidth: 32 },
197
+ { header: "Session", minWidth: 12, maxWidth: 16 }
198
+ ], errors.entries.map((entry) => [
198
199
  entry.taskId,
199
200
  entry.runId,
200
201
  entry.roleName,
201
- String(entry.attempts),
202
- entry.errorClass,
203
- entry.decision
202
+ entry.category,
203
+ entry.code,
204
+ entry.sessionDisposition
204
205
  ]), width));
205
206
  }
206
207
  }
207
208
  else {
208
- lines.push("", ...sectionError("providerRetries", report));
209
+ lines.push("", ...sectionError("agentErrors", report));
209
210
  }
210
211
  if (report.workItems.status === "ok" && report.workItems.data !== undefined) {
211
212
  const items = report.workItems.data;
@@ -257,8 +258,8 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
257
258
  const versions = Object.entries(runtime.contextProtocolVersions)
258
259
  .map(([version, count]) => `${version}:${count}`)
259
260
  .join(", ") || "none";
260
- const retryStates = Object.entries(runtime.activeRetryStates)
261
- .map(([state, count]) => `${state}:${count}`)
261
+ const errorCategories = Object.entries(runtime.agentErrorCategories)
262
+ .map(([category, count]) => `${category}:${count}`)
262
263
  .join(", ") || "none";
263
264
  const exits = Object.entries(runtime.processExitClassifications)
264
265
  .map(([classification, count]) => `${classification}:${count}`)
@@ -266,7 +267,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
266
267
  const usage = Object.entries(runtime.usageSemantics)
267
268
  .map(([semantics, count]) => `${semantics}:${count}`)
268
269
  .join(", ") || "none";
269
- lines.push("", `Runtime protocol: context versions ${versions} · manifest compatibility identities ${runtime.manifestCompatibilityDigests}`, `Active retry: ${runtime.activeRetryEpisodes} episode(s) [${retryStates}] · failures ${runtime.activeConsecutiveFailures} · dispatched ${runtime.activeDispatchedRetries}`, `Retry audit: ${runtime.retryClassifiedEvents} classified · ${runtime.retryDispatchedEvents} dispatched · ${runtime.retryRecoveredEvents} recovered · ${runtime.retryExhaustedEvents} exhausted`, `Process exits: ${runtime.processExitObservations} [${exits}] · capacity failures ${runtime.contextCapacityFailures}`, `Context telemetry: usage [${usage}] · native compaction events ${runtime.compactionEvents}`);
270
+ lines.push("", `Runtime protocol: context versions ${versions} · manifest compatibility identities ${runtime.manifestCompatibilityDigests}`, `Agent errors: ${runtime.agentErrors} [${errorCategories}]`, `Process exits: ${runtime.processExitObservations} [${exits}] · capacity failures ${runtime.contextCapacityFailures}`, `Context telemetry: usage [${usage}] · native compaction events ${runtime.compactionEvents}`);
270
271
  }
271
272
  else {
272
273
  lines.push("", ...sectionError("runtimeProtocol", report));
@@ -247,7 +247,7 @@ function bindRole(args, store) {
247
247
  const switched = switchActiveRoleAgent(withBinding, existingSet ?? createRoleSessionSet({ scope: "global", roleName: name }, role.activeAgentId, now), agentId, {
248
248
  activeRun: false,
249
249
  nativeProcessRunning: activeSession !== undefined
250
- && activeSession.status !== "stopped"
250
+ && activeSession.status === "active"
251
251
  }, now);
252
252
  tx.saveGlobalRoleWithSessionSet(switched.role, switched.sessions);
253
253
  return { message: `Bound role ${name} to ${agentId}`, role: switched.role };
@@ -271,7 +271,7 @@ function removeRole(args, store) {
271
271
  roleName: role.name
272
272
  }, "removal");
273
273
  const sessions = tx.getGlobalRoleSessionSet(name);
274
- if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status !== "stopped")) {
274
+ if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status === "active")) {
275
275
  throw usageError(`GlobalRole is active and cannot be removed: ${name}.`);
276
276
  }
277
277
  if (!tx.removeGlobalRole(name))
@@ -336,7 +336,7 @@ function roleSession(args, store, options) {
336
336
  adapterId: binding.adapterId,
337
337
  nativeSessionId,
338
338
  policy: "fixed",
339
- status: environment.YUI_ROLE === name ? "running" : "ready",
339
+ status: "active",
340
340
  effective: resolveEffectiveLaunch({ role, purpose: "execution" })
341
341
  };
342
342
  if (command === "record") {
@@ -349,7 +349,7 @@ function roleSession(args, store, options) {
349
349
  if (existing === null) {
350
350
  throw usageError("Native session replacement requires an existing native session.");
351
351
  }
352
- if (existing.status !== "stopped") {
352
+ if (existing.status !== "ended") {
353
353
  throw usageError("Native session replacement is blocked while the native Agent process is running.");
354
354
  }
355
355
  if (existing.nativeSessionId === nativeSessionId) {
@@ -75,16 +75,12 @@ function blockedSessions(input) {
75
75
  return [];
76
76
  return [{
77
77
  session,
78
- reason: session.status === "running"
79
- ? "running"
80
- : "runtime-work-pending"
78
+ reason: "runtime-work-pending"
81
79
  }];
82
80
  });
83
81
  }
84
82
  function blockedStopResult(blocked) {
85
- const details = blocked.map(({ session, reason }) => (`- ${renderSessionOwner(session)} (${reason === "running"
86
- ? "a Turn or Run is still running"
87
- : "an active Run or lifecycle operation is still pending"})`));
83
+ const details = blocked.map(({ session }) => (`- ${renderSessionOwner(session)} (an active Run or lifecycle operation is still pending)`));
88
84
  return {
89
85
  output: [
90
86
  `Cannot stop managed Sessions: ${blocked.length} Session(s) are still busy.`,
@@ -207,8 +207,7 @@ export function taskLeaderActionRunId(store, taskId, environment, yuiHome) {
207
207
  if (session === undefined
208
208
  || identity(session.nativeSessionId) === undefined
209
209
  || identity(session.launchId) === undefined
210
- || session.status === "stopped"
211
- || session.status === "broken"
210
+ || session.status === "ended"
212
211
  || session.adapterId !== adapterId
213
212
  || (explicitAssertion === undefined && session.launchId !== launchId))
214
213
  return undefined;