@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
@@ -1150,7 +1150,7 @@ export class SqliteTaskStore {
1150
1150
  ? ""
1151
1151
  : ` WHERE ${predicates.join(" AND ")}`;
1152
1152
  const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
1153
- native_session_id, launch_id, status, session_updated_at,
1153
+ native_session_id, launch_id, session_updated_at,
1154
1154
  cleanup_required
1155
1155
  FROM runtime_session_candidates${where}`).all(...parameters);
1156
1156
  const candidates = rows.map((row) => ({
@@ -1161,7 +1161,6 @@ export class SqliteTaskStore {
1161
1161
  adapterId: row.adapter_id,
1162
1162
  nativeSessionId: row.native_session_id,
1163
1163
  ...(row.launch_id === null ? {} : { launchId: row.launch_id }),
1164
- status: row.status,
1165
1164
  sessionUpdatedAt: row.session_updated_at,
1166
1165
  cleanupRequired: row.cleanup_required === 1
1167
1166
  })).sort(compareRuntimeSessionCandidates);
@@ -1217,14 +1216,14 @@ export class SqliteTaskStore {
1217
1216
  json_extract(active_session, '$.adapterId') AS adapter_id,
1218
1217
  json_extract(active_session, '$.nativeSessionId') AS native_session_id,
1219
1218
  json_extract(active_session, '$.launchId') AS launch_id,
1220
- json_extract(active_session, '$.status') AS status,
1219
+ CASE
1220
+ WHEN json_extract(active_session, '$.status') = 'active'
1221
+ THEN 1 ELSE 0
1222
+ END AS is_active,
1221
1223
  json_extract(active_session, '$.updatedAt') AS session_updated_at,
1222
1224
  CASE
1223
- WHEN json_extract(active_session, '$.status') NOT IN ('stopped','broken')
1224
- AND (
1225
- json_extract(active_session, '$.status') = 'running'
1226
- OR json_type(active_session, '$.launchId') = 'text'
1227
- )
1225
+ WHEN json_extract(active_session, '$.status') = 'active'
1226
+ AND json_type(active_session, '$.launchId') = 'text'
1228
1227
  THEN 1 ELSE 0
1229
1228
  END AS cleanup_required
1230
1229
  FROM active`).get(...source.parameters);
@@ -1245,7 +1244,7 @@ export class SqliteTaskStore {
1245
1244
  && row.adapter_id === candidate.adapterId
1246
1245
  && row.native_session_id === candidate.nativeSessionId
1247
1246
  && (row.launch_id ?? undefined) === candidate.launchId
1248
- && row.status === candidate.status
1247
+ && row.is_active === 1
1249
1248
  && row.session_updated_at === candidate.sessionUpdatedAt
1250
1249
  && row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
1251
1250
  }
@@ -1277,16 +1276,15 @@ export class SqliteTaskStore {
1277
1276
  const taskId = candidate.owner.scope === "task" ? candidate.owner.taskId : "";
1278
1277
  this.#db.prepare(`INSERT INTO runtime_session_candidates (
1279
1278
  scope, task_id, role_name, agent_id, adapter_id, native_session_id,
1280
- launch_id, status, session_updated_at, cleanup_required
1281
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1279
+ launch_id, session_updated_at, cleanup_required
1280
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1282
1281
  ON CONFLICT(scope, task_id, role_name) DO UPDATE SET
1283
1282
  agent_id = excluded.agent_id,
1284
1283
  adapter_id = excluded.adapter_id,
1285
1284
  native_session_id = excluded.native_session_id,
1286
1285
  launch_id = excluded.launch_id,
1287
- status = excluded.status,
1288
1286
  session_updated_at = excluded.session_updated_at,
1289
- cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.launchId ?? null, candidate.status, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
1287
+ cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.launchId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
1290
1288
  }
1291
1289
  #deleteRuntimeSessionCandidate(owner) {
1292
1290
  this.#db.prepare(`DELETE FROM runtime_session_candidates
@@ -1585,37 +1583,6 @@ export class SqliteTaskStore {
1585
1583
  payload = excluded.payload, updated_at = excluded.updated_at`).run(run.taskId, run.id, run.roleName, run.status, this.#json(run), this.#now());
1586
1584
  });
1587
1585
  }
1588
- /**
1589
- * Issue 04: SQLite-native pending retry query. A single indexed scan
1590
- * replaces the adapter's per-Task in-memory sweep, so Controller deadline
1591
- * arming no longer materializes every Task and Run in JavaScript.
1592
- */
1593
- listPendingProviderRetries(taskIds) {
1594
- const selectedTaskIds = taskIds === undefined
1595
- ? undefined
1596
- : [...new Set(taskIds)].sort(numericCompare);
1597
- if (selectedTaskIds?.length === 0)
1598
- return [];
1599
- const taskPredicate = selectedTaskIds === undefined
1600
- ? ""
1601
- : ` AND tc.task_id IN (${selectedTaskIds.map(() => "?").join(", ")})`;
1602
- const rows = this.#db.prepare(`SELECT DISTINCT ar.task_id AS taskId, ar.run_id AS runId, ar.role_name AS roleName,
1603
- json_extract(ar.payload, '$.providerRetry.state') AS state,
1604
- CASE json_extract(ar.payload, '$.providerRetry.state')
1605
- WHEN 'scheduled' THEN json_extract(ar.payload, '$.providerRetry.nextAttemptAt')
1606
- ELSE json_extract(ar.payload, '$.providerRetry.episodeDeadlineAt')
1607
- END AS dueAt
1608
- FROM tasks_catalog tc INDEXED BY idx_tasks_active
1609
- JOIN active_runs ap ON ap.task_id = tc.task_id
1610
- JOIN agent_runs ar ON ar.task_id = ap.task_id AND ar.run_id = ap.run_id
1611
- WHERE tc.is_active = 1
1612
- AND ar.status = 'active'
1613
- AND json_extract(ar.payload, '$.providerRetry.state') IN
1614
- ('scheduled', 'dispatching', 'awaiting-progress')${taskPredicate}`).all(...(selectedTaskIds ?? []));
1615
- return rows.sort((left, right) => (numericCompare(left.taskId, right.taskId)
1616
- || numericCompare(left.roleName, right.roleName)
1617
- || numericCompare(left.runId, right.runId)));
1618
- }
1619
1586
  // -- review rounds ----------------------------------------------------------
1620
1587
  nextReviewRoundId(taskId) { return this.#nextTaskRecordId(taskId, "reviewRound"); }
1621
1588
  getReviewRound(taskId, reviewRoundId) {
@@ -6,7 +6,7 @@ import { validateConfiguredAgent } from "../agent/agent.js";
6
6
  import { validateCapabilityGrant } from "../grant/capabilityGrant.js";
7
7
  import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
8
8
  import { publicationExternalKey, validatePublicationReference } from "../task/publicationReference.js";
9
- import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
9
+ import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
10
10
  import { resolveTimeZone } from "../output/timePresentation.js";
11
11
  import { mailboxBatches, consumePendingBatch, mailboxHasWork, mailboxTargetKey, pendingLane, validateWorkMailbox } from "../coordination/workMailbox.js";
12
12
  import { validateContextSnapshot } from "../context/contextSnapshot.js";
@@ -15,7 +15,6 @@ import { validateInputRequest } from "../input/inputRequest.js";
15
15
  import { validateRoleSessionSet } from "../executor/agentExecutor.js";
16
16
  import { validateTaskMessage } from "../message/message.js";
17
17
  import { agentRunDeliveryReceiptId, validateAgentRun } from "../run/agentRun.js";
18
- import { providerRetryWakeAt } from "../run/providerRetry.js";
19
18
  import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
20
19
  import { FileSessionOwnerRegistry } from "../runtime/sessionOwnerRegistry.js";
21
20
  import { validateReviewConfig } from "../review/reviewConfig.js";
@@ -45,7 +44,7 @@ import { CURRENT_AGGREGATE_SCHEMA_VERSION, requireCompatibleStorageSchema, requi
45
44
  export const STORAGE_STATE_FILE = "state.json";
46
45
  /** The root StorageState schema is the persisted aggregate document version. */
47
46
  export const CURRENT_STORAGE_STATE_SCHEMA_VERSION = CURRENT_AGGREGATE_SCHEMA_VERSION;
48
- export const CURRENT_CONFIG_SCHEMA_VERSION = 3;
47
+ export const CURRENT_CONFIG_SCHEMA_VERSION = 4;
49
48
  export const CURRENT_HOME_IDENTITY_SCHEMA_VERSION = 1;
50
49
  export const CURRENT_ACTIVE_RUN_POINTER_SCHEMA_VERSION = 3;
51
50
  /**
@@ -60,11 +59,11 @@ export const CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION = 2;
60
59
  export const CURRENT_PROJECT_SCHEMA_VERSION = 5;
61
60
  export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 2;
62
61
  export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
63
- export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 3;
62
+ export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 4;
64
63
  export const CURRENT_TASK_SCHEMA_VERSION = 6;
65
64
  export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
66
65
  export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
67
- export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 3;
66
+ export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 4;
68
67
  export const CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION = 2;
69
68
  export const CURRENT_WORK_ITEM_SCHEMA_VERSION = 12;
70
69
  export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 6;
@@ -77,9 +76,9 @@ export const CURRENT_MILESTONE_SCHEMA_VERSION = 2;
77
76
  export const CURRENT_EVENT_SCHEMA_VERSION = 2;
78
77
  export const CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION = 1;
79
78
  export const CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION = 1;
80
- export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 2;
79
+ export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 3;
81
80
  export const CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION = 1;
82
- export const CURRENT_ROLE_AGENT_SESSION_SCHEMA_VERSION = 3;
81
+ export const CURRENT_ROLE_AGENT_SESSION_SCHEMA_VERSION = 4;
83
82
  export const CURRENT_PENDING_WAKEUP_SCHEMA_VERSION = 1;
84
83
  const STORAGE_LOCK_DIRECTORY = ".state.lock";
85
84
  const LOCK_TIMEOUT_MS = 5_000;
@@ -118,13 +117,13 @@ export const CURRENT_STORED_TASK_SCHEMA_VERSION = 18;
118
117
  * Keep these named at the storage boundary so the upgrade record-axis map can
119
118
  * assert it is classifying the same bytes the store reads and writes.
120
119
  */
121
- export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 6;
120
+ export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 8;
122
121
  /**
123
122
  * v7 combines optional Issue 04 retry/receipt fields and Issue 05 Leader
124
123
  * actionability fields. All are optional, so the v6→v7 migration is a
125
124
  * version-only rewrite.
126
125
  */
127
- export const CURRENT_AGENT_RUN_SCHEMA_VERSION = 9;
126
+ export const CURRENT_AGENT_RUN_SCHEMA_VERSION = 10;
128
127
  export const CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION = 1;
129
128
  export class FileTaskStore {
130
129
  rootDir;
@@ -991,29 +990,6 @@ export class FileTaskStore {
991
990
  }
992
991
  getAgentRun(taskId, id) { return optional(this.#state().tasks[taskId]?.agentRuns[id]); }
993
992
  listAgentRuns(taskId) { return values(this.#requireTask(taskId).agentRuns, "id"); }
994
- listPendingProviderRetries(taskIds) {
995
- // The legacy File store can answer the empty case without a scan fallback.
996
- // If durable retry state exists, the db-only capability must fail closed
997
- // instead of silently losing the Controller's wake deadline.
998
- const tasks = taskIds === undefined
999
- ? this.listTasks()
1000
- : [...new Set(taskIds)].sort(numericCompare).flatMap((taskId) => {
1001
- const task = this.getTask(taskId);
1002
- return task === null ? [] : [task];
1003
- });
1004
- for (const task of tasks) {
1005
- if (task.status !== "active")
1006
- continue;
1007
- for (const run of this.listAgentRuns(task.id)) {
1008
- if (run.status === "active"
1009
- && run.providerRetry !== undefined
1010
- && providerRetryWakeAt(run.providerRetry) !== null) {
1011
- throw new StorageRecordError("Provider retry in place requires the SQLite backend; run `yui update` to migrate this Home.");
1012
- }
1013
- }
1014
- }
1015
- return [];
1016
- }
1017
993
  saveAgentRun(run) {
1018
994
  const stored = identified(run, CURRENT_AGENT_RUN_SCHEMA_VERSION, "id", run.id, "Agent run");
1019
995
  validateAgentRun(stored);
@@ -2498,10 +2474,6 @@ export function validateYuiConfig(config) {
2498
2474
  resolveResourcesGcMode(config.resourcesGcMode);
2499
2475
  resolveResourcesGcAutoQuarantine(config.resourcesGcAutoQuarantine);
2500
2476
  resolveResourcesQuarantineTtlHours(config.resourcesQuarantineTtlHours);
2501
- resolveProviderRetryMode(config.providerRetryMode);
2502
- resolveProviderRetryAdapters(config.providerRetryAdapters);
2503
- resolveProviderRetryDelaysSeconds(config.providerRetryDelaysSeconds);
2504
- resolveProviderRetryMaxWindowSeconds(config.providerRetryMaxWindowSeconds);
2505
2477
  resolveRuntimeHealth(config.runtimeHealth);
2506
2478
  resolveControllerTaskConcurrency(config.controllerTaskConcurrency);
2507
2479
  resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds);
@@ -92,6 +92,9 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
92
92
  const effectiveLaunch = activeRun?.effective ?? activeSession?.effective ?? null;
93
93
  return {
94
94
  ...role,
95
+ // Presentation only: workflow activity is derived from AgentRun; the
96
+ // native Session contributes lifecycle detail when no Run is active.
97
+ status: activeRun === undefined ? activeSession?.status ?? "idle" : "running",
95
98
  sessionTokens: projectSessionTokenMetrics(events, resolveSessionTokenIdentity(activeSession === undefined
96
99
  ? null
97
100
  : { taskId, roleName: role.name, ...activeSession })),
@@ -2,9 +2,15 @@
2
2
 
3
3
  # Yui
4
4
 
5
- Yui 是面向持久 Codex/Claude 工作的本地控制平面。用户只需和 Operator
6
- 对话;Operator 将不同 Project 的需求、Bug、审查和问题咨询路由到对应
7
- Task,Leader 再负责拆解、执行选择、验收和安全集成。
5
+ Yui 是面向智能 Codex/Claude Agent 的本地控制平面。它持久保存用户意图、
6
+ Project Knowledge、Task、交接和结果,并提供上下文、消息、委派、工作区、
7
+ Session、审查和集成等小而原子的能力。Agent 组合这些能力,自主决定规划、
8
+ 顺序、委派、重试和恢复。
9
+
10
+ Yui 不把 Agent 的判断固化成确定性的工作流引擎。核心只负责持久身份、用户
11
+ 授权、工作区隔离和原子状态变更;Provider Session 与运行时观测用于执行和
12
+ 连续性,但不是 Task 事实的另一套来源。用户只需和 Operator 对话,Operator
13
+ 负责路由,Leader 负责目标拆解、执行选择、验收和集成。
8
14
 
9
15
  当前实现保留实用的 Role/Agent/session 与 CLI 框架,不恢复后期膨胀的数据维护、租约、定时调度和恢复账本体系。
10
16
 
@@ -514,6 +520,8 @@ Task 生命周期的交互选择只展示有效来源状态:activate 只展示
514
520
 
515
521
  Run、Conversation、Activation 与 Turn 是四个独立身份。Conversation 可以跨多个 Run 和客户端连接;Activation 只代表 Yui 当前的连接,而不是对 Provider thread 的独占所有权;Turn 在写入前先持久化。写入超时或结果不明确会进入 `delivery-unknown`,不会自动重发。Codex 已存在的原生 active Turn 只会让 Yui 暂时等待,不会导致 Yui Run 失败;Claude 等独立进程 Provider 继续通过 Yui 的 view/takeover 边界进行人工控制。
516
522
 
523
+ AgentRun 是 Role 是否有工作正在执行的唯一持久调度状态。Conversation 不再维护另一份 current Run;Provider Turn 中的 Run id 只用于关联接收回执和终态事件。TaskRole 本身只保存身份和期望启动配置,不再保存可写的运行状态;CLI/Web 展示的 Role 状态由活动 AgentRun 派生,并叠加 Session/Driver 生命周期事实用于诊断。Agent 可以在 Provider 终态到达前声明旧 Run 的语义结果,下一批 mailbox 工作也可以先形成新的 AgentRun;Agent Host 仍会串行等待旧 Turn 结束,再投递被保留的新输入,不回滚事件,也不把旧 Conversation 强行换绑到新 Run。
524
+
517
525
  Task Role 使用以下显式入口:
518
526
 
519
527
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "0.13.7",
3
+ "version": "0.13.9",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -122,8 +122,8 @@ or yield merely to preserve that native wait.
122
122
  Before the first durable Leader action, Yui observes fresh native generations
123
123
  that produce no WorkItem, Review, Integration, or Leader-attributed durable
124
124
  event. Two such generations create a non-blocking orchestration advisory for
125
- Leader and Operator judgment; they do not fail the Role, reduce the configured
126
- Provider retry policy, or prevent another useful generation. Read the evidence
125
+ Leader and Operator judgment; they do not fail the Role or prevent another
126
+ useful generation. Read the evidence
127
127
  before retrying, then choose whether to continue, change the configured Leader,
128
128
  or perform direct maintenance without manufacturing protocol records merely to
129
129
  silence the advisory.
@@ -703,13 +703,35 @@ newer WorkItem. If the original execution Session cannot be resumed, surface
703
703
  the recovery decision to the user; do not silently discard its context by
704
704
  creating a replacement.
705
705
 
706
- If a native Role Session disappears, run `yui task reconcile <task-id>`,
707
- inspect the Run and partial work, then retry only a confirmed failed Run:
706
+ For a Role runtime failure, inspect the exact error and runtime identities first:
708
707
 
709
708
  ```sh
710
- yui task run retry <run-id>
709
+ yui task event show <task> <agent-error-event>
710
+ yui task role session inspect <task> <role>
711
711
  ```
712
712
 
713
+ When a Provider-accepted Turn fails with availability, `429`, capacity, or a
714
+ recoverable transport error and the Session remains usable, retain the Run and
715
+ Session; a recovery action adds a new Turn on that same native Session. A
716
+ Session preparation failure or Driver rejection before input acceptance fails
717
+ the exact Run once; inspect its error and explicitly retry that failed Run when
718
+ another attempt is useful. Core will not redispatch it on a scheduler tick.
719
+
720
+ If the Driver proves that the Session cannot continue, settle or retire the
721
+ exact active Run, stop only that Role Session, then retry the failed Run so the
722
+ next dispatch starts a new Session:
723
+
724
+ ```sh
725
+ yui task role session stop <task> <role> --reason "<error decision>"
726
+ yui task run retry <task>/<failed-run>
727
+ ```
728
+
729
+ The new Run context contains the prior Agent, adapter, Run, Host activation,
730
+ native Session/Turn identities, and complete raw error through the referenced
731
+ Task event. Inspect recent `runtime.agent-error` events before another fresh
732
+ Session; after repeated fresh-Session failures, report the evidence and bounded
733
+ options to the user instead of inventing another automatic loop.
734
+
713
735
  ## Request a decision
714
736
 
715
737
  When a real user choice, new authority, or unavailable external fact is
@@ -789,8 +811,8 @@ yui task complete <task-id> --summary "<outcome, validation, and remaining risks
789
811
 
790
812
  Retire obsolete WorkItems with `yui task work retire <task>/<work> --summary
791
813
  "..."`, optionally using `--replacement`. If the current Provider Conversation
792
- cannot continue, request a bounded switch with
793
- `yui task role session switch <task> <role> --reason "..."`; the current
794
- Conversation remains authoritative until Yui safely binds the replacement. Archiving is a
814
+ cannot continue, settle its Run and stop the exact idle Session with
815
+ `yui task role session stop`; the next explicit Run dispatch creates the
816
+ replacement. Archiving is a
795
817
  separate global Operator lifecycle action. It performs the final Task-owned
796
818
  runtime and clean-worktree teardown, including this Leader.
@@ -349,12 +349,20 @@ the workflow without claiming that version was delivered.
349
349
  treat it as a permission boundary. The Operator may make code, semantic,
350
350
  requirement, acceptance, recovery, and integration decisions and must leave
351
351
  the real actor and rationale in durable Task state.
352
- - Reconcile a disappeared native Session with `task reconcile`; inspect the Run
353
- before retrying a confirmed failure.
354
- - If the current Provider Conversation cannot continue, use
355
- `yui task role session switch <task> <role> --reason "..."`. The request is
356
- audited, and Yui keeps the old Conversation authoritative until the exact
357
- replacement bind succeeds; never reconstruct identities from terminal text.
352
+ - Inspect `runtime.agent-error` and `yui task role session inspect` before a
353
+ recovery. When a Provider-accepted Turn fails with availability, `429`,
354
+ capacity, or a recoverable transport error and the Session remains usable,
355
+ add a new Turn to that Session. A Session preparation failure or Driver
356
+ rejection before input acceptance fails the exact Run once; explicitly retry
357
+ that failed Run when another attempt is useful. Core does not redispatch it
358
+ on a scheduler tick. If the Driver proves the Session cannot continue, settle
359
+ or retire its exact Run, stop that one idle Session with `yui task role
360
+ session stop <task> <role> --reason "..."`, then retry the failed Run. The
361
+ replacement Run receives the old Agent, adapter, Run, Host, Session, Turn,
362
+ and complete raw-error facts through Task context.
363
+ - Inspect recent errors before creating another fresh Session. After repeated
364
+ fresh-Session failures, summarize the evidence and bounded options to the
365
+ user; do not hide them behind an automatic replacement counter or loop.
358
366
  - Retry only an explicitly failed recovery Job.
359
367
  - When a Leader first-progress advisory is reported, inspect its native
360
368
  generations and absence of durable progress. It is cost evidence rather than
@@ -12,10 +12,11 @@ workspace layout, native transcript, or an earlier Run.
12
12
  For every managed Task Run:
13
13
 
14
14
  1. Read the exact Run identity from the newest Bootstrap Envelope.
15
- 2. Before acting, load its authorized pack with the ordinary Yui CLI command:
15
+ 2. Before acting, load its authorized pack with the Session CLI named by the
16
+ current Session Manifest:
16
17
 
17
18
  ```sh
18
- yui task run context "$YUI_TASK_ID/<run-id>" --json
19
+ "$YUI_SESSION_CLI" task run context "$YUI_TASK_ID/<run-id>" --json
19
20
  ```
20
21
 
21
22
  3. Verify that the returned Task, Run, Role, purpose, Snapshot digest, workspace,
@@ -27,7 +28,7 @@ For every managed Task Run:
27
28
  `refId`:
28
29
 
29
30
  ```sh
30
- yui task run context expand "$YUI_TASK_ID/<run-id>" <ref-id> --store <store> --mode full --json
31
+ "$YUI_SESSION_CLI" task run context expand "$YUI_TASK_ID/<run-id>" <ref-id> --store <store> --mode full --json
31
32
  ```
32
33
 
33
34
  A bare `<ref-id>` remains supported only when it identifies exactly one
@@ -46,7 +47,7 @@ For a global Operator or custom GlobalRole Session, load the stable authorized v
46
47
  before routing or acting:
47
48
 
48
49
  ```sh
49
- yui session context "$YUI_ROLE" --json
50
+ "$YUI_SESSION_CLI" session context "$YUI_ROLE" --json
50
51
  ```
51
52
 
52
53
  Global context grants no Task implementation workspace. Read a Task only after
@@ -60,7 +61,9 @@ supported checkpoint/yield command as the final control-plane action, then stop
60
61
  immediately. If that direct command is denied or stale, report the blocker once
61
62
  and stop; do not wrap, retry, broaden permissions, or target another Run.
62
63
 
63
- For a transient Provider retry Envelope, continue the failed Turn in the same
64
- native Session. Do not replay the original Assignment or reload unrelated Task
65
- content. Process/child replacement does not by itself authorize a new Yui
66
- generation or native conversation.
64
+ After a failed Provider Turn, read the referenced `runtime.agent-error` fact.
65
+ The failed Turn is immutable; a recovery is always a new Turn. Continue on the
66
+ same native Session when it remains recoverable, and load only the current Run
67
+ delta instead of replaying its original Assignment. A new Host process does not
68
+ imply a new Session, and a new Session must never be substituted silently for
69
+ the persisted native Session id.
@@ -1,152 +0,0 @@
1
- /**
2
- * Issue 04 — Provider error classification.
3
- *
4
- * Provider failures arrive at the driver boundary as opaque free text (Claude
5
- * StopFailure `error`/`errorDetails`, Codex turn-completion summaries). Each
6
- * driver parses its own Provider's format into a structured
7
- * {@link ProviderErrorCode} at the driver boundary. This module maps those
8
- * codes to provider-neutral error classes by lookup, falling back to text
9
- * matching only when the driver could not produce a structured code.
10
- *
11
- * The retry-in-place coordinator needs a stable, provider-neutral error class
12
- * before it can decide whether the original Session may be retried.
13
- *
14
- * Classes (Issue 04 §2):
15
- * - `transient-provider` — 500/502/504, connection reset, backend capacity;
16
- * the original Session is retried in place.
17
- * - `transport-uncertain` — the request may have been accepted but the
18
- * response was lost; native facts are consulted
19
- * before any resend.
20
- * - `policy-denied` — cyber_policy / permission boundary; never retried
21
- * automatically, never worked around by switching
22
- * Session or widening permission.
23
- * - `session-dead` — the process/tmux/native identity is gone; in-place
24
- * retry stops and a replacement blocker is raised.
25
- * - `invalid-request` — deterministic parameter/protocol error; fail fast,
26
- * never call the Provider again.
27
- * - `unclassified` — no conservative match; behaves like
28
- * `invalid-request` for retry purposes (old
29
- * terminalize-immediately behavior) while remaining
30
- * observable in shadow metrics.
31
- */
32
- import { PROVIDER_ERROR_CODE_CLASS } from "../runtime/providerErrorCodes.js";
33
- /** Classes for which the original Session may be retried in place. */
34
- export const RETRYABLE_PROVIDER_ERROR_CLASSES = [
35
- "transient-provider",
36
- "transport-uncertain"
37
- ];
38
- export function isRetryableProviderErrorClass(errorClass) {
39
- return RETRYABLE_PROVIDER_ERROR_CLASSES.includes(errorClass);
40
- }
41
- /**
42
- * Ordered pattern tables. The first class whose pattern matches wins, so the
43
- * table order is the precedence order. Patterns are matched case-insensitively
44
- * against the concatenation of every available text field.
45
- */
46
- const SESSION_DEAD_PATTERNS = [
47
- { pattern: /session not found/iu, label: "session-not-found" },
48
- { pattern: /no such (session|thread)/iu, label: "no-such-session" },
49
- { pattern: /thread not found/iu, label: "thread-not-found" },
50
- { pattern: /session (has )?expired/iu, label: "session-expired" },
51
- { pattern: /session (has )?ended/iu, label: "session-ended" },
52
- { pattern: /session (is )?dead/iu, label: "session-dead" },
53
- { pattern: /session terminated/iu, label: "session-terminated" },
54
- ];
55
- const CONTEXT_CAPACITY_PATTERNS = [
56
- { pattern: /maximum context length/iu, label: "maximum-context-length" },
57
- { pattern: /context length exceeded/iu, label: "context-length-exceeded" },
58
- { pattern: /context window (is )?(full|exceeded)/iu, label: "context-window-exceeded" },
59
- { pattern: /prompt (is )?too long/iu, label: "prompt-too-long" },
60
- { pattern: /too many tokens/iu, label: "too-many-tokens" }
61
- ];
62
- const POLICY_DENIED_PATTERNS = [
63
- { pattern: /cyber[_-]?policy/iu, label: "cyber-policy" },
64
- { pattern: /policy[_-]?violation/iu, label: "policy-violation" },
65
- { pattern: /usage[_-]?policy/iu, label: "usage-policy" },
66
- { pattern: /content[_-]?policy/iu, label: "content-policy" },
67
- { pattern: /safety[_-]?policy/iu, label: "safety-policy" },
68
- { pattern: /policy denial/iu, label: "policy-denial" }
69
- ];
70
- const INVALID_REQUEST_PATTERNS = [
71
- { pattern: /invalid[_-]?request/iu, label: "invalid-request" },
72
- { pattern: /validation error/iu, label: "validation-error" },
73
- { pattern: /bad request/iu, label: "bad-request" },
74
- { pattern: /\b400\b/u, label: "http-400" },
75
- { pattern: /unknown (flag|tool|argument)/iu, label: "unknown-argument" },
76
- { pattern: /unexpected argument/iu, label: "unexpected-argument" },
77
- { pattern: /invalid schema/iu, label: "invalid-schema" }
78
- ];
79
- const TRANSIENT_PROVIDER_PATTERNS = [
80
- { pattern: /\b50[024]\b/u, label: "http-5xx" },
81
- { pattern: /server[\s_-]?error/iu, label: "server-error" },
82
- { pattern: /internal server error/iu, label: "internal-server-error" },
83
- // HTTP/2 RST_STREAM / gRPC status carried by Claude Code and Codex streams
84
- // (Task-27: "stream error: stream ID …; INTERNAL_ERROR; received from peer").
85
- { pattern: /\binternal[\s_-]?error\b/iu, label: "internal-error" },
86
- { pattern: /connection lost/iu, label: "connection-lost" },
87
- { pattern: /connection reset/iu, label: "connection-reset" },
88
- { pattern: /econnreset/iu, label: "econnreset" },
89
- { pattern: /socket hang up/iu, label: "socket-hang-up" },
90
- { pattern: /kv[_-]?cache[_-]?allocate[_-]?failed/iu, label: "kv-cache-allocate-failed" },
91
- { pattern: /overloaded/iu, label: "overloaded" },
92
- { pattern: /\b429\b/u, label: "http-429" },
93
- { pattern: /rate[_-]?limit/iu, label: "rate-limit" },
94
- { pattern: /upstream/iu, label: "upstream" },
95
- { pattern: /bad gateway/iu, label: "bad-gateway" },
96
- { pattern: /gateway timeout/iu, label: "gateway-timeout" },
97
- { pattern: /service unavailable/iu, label: "service-unavailable" },
98
- { pattern: /temporarily unavailable/iu, label: "temporarily-unavailable" },
99
- { pattern: /try again/iu, label: "try-again" }
100
- ];
101
- const TRANSPORT_UNCERTAIN_PATTERNS = [
102
- { pattern: /timed?[ -]?out/iu, label: "timeout" },
103
- { pattern: /etimedout/iu, label: "etimedout" },
104
- { pattern: /response lost/iu, label: "response-lost" },
105
- { pattern: /lost response/iu, label: "lost-response" },
106
- // A stream-level failure means the response may have been cut mid-turn;
107
- // the retry path consults durable completion facts before any resend.
108
- { pattern: /stream error/iu, label: "stream-error" },
109
- { pattern: /stream interrupted/iu, label: "stream-interrupted" },
110
- { pattern: /interrupted function/iu, label: "interrupted-function" },
111
- { pattern: /controller timeout/iu, label: "controller-timeout" },
112
- { pattern: /delivery (unconfirmed|uncertain|not confirmed)/iu, label: "delivery-unconfirmed" },
113
- { pattern: /unconfirmed delivery/iu, label: "unconfirmed-delivery" },
114
- { pattern: /no response/iu, label: "no-response" }
115
- ];
116
- const CLASS_TABLE = [
117
- { errorClass: "session-dead", patterns: SESSION_DEAD_PATTERNS },
118
- { errorClass: "policy-denied", patterns: POLICY_DENIED_PATTERNS },
119
- { errorClass: "context-capacity", patterns: CONTEXT_CAPACITY_PATTERNS },
120
- { errorClass: "invalid-request", patterns: INVALID_REQUEST_PATTERNS },
121
- { errorClass: "transient-provider", patterns: TRANSIENT_PROVIDER_PATTERNS },
122
- { errorClass: "transport-uncertain", patterns: TRANSPORT_UNCERTAIN_PATTERNS }
123
- ];
124
- /**
125
- * Classifies one provider failure. When the driver produced a structured
126
- * {@link ProviderErrorCode}, the class is looked up directly. Otherwise the
127
- * raw text fields are matched against the fallback pattern tables. Every
128
- * available text field is concatenated so a class can be recognized
129
- * regardless of which field carried it.
130
- */
131
- export function classifyProviderError(input) {
132
- // Structured path: the driver already parsed the Provider's error format.
133
- if (input.errorCode !== undefined) {
134
- const errorClass = PROVIDER_ERROR_CODE_CLASS[input.errorCode];
135
- if (errorClass !== undefined) {
136
- return { errorClass, matched: input.errorCode, basis: "structured" };
137
- }
138
- }
139
- // Text fallback: for drivers that cannot yet produce a structured code.
140
- const text = [input.error, input.errorDetails, input.summary]
141
- .filter((value) => typeof value === "string" && value.length > 0)
142
- .join("\n");
143
- if (text.length === 0)
144
- return { errorClass: "unclassified", matched: "none", basis: "text" };
145
- for (const { errorClass, patterns } of CLASS_TABLE) {
146
- for (const { pattern, label } of patterns) {
147
- if (pattern.test(text))
148
- return { errorClass, matched: label, basis: "text" };
149
- }
150
- }
151
- return { errorClass: "unclassified", matched: "none", basis: "text" };
152
- }