@zq-silk/yui 0.16.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +3 -0
  4. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  5. package/dist/agentRun/agentRun.js +7 -17
  6. package/dist/cli/agentConfigurationPicker.js +24 -21
  7. package/dist/cli/commandCatalog.js +18 -10
  8. package/dist/cli/roleWizard.js +41 -30
  9. package/dist/cli/updateCommand.js +35 -13
  10. package/dist/cli/updateOrchestrator.js +73 -33
  11. package/dist/cli/updatePorts.js +40 -17
  12. package/dist/cli.js +35 -26
  13. package/dist/commands/controllerCommands.js +1 -1
  14. package/dist/commands/executionAuditCommands.js +2 -2
  15. package/dist/commands/projectCommands.js +1 -1
  16. package/dist/commands/releaseCommands.js +9 -38
  17. package/dist/commands/taskCommands.js +84 -81
  18. package/dist/commands/taskCompletionGate.js +0 -117
  19. package/dist/commands/taskRoleRuntimeStatus.js +1 -1
  20. package/dist/commands/taskUpstreamCommands.js +93 -54
  21. package/dist/context/runContextPack.js +30 -54
  22. package/dist/context/runInputContract.js +9 -0
  23. package/dist/context/sessionBootstrapManifest.js +11 -19
  24. package/dist/controller/agentHostObservation.js +4 -2
  25. package/dist/controller/controller.js +8 -6
  26. package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
  27. package/dist/controller/globalInputDelivery.js +28 -7
  28. package/dist/controller/globalRuntimeAttention.js +34 -0
  29. package/dist/controller/operatorNotification.js +31 -0
  30. package/dist/controller/providerRetryAdmission.js +3 -1
  31. package/dist/controller/providerRetryDelivery.js +10 -3
  32. package/dist/controller/runtime.js +1 -27
  33. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  34. package/dist/controller/structuredProviderObservation.js +27 -0
  35. package/dist/controller/updateReconciliation.js +72 -20
  36. package/dist/doctor/doctor.js +2 -1
  37. package/dist/executor/agentAdapter.js +28 -96
  38. package/dist/executor/agentConfigurationCatalog.js +13 -68
  39. package/dist/executor/agentConfigurationFields.js +120 -0
  40. package/dist/executor/agentConfigurationProbe.js +55 -80
  41. package/dist/executor/agentExecutor.js +5 -2
  42. package/dist/executor/executorRegistry.js +0 -9
  43. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  44. package/dist/interaction/operatorPresentation.js +2 -1
  45. package/dist/message/messageContinuation.js +8 -9
  46. package/dist/observability/executionAudit.js +1 -10
  47. package/dist/observability/runtimeIdentity.js +0 -23
  48. package/dist/output/agentConfigurationPresentation.js +4 -2
  49. package/dist/release/releaseHandover.js +2 -2
  50. package/dist/release/releaseIdempotencyStore.js +0 -23
  51. package/dist/release/releaseWorkflowPorts.js +8 -4
  52. package/dist/repository/gitWorkspace.js +66 -19
  53. package/dist/repository/taskWorkspacePreparer.js +75 -68
  54. package/dist/resources/resourceRegistry.js +90 -44
  55. package/dist/resources/sqliteResourceRegistry.js +3 -5
  56. package/dist/runtime/acpSession.js +21 -5
  57. package/dist/runtime/agentEndpoint.js +4 -1
  58. package/dist/runtime/agentHost.js +22 -62
  59. package/dist/runtime/agentHostCleanup.js +85 -0
  60. package/dist/runtime/agentHostProtocol.js +1 -1
  61. package/dist/runtime/builtinAgentDrivers.js +46 -119
  62. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  63. package/dist/runtime/codexAppServerRuntime.js +55 -89
  64. package/dist/runtime/jsonLineChannel.js +35 -7
  65. package/dist/runtime/providerContinuationReconciliationService.js +42 -3
  66. package/dist/runtime/providerRetry.js +15 -0
  67. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  68. package/dist/runtime/structuredProviderHost.js +106 -81
  69. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  70. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  71. package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
  72. package/dist/task/nextAction.js +33 -23
  73. package/dist/task/task.js +2 -1
  74. package/dist/web/assets/assetManifest.js +2 -0
  75. package/dist/web/assets/client/app.js +72 -3
  76. package/dist/web/assets/client/components.js +2 -1
  77. package/dist/web/assets/client/i18n.js +2 -0
  78. package/dist/web/assets/client/taskSummary.js +345 -0
  79. package/dist/web/assets/client/taskSurface.js +44 -26
  80. package/dist/web/assets/client/view.js +33 -0
  81. package/dist/web/assets/shell.js +1 -0
  82. package/dist/web/assets/styles/cards.js +21 -0
  83. package/dist/web/webServer.js +39 -5
  84. package/dist/web/webSessions.js +165 -0
  85. package/dist/web/webSnapshot.js +23 -0
  86. package/dist/web/webTaskSurface.js +23 -0
  87. package/docs/managed-turn-and-session-runtime.md +31 -0
  88. package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
  89. package/docs/observability/README.md +47 -0
  90. package/docs/observability/README.zh-CN.md +37 -0
  91. package/docs/project-refresh.md +9 -0
  92. package/docs/project-refresh.zh-CN.md +8 -0
  93. package/docs/provider-retry.md +35 -0
  94. package/docs/release-workflow.md +124 -20
  95. package/docs/release-workflow.zh-CN.md +103 -14
  96. package/docs/roles-and-configuration.md +30 -0
  97. package/docs/roles-and-configuration.zh-CN.md +20 -0
  98. package/docs/task-delivery.md +109 -0
  99. package/docs/task-delivery.zh-CN.md +80 -0
  100. package/docs/task-discovery.md +9 -0
  101. package/docs/task-discovery.zh-CN.md +6 -0
  102. package/docs/testing/verification-levels.md +29 -11
  103. package/docs/testing/verification-levels.zh-CN.md +25 -7
  104. package/i18n/README.zh-CN.md +2 -0
  105. package/package.json +1 -1
  106. package/skills/yui-leader/SKILL.md +11 -0
  107. package/skills/yui-leader/references/execution.md +22 -0
  108. package/skills/yui-leader/references/planning.md +6 -2
  109. package/skills/yui-operator/SKILL.md +45 -13
  110. package/skills/yui-operator/references/task-delivery.md +99 -0
  111. package/skills/yui-runtime/SKILL.md +5 -3
  112. package/skills/yui-runtime/references/publication.md +56 -5
  113. package/skills/yui-runtime/references/recovery.md +12 -2
  114. package/dist/runtime/agentHostCompatibility.js +0 -127
@@ -7,7 +7,7 @@ import WebSocket from "ws";
7
7
  import { YUI_VERSION } from "../version.js";
8
8
  import { AcpStructuredProviderSession } from "./acpSession.js";
9
9
  import { acpDesiredSessionConfiguration } from "./acpSessionConfiguration.js";
10
- import { serializeAgentErrorRaw } from "./agentError.js";
10
+ import { providerDeliveryFailureFrom, serializeAgentErrorRaw } from "./agentError.js";
11
11
  import { unsupportedAgentRunConfiguration } from "./agentRunConfiguration.js";
12
12
  import { codexAppServerErrorIsMissing, CodexAppServerRequestError, CodexAppServerRuntime, codexClientInitialization, codexGoalNotification, codexTurnInput, codexTurnOutput } from "./codexAppServerRuntime.js";
13
13
  import { JsonLineChannel, PROVIDER_MESSAGE_MAX_BYTES, terminateProcessGroup } from "./jsonLineChannel.js";
@@ -65,6 +65,7 @@ export async function startStructuredProviderSession(payload, input = {}) {
65
65
  ? {}
66
66
  : { nativeSessionId: control.nativeSessionId }),
67
67
  ...(input.onTerminal === undefined ? {} : { onTerminal: input.onTerminal }),
68
+ ...(input.onDiagnostic === undefined ? {} : { onDiagnostic: input.onDiagnostic }),
68
69
  mirror
69
70
  });
70
71
  return Object.freeze({ session });
@@ -79,7 +80,7 @@ export async function startStructuredProviderSession(payload, input = {}) {
79
80
  goal: opened.goal
80
81
  });
81
82
  }
82
- const session = await ClaudeStructuredProviderSession.open(child, exit, processInstanceId, control, input.onAccepted, input.onTerminal, input.onGoal, input.onActivity, mirror);
83
+ const session = await ClaudeStructuredProviderSession.open(child, exit, processInstanceId, control, input.onAccepted, input.onTerminal, input.onGoal, input.onActivity, input.onDiagnostic, mirror);
83
84
  return Object.freeze({ session });
84
85
  }
85
86
  catch (error) {
@@ -325,6 +326,7 @@ class CodexStructuredProviderSession {
325
326
  onGoal;
326
327
  onInput;
327
328
  onActivity;
329
+ mirror;
328
330
  nativeAccountHome;
329
331
  #turnAttempts = new Map();
330
332
  adapterId = "codex";
@@ -335,7 +337,7 @@ class CodexStructuredProviderSession {
335
337
  #bufferedStarts = [];
336
338
  #bufferedTerminals = [];
337
339
  #bufferedActivities = [];
338
- constructor(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, nativeAccountHome) {
340
+ constructor(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, mirror, nativeAccountHome) {
339
341
  this.child = child;
340
342
  this.exit = exit;
341
343
  this.processInstanceId = processInstanceId;
@@ -346,6 +348,7 @@ class CodexStructuredProviderSession {
346
348
  this.onGoal = onGoal;
347
349
  this.onInput = onInput;
348
350
  this.onActivity = onActivity;
351
+ this.mirror = mirror;
349
352
  this.nativeAccountHome = nativeAccountHome;
350
353
  }
351
354
  static async open(child, exit, processInstanceId, payload, control, onStarted, onTerminal, onGoal, onInput, onActivity, mirror) {
@@ -387,7 +390,7 @@ class CodexStructuredProviderSession {
387
390
  name: control.sessionTitle
388
391
  });
389
392
  }
390
- const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, optionalId(initialized.codexHome));
393
+ const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, mirror, optionalId(initialized.codexHome));
391
394
  session.#activeTurnId = resumedActiveTurnId;
392
395
  const ownedTurn = control.kind === "restore" ? control.ownedTurn : undefined;
393
396
  session.#clientOwnedTurnId = ownedTurn?.turnId;
@@ -445,9 +448,21 @@ class CodexStructuredProviderSession {
445
448
  #observeMessage(message, emit) {
446
449
  const method = typeof message.method === "string" ? message.method : "";
447
450
  const params = object(message.params) ?? {};
448
- if (optionalId(params.threadId) !== this.conversationId)
451
+ const threadId = optionalId(params.threadId);
452
+ if (threadId !== this.conversationId) {
453
+ if (threadId === undefined && (method === "turn/started" || method === "turn/completed")) {
454
+ this.mirror("stderr", `Codex protocol: ${method} has no thread identity; ignored.\n`);
455
+ }
449
456
  return undefined;
450
- const goal = codexGoalNotification({ method, params });
457
+ }
458
+ let goal;
459
+ try {
460
+ goal = codexGoalNotification({ method, params });
461
+ }
462
+ catch {
463
+ this.mirror("stderr", "Codex protocol: invalid Goal notification; ignored.\n");
464
+ return undefined;
465
+ }
451
466
  if (goal !== undefined) {
452
467
  if (emit)
453
468
  this.onGoal?.(goal);
@@ -456,17 +471,20 @@ class CodexStructuredProviderSession {
456
471
  if (emit && (method === "item/started" || method === "item/completed"
457
472
  || method === "item/agentMessage/delta" || method === "item/reasoning/summaryTextDelta")) {
458
473
  const item = object(params.item);
459
- const id = optionalId(item?.id) ?? optionalId(params.itemId);
474
+ const delta = method === "item/agentMessage/delta" || method === "item/reasoning/summaryTextDelta";
475
+ const id = optionalId(delta ? params.itemId : item?.id);
460
476
  const nativeTurnId = optionalId(params.turnId);
461
- const tool = ["commandExecution", "mcpToolCall", "dynamicToolCall", "fileChange"].includes(String(item?.type));
462
- const model = ["agentMessage", "reasoning"].includes(String(item?.type)) || method.endsWith("Delta") || method.endsWith("/delta");
463
- if (id !== undefined && nativeTurnId !== undefined && (tool || model)) {
477
+ const tool = typeof item?.type === "string"
478
+ && ["commandExecution", "mcpToolCall", "dynamicToolCall", "fileChange"].includes(item.type);
479
+ const model = delta || item?.type === "agentMessage" || item?.type === "reasoning";
480
+ const phase = model ? "model" : tool ? codexToolPhase(item, method === "item/started") : undefined;
481
+ if (tool && phase === undefined) {
482
+ this.mirror("stderr", "Codex protocol: tool item has no valid lifecycle status; ignored.\n");
483
+ }
484
+ if (id !== undefined && nativeTurnId !== undefined && phase !== undefined) {
464
485
  const activity = {
465
486
  conversationId: this.conversationId, nativeSessionId: this.conversationId,
466
- nativeTurnId, id, observedAt: new Date().toISOString(),
467
- phase: model ? "model" : method === "item/started" ? "started"
468
- : item?.status === "failed" || (typeof item?.exitCode === "number" && item.exitCode !== 0)
469
- ? "failed" : "completed"
487
+ nativeTurnId, id, observedAt: new Date().toISOString(), phase
470
488
  };
471
489
  if (this.#submissionPending)
472
490
  this.#bufferedActivities.push(activity);
@@ -486,10 +504,10 @@ class CodexStructuredProviderSession {
486
504
  return undefined;
487
505
  }
488
506
  if (method === "turn/started") {
489
- const turnId = nestedId(params, "turn") ?? optionalId(params.turnId);
490
- if (turnId !== undefined) {
507
+ const turn = object(params.turn);
508
+ const turnId = optionalId(turn?.id);
509
+ if (turnId !== undefined && turn?.status === "inProgress") {
491
510
  this.#activeTurnId = turnId;
492
- const turn = object(params.turn) ?? {};
493
511
  const input = codexTurnInput(turn);
494
512
  const started = {
495
513
  conversationId: this.conversationId,
@@ -503,14 +521,20 @@ class CodexStructuredProviderSession {
503
521
  else if (emit)
504
522
  this.#emitStarted(started);
505
523
  }
524
+ else {
525
+ this.mirror("stderr", "Codex protocol: turn/started has no valid Turn identity/status; ignored.\n");
526
+ }
506
527
  return undefined;
507
528
  }
508
529
  if (method !== "turn/completed")
509
530
  return undefined;
510
- const turn = object(params.turn) ?? {};
511
- const nativeTurnId = optionalId(turn.id) ?? optionalId(params.turnId);
512
- if (nativeTurnId === undefined)
531
+ const turn = object(params.turn);
532
+ const nativeTurnId = optionalId(turn?.id);
533
+ if (nativeTurnId === undefined || turn === null
534
+ || (turn.status !== "completed" && turn.status !== "failed" && turn.status !== "interrupted")) {
535
+ this.mirror("stderr", "Codex protocol: turn/completed has no valid terminal Turn identity/status; ignored.\n");
513
536
  return undefined;
537
+ }
514
538
  const status = turn.status === "failed"
515
539
  ? "failed"
516
540
  : turn.status === "interrupted" ? "cancelled" : "completed";
@@ -680,6 +704,7 @@ class ClaudeStructuredProviderSession {
680
704
  conversationId;
681
705
  channel;
682
706
  onGoal;
707
+ mirror;
683
708
  adapterId = "claude";
684
709
  get ownedProcessId() { return this.child.pid; }
685
710
  #activeAttemptId;
@@ -689,22 +714,33 @@ class ClaudeStructuredProviderSession {
689
714
  #openToolIds = new Set();
690
715
  #resultAttempts = new Map();
691
716
  #lastGoalKey;
692
- constructor(child, exit, processInstanceId, conversationId, channel, onGoal) {
717
+ constructor(child, exit, processInstanceId, conversationId, channel, onGoal, mirror) {
693
718
  this.child = child;
694
719
  this.exit = exit;
695
720
  this.processInstanceId = processInstanceId;
696
721
  this.conversationId = conversationId;
697
722
  this.channel = channel;
698
723
  this.onGoal = onGoal;
724
+ this.mirror = mirror;
699
725
  }
700
- static async open(child, exit, processInstanceId, control, onAccepted, onTerminal, onGoal, onActivity, mirror) {
701
- const channel = new JsonLineChannel(child, mirror);
726
+ static async open(child, exit, processInstanceId, control, onAccepted, onTerminal, onGoal, onActivity, onDiagnostic, mirror) {
702
727
  const nativeSessionId = control.nativeSessionId;
703
728
  if (nativeSessionId === undefined) {
704
729
  throw new Error("Managed Claude launch requires a preallocated native Session id.");
705
730
  }
706
- const session = new ClaudeStructuredProviderSession(child, exit, processInstanceId, nativeSessionId, channel, onGoal);
707
- channel.onMessage((message) => session.#receive(message, onTerminal, onAccepted, onActivity));
731
+ let receivingAttemptId;
732
+ const channel = new JsonLineChannel(child, mirror, error => onDiagnostic?.({
733
+ nativeSessionId,
734
+ ...((receivingAttemptId ?? session.#activeAttemptId) === undefined
735
+ ? {} : { attemptId: receivingAttemptId ?? session.#activeAttemptId }),
736
+ failure: providerDeliveryFailureFrom(error, { phase: "turn-reconcile", inputDisposition: "unknown" })
737
+ }));
738
+ const session = new ClaudeStructuredProviderSession(child, exit, processInstanceId, nativeSessionId, channel, onGoal, mirror);
739
+ channel.onMessage((message) => {
740
+ receivingAttemptId = session.#activeAttemptId;
741
+ session.#receive(message, onTerminal, onAccepted, onActivity);
742
+ receivingAttemptId = undefined;
743
+ });
708
744
  // This client owns the whole Claude execution process, unlike a Codex
709
745
  // proxy. An exit without a result ends the exact local input as failure,
710
746
  // not as continuing work merely because the supervising Host is alive.
@@ -855,24 +891,35 @@ class ClaudeStructuredProviderSession {
855
891
  }
856
892
  return;
857
893
  }
858
- if (message.type !== "result"
859
- || optionalId(message.session_id) !== this.conversationId)
894
+ if (message.type !== "result")
860
895
  return;
896
+ const sessionId = optionalId(message.session_id);
897
+ if (sessionId !== this.conversationId) {
898
+ if (sessionId === undefined)
899
+ this.mirror("stderr", "Claude protocol: result has no Session identity; ignored.\n");
900
+ return;
901
+ }
861
902
  // A result UUID identifies this message, not the Provider execution.
862
903
  // Retain its local association so a delayed duplicate cannot be assigned
863
904
  // to a successor request on the same long-lived process.
864
905
  const resultId = optionalId(message.uuid);
865
- const priorAttempt = resultId === undefined ? undefined : this.#resultAttempts.get(resultId);
906
+ const subtype = message.subtype;
907
+ const failed = subtype === "success" ? message.is_error
908
+ : ["error_during_execution", "error_max_turns", "error_max_budget_usd",
909
+ "error_max_structured_output_retries"].includes(String(subtype)) ? true : undefined;
910
+ if (resultId === undefined || typeof message.is_error !== "boolean" || failed === undefined) {
911
+ this.mirror("stderr", "Claude protocol: result has no valid identity/status; ignored.\n");
912
+ return;
913
+ }
914
+ const priorAttempt = this.#resultAttempts.get(resultId);
866
915
  const attemptId = priorAttempt ?? this.#activeAttemptId;
867
916
  if (attemptId === undefined)
868
917
  return;
869
- if (resultId !== undefined)
870
- this.#resultAttempts.set(resultId, attemptId);
918
+ this.#resultAttempts.set(resultId, attemptId);
871
919
  if (attemptId === this.#activeAttemptId) {
872
920
  this.#activeAttemptId = undefined;
873
921
  this.#openToolIds.clear();
874
922
  }
875
- const failed = message.is_error === true || message.subtype === "error_during_execution";
876
923
  const result = typeof message.result === "string" && message.result.length > 0
877
924
  ? message.result
878
925
  : providerErrorEvidence(message).error;
@@ -891,64 +938,44 @@ class ClaudeStructuredProviderSession {
891
938
  });
892
939
  }
893
940
  }
941
+ function codexToolPhase(item, started) {
942
+ if (started)
943
+ return item.status === "inProgress" ? "started" : undefined;
944
+ if (item.status === "failed")
945
+ return "failed";
946
+ if (item.status === "declined"
947
+ && (item.type === "commandExecution" || item.type === "fileChange"))
948
+ return "failed";
949
+ if (item.status !== "completed")
950
+ return undefined;
951
+ if (item.type === "commandExecution" && typeof item.exitCode === "number" && item.exitCode !== 0) {
952
+ return "failed";
953
+ }
954
+ if (item.type === "dynamicToolCall" && item.success === false)
955
+ return "failed";
956
+ return "completed";
957
+ }
894
958
  function claudeActiveGoal(message, conversationId) {
895
- if (!Object.hasOwn(message, "active_goal"))
959
+ if (message.type !== "active_goal" || optionalId(message.session_id) !== conversationId)
896
960
  return undefined;
897
- if (message.active_goal === null)
961
+ if (message.value === null)
898
962
  return null;
899
- const raw = object(message.active_goal);
963
+ const raw = object(message.value);
900
964
  if (raw === null)
901
965
  return undefined;
902
- const objective = typeof raw.objective === "string" ? raw.objective.trim() : "";
966
+ const objective = typeof raw.condition === "string" ? raw.condition.trim() : "";
903
967
  if (objective.length === 0)
904
968
  return undefined;
905
- const status = providerGoalStatus(raw.status);
906
- if (status === undefined)
907
- return undefined;
908
- const tokenBudget = positiveOptionalInteger(raw.token_budget ?? raw.tokenBudget);
909
969
  return Object.freeze({
910
970
  conversationId,
911
- status,
971
+ // Claude emits a live goal or clears it. There is no Codex-style Goal
972
+ // status/budget/Turn field on this event, and clearing is not success.
973
+ status: "active",
912
974
  objective,
913
- updatedAt: providerTimestamp(raw.updated_at ?? raw.updatedAt),
914
- ...(optionalId(raw.turn_id ?? raw.turnId) === undefined
915
- ? {}
916
- : { nativeTurnId: optionalId(raw.turn_id ?? raw.turnId) }),
917
- ...(tokenBudget === undefined ? {} : { tokenBudget })
975
+ // set_at is creation time, not a last-update timestamp.
976
+ updatedAt: new Date().toISOString()
918
977
  });
919
978
  }
920
- function providerGoalStatus(value) {
921
- switch (value) {
922
- case "active":
923
- case "paused":
924
- case "blocked":
925
- case "complete":
926
- return value;
927
- case "usageLimited":
928
- case "usage_limited":
929
- case "usage-limited":
930
- return "usage-limited";
931
- case "budgetLimited":
932
- case "budget_limited":
933
- case "budget-limited":
934
- return "budget-limited";
935
- default:
936
- return undefined;
937
- }
938
- }
939
- function providerTimestamp(value) {
940
- if (typeof value === "string" && Number.isFinite(Date.parse(value))) {
941
- return new Date(value).toISOString();
942
- }
943
- if (Number.isSafeInteger(value) && value >= 0) {
944
- const epoch = value;
945
- return new Date(epoch < 1_000_000_000_000 ? epoch * 1_000 : epoch).toISOString();
946
- }
947
- return new Date().toISOString();
948
- }
949
- function positiveOptionalInteger(value) {
950
- return Number.isSafeInteger(value) && value > 0 ? value : undefined;
951
- }
952
979
  function childExit(child, processInstanceId) {
953
980
  return new Promise((resolvePromise, reject) => {
954
981
  child.once("error", reject);
@@ -971,10 +998,8 @@ function object(value) {
971
998
  : null;
972
999
  }
973
1000
  function optionalId(value) {
974
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
975
- }
976
- function nestedId(value, key) {
977
- return optionalId(object(value[key])?.id);
1001
+ return typeof value === "string" && !value.includes("\0") && value.length > 0
1002
+ && value.trim() === value ? value : undefined;
978
1003
  }
979
1004
  function providerErrorEvidence(value) {
980
1005
  const error = object(value);
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { serializeRunInputEnvelope } from "../context/runInputContract.js";
3
+ import { readRunContextSnapshot } from "../context/runContextPack.js";
3
4
  import { roleSessionMayContinue, sameEffectiveLaunch } from "../executor/effectiveLaunch.js";
4
5
  import { RuntimeLifecycleBusyError } from "../runtime/lifecycleReservation.js";
5
6
  import { managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
@@ -121,6 +122,9 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
121
122
  let prepared;
122
123
  let submitted = false;
123
124
  try {
125
+ // Validate the dispatch contract before starting any Provider resources.
126
+ readRunContextSnapshot(store, run);
127
+ const text = serializeRunInputEnvelope(runInputEnvelope(run));
124
128
  const nativeSessionId = mode === "resume"
125
129
  ? requireResumeSession(role, run, existingSession)
126
130
  : undefined;
@@ -172,7 +176,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
172
176
  const outcome = await delivery.sendOnce({
173
177
  delivery: ready,
174
178
  receiptId: attemptId,
175
- text: serializeRunInputEnvelope(runInputEnvelope(run))
179
+ text
176
180
  });
177
181
  if (outcome.status === "pending") {
178
182
  // The original Host request is still in flight. Its durable submitting
@@ -1,6 +1,6 @@
1
1
  import { createOperatorBatchPresentation } from "../interaction/operatorPresentation.js";
2
2
  import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
3
- export async function processOperatorInputNotifications(store, delivery, selection, now = new Date()) {
3
+ export async function processOperatorInputNotifications(store, selection, now = new Date()) {
4
4
  if (selection !== undefined && !selection.full && !selection.operator)
5
5
  return [];
6
6
  const targetMailbox = { kind: "operator" };
@@ -35,30 +35,20 @@ export async function processOperatorInputNotifications(store, delivery, selecti
35
35
  store.completeWorkMailbox(targetMailbox, processing.batchId);
36
36
  return [];
37
37
  }
38
- const target = store.getOperatorDeliveryTarget();
39
- if (target === null || delivery.notifyOperatorInputOnce === undefined) {
40
- const reason = target === null ? "operator-unavailable" : "delivery-unsupported";
41
- store.releaseWorkMailbox(targetMailbox, processing.batchId);
42
- return attentions.map((attention) => skipped(attention, reason));
43
- }
44
38
  try {
45
39
  const presentation = createOperatorBatchPresentation(processing.batchId, attentions.map(toPresentationItem));
46
- const outcome = await delivery.notifyOperatorInputOnce({
47
- ...target,
40
+ const outcome = store.queueOperatorNotification({
41
+ batchId: processing.batchId,
48
42
  receiptId: presentation.receiptId,
49
43
  text: presentation.text
50
- });
51
- if (outcome === "unavailable" || outcome === "not-ready") {
44
+ }, now);
45
+ if (outcome.status === "unavailable") {
52
46
  store.releaseWorkMailbox(targetMailbox, processing.batchId);
53
- const reason = outcome === "unavailable" ? "operator-unavailable" : "operator-not-ready";
54
- return attentions.map((attention) => skipped(attention, reason));
47
+ return attentions.map((attention) => skipped(attention, "operator-unavailable"));
55
48
  }
56
- if (outcome === "sent")
57
- store.markOperatorRunStarted(now);
58
- store.completeWorkMailbox(targetMailbox, processing.batchId);
59
49
  return attentions.map((attention) => ({
60
50
  ...attentionIdentity(attention),
61
- status: outcome
51
+ ...outcome
62
52
  }));
63
53
  }
64
54
  catch (error) {
@@ -1,7 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
3
3
  import Database from "better-sqlite3";
4
- import { inspectAgentHostCompatibility, describeAgentHostUpgradeBlockers } from "../../runtime/agentHostCompatibility.js";
5
4
  import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation } from "../../runtime/runtimeObservation.js";
6
5
  import { RUNTIME_PROCESS_EXIT_TASK_EVENT, validateRuntimeProcessExitObservation } from "../../runtime/processExitObservation.js";
7
6
  import { SqliteTaskStore } from "../sqliteStore.js";
@@ -40,15 +39,6 @@ export async function runStorageUpgrade(options) {
40
39
  ? classification.classification.blocker.action
41
40
  : "Use a compatible Yui release.");
42
41
  }
43
- const incompatibleHosts = await inspectAgentHostCompatibility(options.home);
44
- if (incompatibleHosts.length > 0) {
45
- return {
46
- ...blocked(state.status === "current" ? currentClassification() : migratableClassification(state.currentVersion), "host-compatibility", `Existing Agent Hosts cannot safely use the target Controller/storage:\n${describeAgentHostUpgradeBlockers(incompatibleHosts)}`, "Keep the current Controller, Home and Host processes unchanged. Inspect the named Sessions and original pending "
47
- + "inputs/results; let current work settle, then use explicitly authorized Session replacement/cleanup at a safe "
48
- + "boundary before retrying the upgrade. This target cannot hot-patch legacy Host memory."),
49
- hosts: incompatibleHosts
50
- };
51
- }
52
42
  if (state.status === "current") {
53
43
  try {
54
44
  validateCurrentStore(options.home);
@@ -91,24 +91,7 @@ export function projectNextAction(facts) {
91
91
  ref: refs[1]
92
92
  }
93
93
  ],
94
- recommendedCommand: `yui task work edit ${task.id}/${dependencyIssue.workItemId} --clear-dependencies`
95
- });
96
- }
97
- const draftWork = selectOpenWorkItem(facts.workItems);
98
- if (draftWork.kind === "blocked") {
99
- const refs = [
100
- ref("work-item", draftWork.itemId),
101
- ref("work-item", draftWork.blockedBy)
102
- ];
103
- return buildAction(facts, {
104
- kind: "repair-protocol-inconsistency",
105
- reason: `Draft Work Item ${draftWork.itemId} depends on ${draftWork.blockedBy}, which is missing, retired, or not completed. Edit the Draft before activation.`,
106
- refs,
107
- conflicts: refs,
108
- preconditions: [
109
- { fact: `Draft dependency ${draftWork.blockedBy} is valid`, satisfied: false, ref: refs[1] }
110
- ],
111
- recommendedCommand: `yui task work edit ${task.id}/${draftWork.itemId} --clear-dependencies`
94
+ ...dependencyJudgment(task.id, dependencyIssue.workItemId)
112
95
  });
113
96
  }
114
97
  return buildAction(facts, {
@@ -323,18 +306,24 @@ export function projectNextAction(facts) {
323
306
  }
324
307
  const openWork = selectOpenWorkItem(facts.workItems);
325
308
  if (openWork?.kind === "blocked") {
309
+ const issue = draftWorkItemDependencyIssue(facts.workItems);
310
+ const itemId = issue?.workItemId ?? openWork.itemId;
311
+ const dependencyId = issue?.dependencyId ?? openWork.blockedBy;
326
312
  const refs = [
327
- ref("work-item", openWork.itemId),
328
- ref("work-item", openWork.blockedBy)
313
+ ref("work-item", itemId),
314
+ ref("work-item", dependencyId)
329
315
  ];
330
316
  return buildAction(facts, {
331
317
  kind: "repair-protocol-inconsistency",
332
- reason: `Work Item ${openWork.itemId} depends on ${openWork.blockedBy}, which is not completed or available.`,
318
+ reason: issue?.kind === "cycle"
319
+ ? `Work Item dependency cycle includes ${itemId}/${dependencyId}.`
320
+ : `Work Item ${itemId} depends on ${dependencyId}, which is ${issue === undefined ? "not completed" : "missing or retired"}.`,
333
321
  refs,
334
322
  conflicts: refs,
335
323
  preconditions: [
336
- { fact: `Dependency ${openWork.blockedBy} is completed`, satisfied: false, ref: refs[1] }
337
- ]
324
+ { fact: `Dependency ${dependencyId} is valid and completed`, satisfied: false, ref: refs[1] }
325
+ ],
326
+ ...dependencyJudgment(task.id, itemId)
338
327
  });
339
328
  }
340
329
  if (openWork?.kind === "ready") {
@@ -769,6 +758,27 @@ function reviewRoundConflict(round, activeRuns) {
769
758
  }
770
759
  return null;
771
760
  }
761
+ function dependencyJudgment(taskId, workItemId) {
762
+ return {
763
+ recommendedCommand: `yui task work show ${taskId}/${workItemId}`,
764
+ alternatives: [
765
+ {
766
+ kind: "inspect-dependencies",
767
+ reason: "Read the related requirements and dependency records before choosing a repair.",
768
+ recommendedCommand: `yui task work list ${taskId}`
769
+ },
770
+ {
771
+ kind: "revise-dependencies",
772
+ reason: "If the intended dependency graph is wrong, edit the affected WorkItem with --after for the complete intended set, preserving unrelated valid dependencies."
773
+ },
774
+ {
775
+ kind: "retire-obsolete-work",
776
+ reason: "Only if the requirement is no longer needed, deliberately retire it and reconcile its dependents."
777
+ }
778
+ ],
779
+ judgmentRequired: "The records identify a blocked dependency, not which business requirement or edge is wrong. Determine the intended graph from Task requirements; do not infer permission to delete dependencies."
780
+ };
781
+ }
772
782
  function selectOpenWorkItem(workItems) {
773
783
  const byId = new Map(workItems.map((item) => [item.id, item]));
774
784
  const openItems = workItems.filter((item) => OPEN_WORK_ITEM_STATUSES.has(item.status));
package/dist/task/task.js CHANGED
@@ -304,7 +304,8 @@ export function reopenTask(task, now) {
304
304
  }
305
305
  : {}),
306
306
  status: "active",
307
- executionGate: { state: "enabled" },
307
+ // Reopening intent does not lift an independent execution stop. Only the
308
+ // explicit start operation may do that after its authority/cleanup checks.
308
309
  updatedAt: now.toISOString()
309
310
  });
310
311
  }
@@ -9,6 +9,7 @@ import { MARKDOWN_SCRIPT } from "./client/markdown.js";
9
9
  import { THEME_SCRIPT } from "./client/theme.js";
10
10
  import { VIEW_SCRIPT } from "./client/view.js";
11
11
  import { TASK_SURFACE_SCRIPT } from "./client/taskSurface.js";
12
+ import { TASK_SUMMARY_SCRIPT } from "./client/taskSummary.js";
12
13
  import { DASHBOARD_HTML } from "./shell.js";
13
14
  import { FONT_FACE_STYLES } from "./fonts.js";
14
15
  import { FONT_WOFF2_BASE64 } from "./fontData.js";
@@ -35,6 +36,7 @@ export const WEB_ASSETS = Object.freeze({
35
36
  "/assets/js/components.js": { contentType: "text/javascript; charset=utf-8", body: COMPONENTS_SCRIPT },
36
37
  "/assets/js/view.js": { contentType: "text/javascript; charset=utf-8", body: VIEW_SCRIPT },
37
38
  "/assets/js/task-surface.js": { contentType: "text/javascript; charset=utf-8", body: TASK_SURFACE_SCRIPT },
39
+ "/assets/js/task-summary.js": { contentType: "text/javascript; charset=utf-8", body: TASK_SUMMARY_SCRIPT },
38
40
  "/assets/app.js": { contentType: "text/javascript; charset=utf-8", body: APP_SCRIPT },
39
41
  ...fontAssets(),
40
42
  "/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", "text/javascript; charset=utf-8"),