@zq-silk/yui 2.7.1 → 2.8.0

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 (90) hide show
  1. package/dist/cli/commandCatalog.js +8 -7
  2. package/dist/cli/updateCommand.js +3 -0
  3. package/dist/commands/configCommands.js +1 -0
  4. package/dist/commands/globalRoleCommands.js +42 -35
  5. package/dist/commands/inboxCommands.js +60 -40
  6. package/dist/commands/taskCommands.js +7 -3
  7. package/dist/commands/taskRoleRuntimeStatus.js +2 -0
  8. package/dist/context/runContextPack.js +48 -3
  9. package/dist/context/taskContext.js +36 -12
  10. package/dist/controlPlaneCli.js +46 -12
  11. package/dist/controller/controller.js +5 -0
  12. package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
  13. package/dist/controller/globalInputDelivery.js +17 -5
  14. package/dist/controller/globalRuntimeAttention.js +135 -5
  15. package/dist/controller/runtime.js +4 -0
  16. package/dist/controller/structuredProviderObservation.js +51 -15
  17. package/dist/controller/taskHostTerminalRecovery.js +33 -0
  18. package/dist/executor/fileRoleLaunchPlanner.js +4 -9
  19. package/dist/message/inboxReceipt.js +30 -0
  20. package/dist/message/taskInboxOwnership.js +25 -3
  21. package/dist/models/modelManagement.js +469 -0
  22. package/dist/models/modelManagementConfig.js +46 -0
  23. package/dist/models/modelManagementFiles.js +90 -0
  24. package/dist/models/modelManagementHttp.js +90 -0
  25. package/dist/models/modelManagementTypes.js +1 -0
  26. package/dist/output/boundedRead.js +43 -14
  27. package/dist/runtime/agentEndpointIdentity.js +8 -4
  28. package/dist/runtime/agentEndpointOwnership.js +5 -6
  29. package/dist/runtime/agentHost.js +62 -10
  30. package/dist/runtime/agentHostImplementation.js +18 -0
  31. package/dist/runtime/managedCaller.js +25 -1
  32. package/dist/runtime/managedIdentity.js +1 -1
  33. package/dist/runtime/providerRetry.js +19 -1
  34. package/dist/runtime/sessionCaller.js +53 -0
  35. package/dist/storage/contextRecords.js +28 -0
  36. package/dist/storage/sqliteSchema.js +10 -0
  37. package/dist/storage/sqliteStore.js +9 -5
  38. package/dist/storage/storageVersions.js +1 -1
  39. package/dist/storage/taskStore.js +4 -0
  40. package/dist/task/completionReadiness.js +3 -3
  41. package/dist/web/assets/assetManifest.js +28 -2
  42. package/dist/web/assets/client/app/manage.js +6 -0
  43. package/dist/web/assets/client/app/models.js +20 -0
  44. package/dist/web/assets/client/app/settings.js +41 -9
  45. package/dist/web/assets/client/i18n/en.js +180 -2
  46. package/dist/web/assets/client/i18n/zh-CN.js +178 -2
  47. package/dist/web/assets/client/index.js +8 -0
  48. package/dist/web/assets/client/layout/manageShell.js +94 -0
  49. package/dist/web/assets/client/lib/api.js +13 -1
  50. package/dist/web/assets/client/lib/i18n.js +5 -3
  51. package/dist/web/assets/client/views/dock/conversation.js +231 -80
  52. package/dist/web/assets/client/views/models.js +777 -0
  53. package/dist/web/assets/client/views/skills.js +22 -15
  54. package/dist/web/assets/shared/icons.js +2 -0
  55. package/dist/web/assets/shared/manageNav.js +13 -0
  56. package/dist/web/assets/shell/manage.js +54 -0
  57. package/dist/web/assets/shell/models.js +7 -0
  58. package/dist/web/assets/shell/settings.js +1 -35
  59. package/dist/web/assets/shell/settingsContent.js +30 -0
  60. package/dist/web/assets/shell/sidebar.js +1 -0
  61. package/dist/web/assets/styles/index.js +6 -0
  62. package/dist/web/assets/styles/views/conversation.js +133 -0
  63. package/dist/web/assets/styles/views/manage.js +71 -0
  64. package/dist/web/assets/styles/views/models.js +129 -0
  65. package/dist/web/assets/styles/views/settings.js +28 -14
  66. package/dist/web/assets/styles/views/sidebar.js +1 -0
  67. package/dist/web/webServer.js +59 -9
  68. package/docs/cli-information-contract.md +42 -11
  69. package/docs/cli-information-contract.zh-CN.md +28 -7
  70. package/docs/managed-turn-and-session-runtime.md +47 -1
  71. package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
  72. package/docs/release-workflow.md +58 -7
  73. package/docs/release-workflow.zh-CN.md +39 -4
  74. package/docs/storage-baseline.md +6 -1
  75. package/docs/storage-baseline.zh-CN.md +4 -1
  76. package/docs/testing/review-evaluation.md +148 -0
  77. package/docs/testing/review-evaluation.zh-CN.md +117 -0
  78. package/docs/testing/verification-levels.md +6 -2
  79. package/docs/testing/verification-levels.zh-CN.md +3 -0
  80. package/package.json +1 -1
  81. package/skills/yui-leader/VERSION +1 -1
  82. package/skills/yui-leader/references/execution.md +29 -0
  83. package/skills/yui-leader/references/replicated-execution.md +7 -0
  84. package/skills/yui-operator/SKILL.md +5 -1
  85. package/skills/yui-operator/VERSION +1 -1
  86. package/skills/yui-reviewer/SKILL.md +35 -0
  87. package/skills/yui-reviewer/VERSION +1 -1
  88. package/skills/yui-runtime/SKILL.md +53 -7
  89. package/skills/yui-runtime/VERSION +1 -1
  90. package/skills/yui-runtime/references/recovery.md +24 -5
@@ -10,10 +10,15 @@ import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
10
10
  import { AGENT_HOST_EVENT_PROTOCOL } from "../runtime/agentHostProtocol.js";
11
11
  import { redactAgentErrorText } from "../runtime/agentError.js";
12
12
  let structuredSequence = 0;
13
- let deliveryFailure;
13
+ const deliveryFailures = new Map();
14
14
  // Only live diagnostics for this process's immutable files, not another queue.
15
15
  // No payload, retry, or authoritative delivery state is kept in this map.
16
16
  const emittedEvents = new Map();
17
+ // Only facts whose immutable Inbox write has not succeeded. The original
18
+ // process must retain these bytes during a filesystem fault: another direct
19
+ // native turn can arrive without Yui admission and replace a last-turn cache.
20
+ // No new durable queue or execution retry; discard only after durable enqueue.
21
+ const unpersistedEvents = new Map();
17
22
  /** Retained in the existing Host Inbox; it never settles a Turn or a resource. */
18
23
  export async function publishStructuredProviderDiagnostic(input) {
19
24
  const diagnostic = input.diagnostic;
@@ -44,6 +49,7 @@ export async function publishStructuredProviderDiagnostic(input) {
44
49
  /** A live transport observation, independent of Provider state and Run truth. */
45
50
  export function structuredProviderEventDelivery(home, environment, nativeSessionId) {
46
51
  try {
52
+ const deliveryFailure = deliveryFailures.get(deliveryKey(home, environment));
47
53
  const inbox = new FileRuntimeEventInbox(home);
48
54
  const pending = [];
49
55
  for (const [id, entry] of emittedEvents) {
@@ -57,16 +63,39 @@ export function structuredProviderEventDelivery(home, environment, nativeSession
57
63
  && (nativeSessionId === undefined || entry.nativeSessionId === nativeSessionId))
58
64
  pending.push(entry);
59
65
  }
66
+ const retained = pendingPersistence(home, environment);
60
67
  return {
61
- pending: pending.length,
62
- pendingTerminals: pending.filter(event => event.terminal).length,
68
+ pending: pending.length + retained.length,
69
+ unpersisted: retained.length,
70
+ pendingTerminals: pending.filter(event => event.terminal).length
71
+ + retained.filter(event => ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)).length,
63
72
  ...(deliveryFailure === undefined ? {} : { failure: deliveryFailure })
64
73
  };
65
74
  }
66
75
  catch (error) {
67
- return { pending: 0, pendingTerminals: 0, failure: deliveryFault("persistence", error) };
76
+ const retained = pendingPersistence(home, environment);
77
+ return {
78
+ pending: retained.length, unpersisted: retained.length,
79
+ pendingTerminals: retained.filter(event => ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)).length,
80
+ failure: deliveryFault("persistence", error)
81
+ };
68
82
  }
69
83
  }
84
+ function deliveryKey(home, environment) {
85
+ return JSON.stringify([home, environment.YUI_TASK_ID, environment.YUI_ROLE, environment.YUI_AGENT_ID]);
86
+ }
87
+ function pendingPersistence(home, environment) {
88
+ return [...unpersistedEvents.values()].filter(event => event.home === home
89
+ && event.observation.fence.taskId === environment.YUI_TASK_ID
90
+ && event.observation.fence.roleName === environment.YUI_ROLE
91
+ && event.observation.fence.agentId === environment.YUI_AGENT_ID);
92
+ }
93
+ /** Retry only original native facts, under the original Host's control fence. */
94
+ export async function replayStructuredProviderPersistence(home, environment) {
95
+ const retained = pendingPersistence(home, environment).map(event => event.observation);
96
+ await persistAndApply(home, [], environment);
97
+ return retained;
98
+ }
70
99
  function deliveryFault(stage, error) {
71
100
  return {
72
101
  stage, detail: redactAgentErrorText(error instanceof Error ? error.message : String(error)),
@@ -412,28 +441,35 @@ function requireIdentity(value, label) {
412
441
  }
413
442
  async function persistAndApply(home, observations, environment, connection, startupRunId) {
414
443
  const inbox = new FileRuntimeEventInbox(home);
444
+ const key = deliveryKey(home, environment);
415
445
  const host = {
416
446
  protocol: AGENT_HOST_EVENT_PROTOCOL,
417
447
  adapterId: requireIdentity(environment.YUI_ADAPTER_ID, "Agent adapter id"),
418
448
  workspace: requireIdentity(environment.YUI_WORKSPACE, "Yui workspace"),
419
449
  ...(connection === undefined ? {} : { connection })
420
450
  };
421
- let events;
451
+ const source = { ...host, ...(startupRunId === undefined ? {} : { startupRunId }) };
452
+ for (const observation of observations) {
453
+ unpersistedEvents.set(observation.eventId, { home, observation, host: source });
454
+ }
455
+ const events = [];
422
456
  try {
423
457
  structuredProviderEventDelivery(home, environment); // prune ACKed local diagnostics
424
- events = observations.map(entry => inbox.enqueueObservation(entry, {
425
- ...host,
426
- ...(startupRunId === undefined ? {} : { startupRunId })
427
- }).event);
428
- for (const event of events)
458
+ for (const retained of pendingPersistence(home, environment)) {
459
+ const event = inbox.enqueueObservation(retained.observation, retained.host).event;
460
+ events.push(event);
461
+ unpersistedEvents.delete(retained.observation.eventId);
429
462
  emittedEvents.set(event.id, {
430
463
  home, taskId: event.taskId, roleName: event.observation.fence.roleName,
431
464
  nativeSessionId: event.observation.fence.nativeSessionId,
432
465
  terminal: ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)
433
466
  });
467
+ }
468
+ if (deliveryFailures.get(key)?.stage === "persistence")
469
+ deliveryFailures.delete(key);
434
470
  }
435
471
  catch (error) {
436
- deliveryFailure = deliveryFault("persistence", error);
472
+ deliveryFailures.set(key, deliveryFault("persistence", error));
437
473
  throw error;
438
474
  }
439
475
  // The immutable inbox is authoritative. During a release/update handover the
@@ -463,7 +499,7 @@ async function persistAndApply(home, observations, environment, connection, star
463
499
  return;
464
500
  // The native fact is already durable. An incompatible/refusing
465
501
  // Controller is a reporting fault, not Provider execution failure.
466
- deliveryFailure = deliveryFault("controller", error);
502
+ deliveryFailures.set(key, deliveryFault("controller", error));
467
503
  return;
468
504
  }
469
505
  // A fast Provider can accept the initial AgentRun before the scheduler call
@@ -472,12 +508,12 @@ async function persistAndApply(home, observations, environment, connection, star
472
508
  // `deferred` therefore means "retained for replay", not delivery failure.
473
509
  // The signal below schedules the replay after the transport transaction.
474
510
  if (result.outcome !== "applied" && result.outcome !== "deferred") {
475
- deliveryFailure = deliveryFault("controller", `Structured Provider observation was rejected: ${result.outcome ?? "unknown"}. Event: ${entry.id}`);
511
+ deliveryFailures.set(key, deliveryFault("controller", `Structured Provider observation was rejected: ${result.outcome ?? "unknown"}. Event: ${entry.id}`));
476
512
  return;
477
513
  }
478
514
  }
479
- if (deliveryFailure?.stage === "controller")
480
- deliveryFailure = undefined;
515
+ if (events.length > 0 && deliveryFailures.get(key)?.stage === "controller")
516
+ deliveryFailures.delete(key);
481
517
  await signalController(home, hostTaskId(environment), requireIdentity(environment.YUI_ROLE, "Role"));
482
518
  }
483
519
  /** Capture only identities the Host actually received; the current Controller
@@ -0,0 +1,33 @@
1
+ import { AGENT_HOST_CONTROL_PROTOCOL, reconcileAgentHostTurn } from "../runtime/agentHost.js";
2
+ import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
3
+ import { providerRetryPending } from "../runtime/providerRetry.js";
4
+ /** Existing full reconciliation pass only; no new timer, input or Session. */
5
+ export async function recoverTaskHostTerminals(home, store, onError, reconcile = reconcileAgentHostTurn) {
6
+ for (const candidate of store.listRuntimeSessionCandidates({ scope: "task" })) {
7
+ const owner = candidate.owner;
8
+ if (owner.scope !== "task")
9
+ continue;
10
+ const binding = store.getTaskRoleSessionSet(owner.taskId, owner.roleName)?.providerBinding;
11
+ const turn = binding?.run;
12
+ if (binding === undefined || binding === null || turn == null || providerRetryPending(binding)
13
+ || !["submitting", "accepted", "delivery-unknown"].includes(turn.status)
14
+ || binding.authority.owner !== "controller" || binding.authority.holderId === undefined)
15
+ continue;
16
+ try {
17
+ const result = await reconcile({ home, ...owner, control: {
18
+ protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "reconcile-turn",
19
+ nativeSessionId: currentProviderConversation(binding).conversationId,
20
+ attemptId: turn.attemptId, nativeTurnId: turn.nativeTurnId,
21
+ authority: { epoch: binding.authority.epoch, owner: "controller", holderId: binding.authority.holderId }
22
+ } });
23
+ if (result.outcome === "rejected" || result.reconciliation === "unsupported") {
24
+ throw new Error(result.failure?.detail
25
+ ?? "Host terminal reconciliation is unsupported; retain the original input and inspect Host/Inbox evidence.");
26
+ }
27
+ }
28
+ catch (error) {
29
+ onError(new Error(`Task Host terminal recovery ${owner.taskId}/${owner.roleName}/${turn.attemptId}: `
30
+ + `${error instanceof Error ? error.message : String(error)} No input was replayed.`, { cause: error }));
31
+ }
32
+ }
33
+ }
@@ -259,15 +259,10 @@ export class FileRoleLaunchPlanner {
259
259
  const existingSession = owner.scope === "task"
260
260
  ? this.store.getTaskRoleSessionSet(owner.taskId, role.name)?.sessions[input.agentId]
261
261
  : this.store.getGlobalRoleSessionSet(role.name)?.sessions[input.agentId];
262
- // A resume carries the Session's recorded generation forward unchanged. This
263
- // planner runs in the Controller, which after an upgrade is already the new
264
- // code, so it cannot speak for the still-running Agent Host that owns the
265
- // Session: rejecting here would end live Sessions on Controller upgrade,
266
- // while rewriting to current code would silently move a Session onto code it
267
- // never started on. The Host that actually executes the implementation makes
268
- // the decision — a reused live Host accepts its own generation, and a newly
269
- // started Host running different code fails closed
270
- // (`agentEndpointOwnership.pin` -> `requireBuiltinAgentEndpointImplementation`).
262
+ // Retain original launch provenance. The Host reports its actually loaded
263
+ // build separately and checks adapter/native identity, not build equality.
264
+ // A compatible new Host can resume this exact Session after safe release;
265
+ // this planner never silently replaces a live Host or native conversation.
271
266
  const endpointImplementation = input.mode === "resume"
272
267
  ? validateAgentEndpointImplementation(existingSession.endpointImplementation)
273
268
  : builtinAgentEndpointImplementation(binding.adapterId);
@@ -1,5 +1,24 @@
1
1
  import { requireKnownFields, requireText, requireTimestamp } from "../domain/validation.js";
2
2
  import { isDeepStrictEqual } from "node:util";
3
+ /** Semantic handling and transport delivery are independent facts. Projections
4
+ * must not infer handling from delivery, a native terminal, or an empty queue. */
5
+ export function inboxHandlingState(receipt) {
6
+ return receipt?.ack !== undefined ? "handled" : receipt !== undefined ? "claimed-unacknowledged" : "unclaimed";
7
+ }
8
+ export function isGlobalInboxMessage(message) {
9
+ return message.inputControl !== undefined || message.interruptThen !== undefined;
10
+ }
11
+ /** Unhandled own-role input, including accepted and unknown delivery. This is
12
+ * NOT the scheduler's deliverable queue; selecting it never authorizes replay. */
13
+ export function pendingGlobalInboxMessages(messages) {
14
+ return messages.filter(message => isGlobalInboxMessage(message) && inboxHandlingState(message.inbox) !== "handled");
15
+ }
16
+ /** Original message evidence only. A missing receipt is not proof of
17
+ * non-submission; admission additionally inspects the exact native attempt. */
18
+ export function globalInboxDeliveryState(message) {
19
+ return message.control?.outcome ?? (message.delivery?.via === "provider" ? "accepted"
20
+ : message.delivery === undefined ? "unobserved" : "delivery-unknown");
21
+ }
3
22
  export function validateInboxReceipt(receipt) {
4
23
  if (receipt === undefined)
5
24
  return;
@@ -30,3 +49,14 @@ export function assertInboxReceiptPreserved(previous, next) {
30
49
  throw new Error("Inbox handling ownership changed; reread the original instead of overwriting its receipt.");
31
50
  }
32
51
  }
52
+ /** A receipt cannot silently acknowledge different content under the same ID.
53
+ * Operational delivery/Assignment fields may evolve with their existing
54
+ * guards, but handled originals must be submitted as new Messages to change.
55
+ */
56
+ export function assertInboxOriginalPreserved(previous, next) {
57
+ assertInboxReceiptPreserved(previous?.inbox, next.inbox);
58
+ if (previous?.inbox !== undefined && (previous.body !== next.body || previous.kind !== next.kind
59
+ || !isDeepStrictEqual(previous.author, next.author) || !isDeepStrictEqual(previous.inputControl, next.inputControl))) {
60
+ throw new Error("Inbox receipt binds the exact original Message; submit changed content as a new Message.");
61
+ }
62
+ }
@@ -1,4 +1,19 @@
1
1
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
2
+ /** Pin mutable originals by their existing event identity, not wall-clock time.
3
+ * Both automatic notification and active consumption use the same fixed refs. */
4
+ export function pinMessageRevisions(store, taskId, refs) {
5
+ const pinned = [...refs];
6
+ for (const ref of refs) {
7
+ if (ref.type !== "message" || ref.taskId !== taskId)
8
+ continue;
9
+ const message = store.listMessages(taskId).find(message => message.id === ref.id);
10
+ const revision = message === undefined ? undefined : latestMessageUpdate(store, message);
11
+ if (revision !== undefined && !pinned.some(ref => ref.type === "event" && ref.id === revision.id)) {
12
+ pinned.push({ type: "event", taskId, id: revision.id });
13
+ }
14
+ }
15
+ return pinned;
16
+ }
2
17
  export function latestMessageUpdate(store, message) {
3
18
  return store.listEvents(message.taskId).filter(event => event.type === "message.updated" && event.payload.messageId === message.id).at(-1);
4
19
  }
@@ -9,10 +24,17 @@ export function isLeaderSteer(message) {
9
24
  }
10
25
  /** Derive ownership from original receipts, never from disposable mailboxes. */
11
26
  export function taskMessageInboxReceipt(store, message) {
27
+ return message.inbox ?? messageRevisionWakes(store, message).find(wake => wake.inbox !== undefined)?.inbox;
28
+ }
29
+ /** Delivery suppresses repeat notification of that revision, but remains
30
+ * distinct from handling. The original fixed window stays in the own inbox. */
31
+ export function deliveredMessageWake(store, message) {
32
+ return messageRevisionWakes(store, message).find(wake => wake.status === "consumed");
33
+ }
34
+ function messageRevisionWakes(store, message) {
12
35
  const revision = latestMessageUpdate(store, message);
13
- return message.inbox ?? store.listTaskWakes(message.taskId).find(wake => wake.inbox !== undefined
14
- && (revision === undefined || wake.refs?.some(ref => ref.type === "event" && ref.taskId === message.taskId && ref.id === revision.id))
15
- && wake.refs?.some(ref => ref.type === "message" && ref.taskId === message.taskId && ref.id === message.id))?.inbox;
36
+ return store.listTaskWakes(message.taskId).filter(wake => (revision === undefined || wake.refs?.some(ref => ref.type === "event" && ref.taskId === message.taskId && ref.id === revision.id))
37
+ && wake.refs?.some(ref => ref.type === "message" && ref.taskId === message.taskId && ref.id === message.id));
16
38
  }
17
39
  export function unfinishedLeaderInbox(store, taskId) {
18
40
  return store.listTaskWakes(taskId).some(wake => wake.inbox !== undefined && wake.inbox.ack === undefined)