@zq-silk/yui 0.13.4 → 0.13.6

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 (54) hide show
  1. package/ARCHITECTURE.md +13 -13
  2. package/README.md +19 -20
  3. package/dist/cli/commandCatalog.js +22 -21
  4. package/dist/cli/interactionPolicy.js +0 -14
  5. package/dist/cli.js +42 -0
  6. package/dist/commands/executionAuditCommands.js +1 -1
  7. package/dist/commands/taskCommands.js +60 -326
  8. package/dist/commands/taskContextCommand.js +3 -14
  9. package/dist/commands/taskExecutionCommands.js +254 -0
  10. package/dist/commands/taskNextActionCommand.js +1 -3
  11. package/dist/commands/taskOverviewCommand.js +9 -2
  12. package/dist/commands/taskRoleRuntimeStatus.js +2 -25
  13. package/dist/controller/agentRuntimeObserver.js +4 -2
  14. package/dist/controller/clientRuntime.js +45 -2
  15. package/dist/controller/controller.js +6 -3
  16. package/dist/controller/fileSchedulerStoreAdapter.js +59 -188
  17. package/dist/controller/jobControl.js +3 -2
  18. package/dist/controller/runtime.js +6 -33
  19. package/dist/controller/runtimeEventProcessor.js +8 -4
  20. package/dist/controller/runtimeHookRunFence.js +4 -10
  21. package/dist/execution/executionHealth.js +8 -16
  22. package/dist/executor/agentAdapter.js +3 -8
  23. package/dist/executor/agentExecutor.js +13 -14
  24. package/dist/executor/fileRoleLaunchPlanner.js +10 -26
  25. package/dist/lifecycle/exactRunTerminalization.js +24 -322
  26. package/dist/repository/taskWorkspaceCoordinator.js +0 -9
  27. package/dist/runtime/agentHost.js +22 -83
  28. package/dist/runtime/builtinAgentDrivers.js +1 -1
  29. package/dist/runtime/exactControlPlane.js +15 -9
  30. package/dist/runtime/launchBroker.js +1 -11
  31. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  32. package/dist/runtime/providerRecoveryDecision.js +1 -1
  33. package/dist/runtime/providerRuntimeIdentity.js +25 -15
  34. package/dist/runtime/structuredProviderHost.js +0 -57
  35. package/dist/scheduler/activeRoleRunDelivery.js +1 -19
  36. package/dist/scheduler/leaderWakeupProcessor.js +12 -63
  37. package/dist/scheduler/ports.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +4 -1
  39. package/dist/scheduler/roleRunStall.js +0 -2
  40. package/dist/scheduler/taskExecutionProjection.js +18 -1
  41. package/dist/scheduler/wakeupQueue.js +2 -1
  42. package/dist/storage/migration/productionRegistry.js +65 -0
  43. package/dist/storage/sqliteStore.js +10 -2
  44. package/dist/storage/taskStore.js +11 -3
  45. package/dist/task/completionReadiness.js +0 -67
  46. package/dist/task/nextAction.js +16 -32
  47. package/dist/task/task.js +38 -3
  48. package/dist/web/assets/client/i18n.js +0 -4
  49. package/dist/web/assets/client/view.js +0 -18
  50. package/dist/web/webSnapshot.js +7 -13
  51. package/i18n/README.zh-CN.md +4 -4
  52. package/package.json +1 -1
  53. package/dist/run/recoveryProjection.js +0 -252
  54. package/dist/runtime/conversationSwitch.js +0 -277
package/ARCHITECTURE.md CHANGED
@@ -254,21 +254,21 @@ Yui's current attachment, not exclusive ownership of the Provider thread. One
254
254
  Turn identifies one provider-native execution. Yui's authority epoch fences
255
255
  only Yui's own submissions and retries.
256
256
 
257
- Codex Task threads can be opened and used directly in Desktop as ordinary
258
- sessions; no Yui takeover is required. If a direct user Turn is active, Yui
259
- keeps its pending Run/message and retries after that Turn settles. The explicit
260
- tmux takeover gateway remains for providers whose managed conversation is
261
- attached to an independent process. Global interactive entry remains a native
262
- session-lifecycle operation outside the Task delivery contract.
263
-
264
- Codex uses `app-server proxy` to create or resume a normal thread on the shared
265
- App Server daemon. Role model, effort, permission, workspace, and shell settings are passed at
257
+ Codex Task threads remain ordinary native sessions and can be inspected in
258
+ Desktop. While their managed Role runtime is live, writes go through Yui's
259
+ view/takeover boundary; stop Task execution before resuming the thread from a
260
+ different client. If Yui observes a pre-existing active Turn during resume, it
261
+ keeps its pending Run/message until that Turn settles. Global interactive entry
262
+ remains a native session-lifecycle operation outside the Task delivery contract.
263
+
264
+ Codex uses a Yui-owned direct `app-server` child to create or resume a normal
265
+ thread. The process loads the selected native config profile; Role model,
266
+ effort, permission, workspace, and shell settings are passed at
266
267
  `thread/start`/`thread/resume`, while the ordinary Task message points to the
267
268
  Session Manifest and matching Role Skill. Yui does not write either to global
268
- Codex config and may only ensure that the daemon is running; it never restarts
269
- or stops it for a thread error. A Codex native config profile is rejected for
270
- Managed Codex because the protocol cannot isolate it to one thread; a Yui Agent
271
- Profile supplies the per-Role behavior, Skills, model, and effort instead.
269
+ Codex config. The Agent Host owns the Provider process group, so Task execution
270
+ stop can end the complete runtime and start can rebuild it without depending on
271
+ the shared Codex daemon.
272
272
  Claude uses a persistent stream-json
273
273
  transport with exact user-message replay acknowledgement. In both cases, Yui
274
274
  records Turn intent before writing, accepts only exact Provider evidence, and
package/README.md CHANGED
@@ -549,18 +549,18 @@ yui operator new
549
549
  yui operator enter
550
550
  ```
551
551
 
552
- When a Task Role's current Provider Conversation cannot continue, request a
553
- bounded switch with an audited reason:
552
+ If current execution cannot be settled normally, fence the Task and restart
553
+ from its durable progress:
554
554
 
555
555
  ```sh
556
- yui task role session switch <task-id> <role> --reason "<why this conversation cannot continue>"
556
+ yui task execution stop <task-id> --force --reason "<why execution must be fenced>"
557
+ yui task execution start <task-id>
557
558
  ```
558
559
 
559
- The command records intent only. It does not fail active work, stop a live
560
- runtime, or forget the current Conversation. Once the Role has ready work and
561
- the current writer/Turn is settled, Yui creates and binds the replacement; a
562
- failure before that bind leaves the old Conversation authoritative. Existing
563
- messages, reviews, and delivery history remain durable.
560
+ `stop` terminates disposable Runs and Sessions while preserving WorkItems,
561
+ repository changes, Messages, reviews, and other Task progress. `start` admits
562
+ one new Leader attempt from those durable records; it does not recover an old
563
+ Agent conversation.
564
564
 
565
565
  Without `--task`, `operator submit` creates a new Draft. Drafts accept planning changes but must be activated before Agent execution.
566
566
  Operator resolves every request against the Project catalog and existing Task
@@ -875,13 +875,13 @@ Provider conversations remain ordinary user conversations. Yui adds the Role
875
875
  Skill and Session Manifest pointer, then sends Task work through provider-native
876
876
  structured requests. Managed prompts are never delivered as terminal bytes.
877
877
 
878
- Codex uses `app-server proxy` to create or resume a normal thread on the shared
879
- App Server daemon. The thread is visible and directly usable in Desktop; Yui
880
- does not require takeover or install global Hook/config state. It may
881
- idempotently start an absent daemon, but never restarts or stops it for a thread
882
- error. When a user Turn is active, Yui keeps pending Task work and retries
883
- later. If Yui's proxy disconnects, the Host reattaches without ending the
884
- logical Activation and reconciles the exact owned Turn from native history.
878
+ Codex uses a direct, Yui-owned `app-server` child to create or resume a normal
879
+ thread. The thread remains visible in Codex, while Yui's view/takeover boundary
880
+ is the supported way to interact with a live managed Role. Task execution stop
881
+ terminates the Agent Host and its Provider process group; start creates a fresh
882
+ owned runtime without depending on the shared Codex daemon. If the child
883
+ disconnects, the Host may start a bounded replacement process and reconcile the
884
+ exact owned Turn from native history.
885
885
  Claude Code keeps its independent stream-json process with exact
886
886
  user-message replay acknowledgement. A timeout or uncertain write becomes
887
887
  `delivery-unknown` and is never automatically retried.
@@ -894,10 +894,9 @@ yui task role takeover <task-id> <role>
894
894
  yui task role release <task-id> <role>
895
895
  ```
896
896
 
897
- For Codex, open the Role's ordinary thread in Desktop for direct interaction;
898
- these commands are not required. `view`/`takeover`/`release` remain the explicit
899
- PTY gateway for providers whose managed conversation uses an independent
900
- process.
897
+ For a live managed Role, including Codex, these commands are the supported
898
+ human-control boundary. Its ordinary native thread remains inspectable in
899
+ Codex; stop Task execution before resuming it from a different client.
901
900
 
902
901
  Global Operator and global Role sessions remain native interactive CLIs:
903
902
 
@@ -962,7 +961,7 @@ movement cannot conceal a workflow that is not advancing.
962
961
 
963
962
  Stable Role context never creates a separate bootstrap Turn. Task execution Runs use the generic Leader or Worker Skill, while review Runs use the generic Reviewer Skill based on durable Run purpose rather than a configured Role name. The provider either carries the Skill through a safe additive native context channel or points to it from the ordinary Task delivery. These Yui-owned Role Skills define portable orchestration only. Project Skills remain ordinary versioned files in the Project and are discovered, selected, and loaded by the Agent through its native project mechanism; Yui does not scan, parse, copy, or inject them.
964
963
 
965
- Managed Codex keeps the user's native developer instructions unchanged. The ordinary Task message includes a compact absolute Session Manifest pointer, and the manifest identifies the matching Yui-owned Role Skill for Codex to read on demand. Model, effort, permission, workspace, and shell settings selected for the Role are supplied as thread-scoped `thread/start` or `thread/resume` configuration through the shared App Server daemon. A Codex native config profile cannot be isolated to one shared-daemon thread, so Managed Codex rejects that setting and directs callers to a Yui Agent Profile; ordinary non-Yui and interactive Codex sessions remain unaffected. Unrelated Codex threads keep their existing user, profile, project, and system configuration and the underlying config file is never mutated. App Server notifications are the managed thread's lifecycle authority; Yui installs no managed Codex Hook and does not claim `notify`. Interactive Codex Sessions may still use Yui's structured `notify` callback, and Doctor reports any effective configuration conflict. `skills.config` is not misused because it only enables or disables already-discovered Skills. Claude receives the same Yui-owned Role Skill content from a private `0600` managed context file rather than a large or sensitive argv value; retries and resumes reuse the purpose-specific Role path. Non-Operator global Roles stay neutral and receive no Task orchestration Skill. Operator therefore opens at an empty native composer, so the user's text remains its first user message. Leader wakeups and Worker or Reviewer Run assignments remain real mailbox-delivered work messages.
964
+ Managed Codex keeps the user's native developer instructions unchanged. The ordinary Task message includes a compact absolute Session Manifest pointer, and the manifest identifies the matching Yui-owned Role Skill for Codex to read on demand. Its Yui-owned App Server process loads any selected Codex native config profile; model, effort, permission, workspace, and shell settings are supplied to `thread/start` or `thread/resume`, and the underlying Codex config file is never mutated. App Server notifications are the managed thread's lifecycle authority; Yui installs no managed Codex Hook and does not claim `notify`. Interactive Codex Sessions may still use Yui's structured `notify` callback, and Doctor reports any effective configuration conflict. `skills.config` is not misused because it only enables or disables already-discovered Skills. Claude receives the same Yui-owned Role Skill content from a private `0600` managed context file rather than a large or sensitive argv value; retries and resumes reuse the purpose-specific Role path. Non-Operator global Roles stay neutral and receive no Task orchestration Skill. Operator therefore opens at an empty native composer, so the user's text remains its first user message. Leader wakeups and Worker or Reviewer Run assignments remain real mailbox-delivered work messages.
966
965
 
967
966
  ## Controller and failure handling
968
967
 
@@ -282,6 +282,24 @@ const taskChildren = [
282
282
  }
283
283
  },
284
284
  { name: "activate", summary: "Activate a Draft Task.", usage: "yui task activate <id>" },
285
+ {
286
+ name: "execution",
287
+ summary: "Fence or resume all execution for a Task.",
288
+ sections: [{ id: "manage", title: "Commands", entries: ["stop", "start"] }],
289
+ children: [
290
+ {
291
+ name: "stop",
292
+ summary: "Stop all Task execution while preserving durable progress.",
293
+ usage: "yui task execution stop <task> --force --reason <text>",
294
+ options: ["--force", "--reason"]
295
+ },
296
+ {
297
+ name: "start",
298
+ summary: "Resume the Leader from durable Task progress.",
299
+ usage: "yui task execution start <task>"
300
+ }
301
+ ]
302
+ },
285
303
  {
286
304
  name: "complete",
287
305
  summary: "Complete an active Task and stop automatic wakeups.",
@@ -540,7 +558,7 @@ const taskChildren = [
540
558
  name: "role",
541
559
  summary: "Manage Roles within a Task.",
542
560
  sections: [{ id: "manage", title: "Commands", entries: [
543
- "add", "list", "status", "show", "update", "remove", "bind", "unbind", "session",
561
+ "add", "list", "status", "show", "update", "remove", "bind", "unbind",
544
562
  "view", "takeover", "release"
545
563
  ] }],
546
564
  children: [
@@ -569,17 +587,6 @@ const taskChildren = [
569
587
  { name: "remove", summary: "Remove a Task Role.", usage: "yui task role remove <task> <role>" },
570
588
  { name: "bind", summary: "Bind and activate an Agent for a Task Role.", usage: "yui task role bind <task> <role> <agent-id>" },
571
589
  { name: "unbind", summary: "Unbind a dormant Agent from a Task Role.", usage: "yui task role unbind <task> <role> <agent-id>" },
572
- {
573
- name: "session",
574
- summary: "Manage the Provider Conversation bound to a Task Role.",
575
- sections: [{ id: "manage", title: "Commands", entries: ["switch"] }],
576
- children: [{
577
- name: "switch",
578
- summary: "Request a fresh Conversation after the current one reaches a safe boundary.",
579
- usage: "yui task role session switch <task> <role> --reason <text>",
580
- options: ["--reason"]
581
- }]
582
- },
583
590
  {
584
591
  name: "view",
585
592
  summary: "Attach read-only to an independent Provider presentation surface.",
@@ -720,12 +727,12 @@ const taskChildren = [
720
727
  {
721
728
  name: "run",
722
729
  summary: "Inspect and control Task Role Agent Runs.",
723
- sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "settle", "recover", "yield", "context", "checkpoint", "retire"] }],
730
+ sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "settle", "yield", "context", "checkpoint", "retire"] }],
724
731
  children: [
725
732
  { name: "list", summary: "List Runs for a work item.", usage: "yui task run list <task>/<work>" },
726
733
  {
727
734
  name: "show",
728
- summary: "Show one Run with its canonical recovery fence and exact recovery actions.",
735
+ summary: "Show one Run and its retained audit evidence.",
729
736
  usage: "yui task run show <task>/<run> [--json]"
730
737
  },
731
738
  {
@@ -738,12 +745,6 @@ const taskChildren = [
738
745
  summary: "Close an obsolete stranded final Review Run without requesting a retry Round.",
739
746
  usage: "yui task run settle <task>/<run>"
740
747
  },
741
- {
742
- name: "recover",
743
- summary: "Record one exact Leader-controlled Run recovery decision.",
744
- usage: "yui task run recover <task>/<run> --action <diagnose|retry|terminate> (--expected-progress-at <timestamp>|--from-next-action <fingerprint>) --provider-acceptance <accepted|rejected|ambiguous> --reason <text>",
745
- options: ["--action", "--expected-progress-at", "--progress-at", "--from-next-action", "--provider-acceptance", "--reason", "--role", "--agent-id", "--adapter-id", "--native-session-id", "--launch-id"]
746
- },
747
748
  {
748
749
  name: "yield",
749
750
  summary: "Complete an active Run and wake the Leader.",
@@ -1432,7 +1433,7 @@ export const ROOT_COMMAND = buildNode({
1432
1433
  name: "task",
1433
1434
  summary: "Manage Tasks, WorkItems, Agent Runs, and integration.",
1434
1435
  sections: [
1435
- { id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile", "upstream"] },
1436
+ { id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "execution", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile", "upstream"] },
1436
1437
  { id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "overlap", "change-set"] },
1437
1438
  { id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event", "continuation", "wake"] }
1438
1439
  ],
@@ -346,20 +346,6 @@ export const INTERACTION_POLICIES = Object.freeze([
346
346
  { argumentIndex: 5, entity: "agent", provider: "configured-agents", actionTarget: false }
347
347
  ]
348
348
  })),
349
- {
350
- commandPath: ["task", "role", "session", "switch"],
351
- selectors: [
352
- { argumentIndex: 4, entity: "task", provider: "tasks", actionTarget: true, statuses: ["active"] },
353
- {
354
- argumentIndex: 5,
355
- entity: "task-role",
356
- provider: "task-roles",
357
- dependsOn: 4,
358
- actionTarget: true
359
- }
360
- ],
361
- trailingOptions: { "--reason": "value" }
362
- },
363
349
  ...["view", "takeover", "release"].map((command) => ({
364
350
  commandPath: ["task", "role", command],
365
351
  selectors: [
package/dist/cli.js CHANGED
@@ -39,6 +39,7 @@ import { runProjectCommand } from "./commands/projectCommands.js";
39
39
  import { runProfileCommand } from "./commands/profileCommands.js";
40
40
  import { dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
41
41
  import { taskActor } from "./commands/taskActor.js";
42
+ import { parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, finalizeStoppedTaskExecution, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
42
43
  import { isCurrentGlobalOperator } from "./commands/taskInputCommands.js";
43
44
  import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
44
45
  import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
@@ -808,6 +809,47 @@ export async function main() {
808
809
  return;
809
810
  }
810
811
  if (resolved[0] === "task") {
812
+ if (resolved[1] === "execution") {
813
+ if (resolved[2] === "stop") {
814
+ const request = parseTaskExecutionStopRequest(resolved.slice(3));
815
+ const result = stopTaskExecutionCommand(request, store, { environment: process.env });
816
+ try {
817
+ await ensureFileTaskController(home, { environment: process.env });
818
+ await runtime.stopTaskDurableJobs(result.taskId);
819
+ await runtime.stopTaskRoleSessions(result.taskId, result.roleNames);
820
+ await runtime.assertTaskPhysicalResourcesReleased(result.taskId);
821
+ finalizeStoppedTaskExecution(result.taskId, store);
822
+ }
823
+ catch (error) {
824
+ const message = error instanceof Error ? error.message : String(error);
825
+ throw runtimeError(`Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed: ${message}`);
826
+ }
827
+ emit(result.output, false, result);
828
+ return;
829
+ }
830
+ if (resolved[2] === "start") {
831
+ const taskId = parseTaskExecutionStartRequest(resolved.slice(3));
832
+ const task = store.getTask(taskId);
833
+ if (task === null)
834
+ throw usageError(`Task not found: ${taskId}.`);
835
+ // Reject managed Task callers before inspecting or starting runtime resources.
836
+ if (taskActor(process.env, taskId) === "leader") {
837
+ throw usageError("Task execution stop/start requires the global Operator or a human user.");
838
+ }
839
+ if (task.executionGate.state === "stopped") {
840
+ await runtime.assertTaskPhysicalResourcesReleased(taskId);
841
+ }
842
+ await ensureFileTaskController(home, { environment: process.env });
843
+ const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
844
+ // Idempotent start is also a reliable kick: if an earlier caller
845
+ // committed the gate but lost its Controller acknowledgement, retrying
846
+ // start re-signals the same durable wake without creating another one.
847
+ await runtime.notifyMailboxChanged?.({ kind: "role", taskId, roleName: "leader" });
848
+ emit(result.output, false, result);
849
+ return;
850
+ }
851
+ throw usageError("Task execution usage: yui task execution <stop|start> ...");
852
+ }
811
853
  if (resolved[1] === "integration") {
812
854
  const result = await runTaskIntegrationCommand(resolved.slice(2), store, home, {
813
855
  environment: process.env,
@@ -139,7 +139,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
139
139
  }
140
140
  if (report.sessions.status === "ok" && report.sessions.data !== undefined) {
141
141
  const sessions = report.sessions.data;
142
- lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · Conversation switches ${sessions.conversationSwitches}`
142
+ lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · Historical conversation switches ${sessions.conversationSwitches}`
143
143
  + ` · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`, `Terminal by Run relation: ${sessions.terminalByRunRelation.postRunYielded} post-run-yielded`
144
144
  + ` · ${sessions.terminalByRunRelation.runFailed} run-failed`
145
145
  + ` · ${sessions.terminalByRunRelation.activeRun} active-run`