@zq-silk/yui 2.4.0 → 2.6.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 (58) hide show
  1. package/dist/cli/commandCatalog.js +4 -3
  2. package/dist/commands/taskCommands.js +48 -15
  3. package/dist/commands/taskContextCommand.js +3 -2
  4. package/dist/context/sessionBootstrapManifest.js +8 -2
  5. package/dist/context/taskContext.js +7 -4
  6. package/dist/controller/fileSchedulerStoreAdapter.js +7 -2
  7. package/dist/controller/globalInputDelivery.js +2 -4
  8. package/dist/controller/providerRetryDelivery.js +4 -2
  9. package/dist/executor/agentAdapter.js +3 -5
  10. package/dist/executor/fileRoleLaunchPlanner.js +2 -2
  11. package/dist/interaction/notificationPresentation.js +85 -0
  12. package/dist/interaction/operatorPresentation.js +1 -2
  13. package/dist/kernel/builtinCapabilities.js +2 -1
  14. package/dist/runtime/builtinAgentErrorMappers.js +9 -5
  15. package/dist/runtime/providerRetry.js +38 -0
  16. package/dist/runtime/taskSessions.js +240 -0
  17. package/dist/scheduler/leaderWakeupProcessor.js +1 -9
  18. package/dist/scheduler/taskExecutionProjection.js +78 -6
  19. package/dist/web/assets/assetManifest.js +2 -0
  20. package/dist/web/assets/client/app/taskView.js +1 -0
  21. package/dist/web/assets/client/domain/records.js +70 -2
  22. package/dist/web/assets/client/domain/runs.js +3 -2
  23. package/dist/web/assets/client/domain/vocab.js +1 -1
  24. package/dist/web/assets/client/i18n/en.js +17 -1
  25. package/dist/web/assets/client/i18n/zh-CN.js +17 -1
  26. package/dist/web/assets/client/lib/api.js +4 -1
  27. package/dist/web/assets/client/lib/theme.js +31 -10
  28. package/dist/web/assets/client/views/task/actions.js +38 -21
  29. package/dist/web/assets/client/views/task/observation.js +12 -3
  30. package/dist/web/assets/client/views/task/overview.js +7 -40
  31. package/dist/web/assets/client/views/task/runtime.js +71 -34
  32. package/dist/web/assets/shared/icons.js +2 -0
  33. package/dist/web/assets/shell/index.js +1 -0
  34. package/dist/web/assets/shell/settings.js +1 -0
  35. package/dist/web/assets/styles/views/task.js +14 -0
  36. package/dist/web/webConversation.js +2 -2
  37. package/dist/web/webServer.js +12 -0
  38. package/dist/web/webSessions.js +2 -165
  39. package/dist/web/webSnapshot.js +9 -5
  40. package/dist/web/webTaskActivity.js +54 -0
  41. package/dist/web/webTaskSurface.js +1 -0
  42. package/docs/cli-information-contract.md +1 -1
  43. package/docs/cli-information-contract.zh-CN.md +1 -1
  44. package/docs/managed-turn-and-session-runtime.md +22 -0
  45. package/docs/managed-turn-and-session-runtime.zh-CN.md +15 -0
  46. package/docs/observability/README.md +25 -1
  47. package/docs/observability/README.zh-CN.md +18 -1
  48. package/docs/provider-retry.md +11 -0
  49. package/docs/release-workflow.md +53 -0
  50. package/docs/release-workflow.zh-CN.md +40 -0
  51. package/package.json +1 -1
  52. package/skills/yui-leader/SKILL.md +5 -4
  53. package/skills/yui-leader/references/execution.md +2 -1
  54. package/skills/yui-operator/SKILL.md +3 -0
  55. package/skills/yui-reviewer/SKILL.md +4 -2
  56. package/skills/yui-runtime/SKILL.md +43 -2
  57. package/skills/yui-runtime/references/recovery.md +9 -0
  58. package/skills/yui-worker/SKILL.md +3 -2
@@ -408,8 +408,9 @@ const taskChildren = [
408
408
  {
409
409
  name: "context", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment", "unbound"] },
410
410
  summary: "Read compact authorized facts, fixed-bound delta, or inspect a Context reference.",
411
- usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>]",
412
- options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item"]
411
+ usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>] [--view <current|all>]",
412
+ options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item", "--view"],
413
+ optionValues: { "--view": ["current", "all"] }
413
414
  },
414
415
  {
415
416
  name: "next-action", discovery: { surface: "managed", audiences: ["operator", "leader"] },
@@ -1096,7 +1097,7 @@ const taskChildren = [
1096
1097
  sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "resolve"] }],
1097
1098
  children: [
1098
1099
  { name: "list", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "List recorded Leader wakes.", usage: "yui task wake list <task> [--limit <1..100>] [--cursor <cursor>]", options: ["--limit", "--cursor"] },
1099
- { name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show a fixed wake window and exact detail reads.", usage: "yui task wake show <task> <wake> [--cursor <cursor>]", options: ["--cursor"] },
1100
+ { name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show fixed wake inputs/results; expand diagnostics with --history.", usage: "yui task wake show <task> <wake> [--history] [--cursor <cursor>]", options: ["--history", "--cursor"] },
1100
1101
  { name: "retry", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Retry a rejected notification after correcting its cause; preserve original input.",
1101
1102
  usage: "yui task wake retry <task> <wake> --reason <correction>", options: ["--reason"] },
1102
1103
  { name: "resolve", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Release an unknown notification claim after explicit quiescence evidence, without replay.",
@@ -13,6 +13,7 @@ import { assertContextRecordReadable, readTaskContextResource } from "../context
13
13
  import { parseTaskSearchOptions, searchTaskBodies } from "../context/taskSearch.js";
14
14
  import { boundedDocument, readOptions, messageReceipt, recordPage } from "../output/boundedRead.js";
15
15
  import { referencedWakeRunIds } from "../context/wakeRunReferences.js";
16
+ import { taskMessagePresentation, wakeChangeLabels, wakeMessages } from "../interaction/notificationPresentation.js";
16
17
  import { completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
17
18
  import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
18
19
  import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageError } from "../errors/cliError.js";
@@ -44,14 +45,14 @@ import { copyGlobalRoleToTaskRole, activeRoleAgentBinding, createRole, createRol
44
45
  import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
45
46
  import { RUNTIME_SESSION_REPLACE_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
46
47
  import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
47
- import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
48
+ import { controlProviderRetry, providerRunRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
48
49
  import { currentProviderConversation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
49
50
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
50
51
  import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
51
52
  import { failureCapabilitiesCommand } from "../runtime/agentFailureContext.js";
52
53
  import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
53
54
  import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
54
- import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
55
+ import { parseWakeReason, renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
55
56
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
56
57
  import { planningRuntimeCwd } from "../storage/homeLayout.js";
57
58
  import { StorageConflictError } from "../storage/taskStore.js";
@@ -4896,7 +4897,9 @@ function retryRun(args, store, options) {
4896
4897
  return output(`Retry already recorded as ${existing.payload.runId}; retry that exact successor if needed.\n`);
4897
4898
  }
4898
4899
  const sessions = tx.getTaskRoleSessionSet(previous.taskId, previous.roleName);
4899
- const retry = sessions?.providerBinding?.retry;
4900
+ const retry = options.providerRetryChainId !== undefined ? sessions?.providerBinding?.retry
4901
+ : sessions?.providerBinding == null ? undefined
4902
+ : providerRunRetry(sessions.providerBinding, previous.id, clock(options).getTime());
4900
4903
  if (options.providerRetryChainId !== undefined
4901
4904
  && (retry?.chainId !== options.providerRetryChainId || retry.status !== "waiting"
4902
4905
  || retry.previousRunId !== previous.id || clock(options).getTime() < Date.parse(retry.nextEligibleAt)
@@ -4908,7 +4911,10 @@ function retryRun(args, store, options) {
4908
4911
  const successor = tx.getActiveRun(previous.taskId, previous.roleName);
4909
4912
  tx.saveTaskRoleSessionSet({
4910
4913
  ...sessions, providerBinding: {
4911
- ...sessions.providerBinding, retry: {
4914
+ ...sessions.providerBinding,
4915
+ ...(sessions.providerBinding.retry !== undefined && sessions.providerBinding.retry.chainId !== retry.chainId
4916
+ ? { retryHistory: [...(sessions.providerBinding.retryHistory ?? []), sessions.providerBinding.retry] } : {}),
4917
+ retry: {
4912
4918
  ...retry, status: "waiting", successorAutomatic: options.providerRetryChainId !== undefined,
4913
4919
  ...(options.providerRetryChainId === undefined ? {
4914
4920
  intentAt: clock(options).toISOString(),
@@ -6932,12 +6938,16 @@ function taskWakeInspectionCommand(args, store) {
6932
6938
  return output(`${JSON.stringify(data)}\n`, data);
6933
6939
  }
6934
6940
  if (command === "show") {
6935
- const usage = "Task wake show usage: yui task wake show <task> <wake>.";
6936
- exactPositionals(rest, 2, usage);
6937
- const task = requireTask(store, rest[0]);
6938
- const wake = store.getTaskWake(task.id, rest[1]);
6941
+ const usage = "Task wake show usage: yui task wake show <task> <wake> [--history].";
6942
+ const history = rest.includes("--history");
6943
+ const positional = history ? rest.filter(arg => arg !== "--history") : rest;
6944
+ exactPositionals(positional, 2, usage);
6945
+ if (rest.filter(arg => arg === "--history").length > 1)
6946
+ throw usageError(usage);
6947
+ const task = requireTask(store, positional[0]);
6948
+ const wake = store.getTaskWake(task.id, positional[1]);
6939
6949
  if (wake === null)
6940
- throw dataError(`Wake not found: ${rest[1]}.`);
6950
+ throw dataError(`Wake not found: ${positional[1]}.`);
6941
6951
  const timeZone = store.getConfig().timeZone;
6942
6952
  const fromMs = Date.parse(wake.fromCursor);
6943
6953
  const toMs = Date.parse(wake.toCursor);
@@ -6953,8 +6963,20 @@ function taskWakeInspectionCommand(args, store) {
6953
6963
  const currentClaim = store.getWorkMailbox(leaderMailbox(task.id))?.processing;
6954
6964
  const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
6955
6965
  && (!("taskId" in ref) || ref.taskId === task.id)) === true;
6956
- const events = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id));
6957
- const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt) || referenced("message", m.id));
6966
+ // Some existing producers carry the exact source only in a reason tag
6967
+ // (e.g. input-timeout:<request>). Do not hide its decision behind --history.
6968
+ const reasonRefs = new Set(wake.reasons.flatMap(tag => {
6969
+ const reason = parseWakeReason(tag);
6970
+ return reason.kind !== "force-wake" && reason.ref !== undefined ? [reason.ref] : [];
6971
+ }));
6972
+ const reasonEvent = (event) => Date.parse(event.createdAt) <= toMs && [
6973
+ event.id, event.payload.runId, event.payload.requestId, event.payload.jobId,
6974
+ event.payload.workItemId, event.payload.reviewRoundId
6975
+ ].some(id => id !== undefined && reasonRefs.has(id));
6976
+ const windowEvents = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id) || reasonEvent(e));
6977
+ const events = history ? windowEvents : windowEvents.filter(e => referenced("event", e.id)
6978
+ || reasonEvent(e) || ["run.completed", "run.failed", "run.cancelled"].includes(e.type));
6979
+ const messages = wakeMessages(wake, store.listMessages(task.id));
6958
6980
  const allRuns = store.listRuns(task.id);
6959
6981
  const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
6960
6982
  for (const run of allRuns)
@@ -6963,7 +6985,7 @@ function taskWakeInspectionCommand(args, store) {
6963
6985
  for (const message of messages)
6964
6986
  if (message.resultRef?.type === "agent-run-result")
6965
6987
  referencedRunIds.add(message.resultRef.runId);
6966
- const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6988
+ const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (history && inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6967
6989
  const lines = [
6968
6990
  `Wake: ${wake.id}`,
6969
6991
  `Task: ${task.id}`,
@@ -6979,29 +7001,40 @@ function taskWakeInspectionCommand(args, store) {
6979
7001
  `Recovery: correct the cause, then yui task wake retry ${task.id} ${wake.id} --reason <correction>.`
6980
7002
  ]),
6981
7003
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
7004
+ `Changes: ${wakeChangeLabels(wake.reasons).join("; ")}`,
6982
7005
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6983
7006
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6984
7007
  ...(wake.consumedAt === undefined
6985
7008
  ? []
6986
7009
  : [`Consumed: ${presentTime(wake.consumedAt, timeZone)}`]),
6987
7010
  `Events (${events.length}):`,
6988
- ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
7011
+ ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)} → yui task event show ${task.id} ${e.id}`),
6989
7012
  `Messages (${messages.length}):`,
6990
- ...messages.map((m) => ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${presentTime(m.createdAt, timeZone)}`),
7013
+ ...messages.map((m) => {
7014
+ const item = taskMessagePresentation(m, (taskId, runId) => store.getRun(taskId, runId));
7015
+ return ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${item.fact}`
7016
+ + (item.summary === undefined ? "" : `; author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`)
7017
+ + ` → yui task message show ${task.id}/${m.id}`;
7018
+ }),
6991
7019
  `AgentRuns (${runs.length}):`,
6992
7020
  ...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
6993
7021
  + `${presentTime(run.createdAt, timeZone)}`
6994
7022
  + `${referencedRunIds.has(run.id)
6995
7023
  ? ` → yui task run show ${task.id}/${run.id}`
6996
- : ""}`))
7024
+ : ""}`)),
7025
+ ...(!history ? [`History/diagnostics: yui task wake show ${task.id} ${wake.id} --history`,
7026
+ "Read relevant originals in full; follow contentPage.nextCursor when present."] : [])
6997
7027
  ];
6998
7028
  return output(lines.join("\n").concat("\n"), {
6999
7029
  taskId: task.id,
7000
7030
  deliveryEvents,
7001
7031
  wake,
7032
+ view: history ? "history" : "focus",
7033
+ historyRead: `task wake show ${task.id} ${wake.id} --history`,
7002
7034
  events: events.map(event => ({ id: event.id, type: event.type, createdAt: event.createdAt,
7003
7035
  read: `task event show ${task.id} ${event.id}` })),
7004
7036
  messages: messages.map(message => ({ id: message.id, kind: message.kind, author: message.author,
7037
+ ...taskMessagePresentation(message, (taskId, runId) => store.getRun(taskId, runId)),
7005
7038
  resultRef: message.resultRef, read: `task message show ${task.id}/${message.id}` })),
7006
7039
  runs: runs.map(run => ({ id: run.id, status: run.status, purpose: run.purpose, roleName: run.roleName,
7007
7040
  read: `task run show ${task.id}/${run.id}` }))
@@ -17,12 +17,12 @@ export function runTaskContextCommand(args, store, environment = {}) {
17
17
  const action = explicit ? args[0] : "read";
18
18
  const taskId = args[explicit ? 1 : 0];
19
19
  if (!taskId || taskId.startsWith("--"))
20
- throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--status <status>] [--work-item <id>].");
20
+ throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--view <current|all>] [--status <status>] [--work-item <id>].");
21
21
  const options = new Map();
22
22
  const rest = args.slice(explicit ? 2 : 1);
23
23
  const allowed = action === "read" ? [] : action === "delta"
24
24
  ? ["--after", "--continuation", "--limit"] : action === "list"
25
- ? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item"] : ["--store", "--ref", "--digest", "--cursor"];
25
+ ? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item", "--view"] : ["--store", "--ref", "--digest", "--cursor"];
26
26
  for (let i = 0; i < rest.length; i += 2) {
27
27
  const flag = rest[i];
28
28
  const value = rest[i + 1];
@@ -40,6 +40,7 @@ export function runTaskContextCommand(args, store, environment = {}) {
40
40
  throw usageError("Context list requires --store.");
41
41
  data = listTaskContext(store, taskId, family, {
42
42
  cursor: options.get("--cursor"),
43
+ view: options.get("--view"),
43
44
  status: options.get("--status"), after: options.get("--after"), workItemId: options.get("--work-item"),
44
45
  ...(options.has("--limit") ? { limit: Number(options.get("--limit")) } : {})
45
46
  }, environment);
@@ -12,12 +12,18 @@ export function withSessionContextPointer(text, environment) {
12
12
  if (manifest === undefined)
13
13
  return text;
14
14
  return [
15
- `Read the Yui Session Manifest at ${manifest}.`,
16
- "Follow its referenced Role Skills and role profile. It gives the exact Context load command.",
15
+ sessionContextInstructions(manifest),
17
16
  "",
18
17
  text
19
18
  ].join("\n");
20
19
  }
20
+ /** No remembered/sent flag can prove instructions survived context compaction. */
21
+ export function sessionContextInstructions(manifestPath, digest) {
22
+ return `Yui Session Manifest: ${manifestPath}${digest === undefined ? "" : ` (digest ${digest})`}. `
23
+ + "Apply yui-runtime and its Role Skills/profile by exact manifest paths. "
24
+ + "Load on first entry, version change or missing context; reuse only same-version instructions "
25
+ + "fully available now, never infer availability from prior reads.";
26
+ }
21
27
  /** Read back one immutable Session Manifest and verify its content digest. */
22
28
  export function readSessionBootstrapManifest(path) {
23
29
  const source = resolve(path);
@@ -191,7 +191,10 @@ export function readTaskContextResource(store, taskId, selector, environment = {
191
191
  export function listTaskContext(store, taskId, family, options = {}, environment = {}) {
192
192
  return store.readTransaction(reader => {
193
193
  const scope = authorizeContext(reader, taskId, environment);
194
- const sources = authorizedSources(reader, scope, false, family);
194
+ if (options.view !== undefined && !["current", "all"].includes(options.view)) {
195
+ throw usageError("Context list view must be current or all.");
196
+ }
197
+ const sources = authorizedSources(reader, scope, options.view === "current", family, true);
195
198
  if (!CONTEXT_FAMILIES.includes(family))
196
199
  throw usageError(`Unknown Context store: ${family}.`);
197
200
  if (options.after !== undefined && !Number.isFinite(Date.parse(options.after)))
@@ -216,7 +219,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
216
219
  }
217
220
  }
218
221
  return recordPage(records, `task:${taskId}/${family}/${JSON.stringify({
219
- status: options.status, after: options.after, workItemId: options.workItemId
222
+ view: options.view ?? "all", status: options.status, after: options.after, workItemId: options.workItemId
220
223
  })}`, options);
221
224
  });
222
225
  }
@@ -447,7 +450,7 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
447
450
  default: return null;
448
451
  }
449
452
  }
450
- function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily) {
453
+ function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily, discovery = false) {
451
454
  const taskId = task.id;
452
455
  const sources = [];
453
456
  const add = (family, id, value) => {
@@ -462,7 +465,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
462
465
  const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
463
466
  sources.push({ family, count, *read(offset = 0) {
464
467
  for (const { id, value } of store.queryContextRecords(taskId, {
465
- ...selection, offset, limit: current ? 8 : MAX_RECORDS
468
+ ...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
466
469
  }).records)
467
470
  yield materialize(family, id, value);
468
471
  } });
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
4
4
  import { freezeRunContextSnapshot } from "../context/runContextPack.js";
5
+ import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
5
6
  import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
6
7
  import { interruptThenTerminalState, prepareMessageContinuations } from "../message/messageContinuation.js";
7
8
  import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
@@ -1848,7 +1849,11 @@ export class FileSchedulerStoreAdapter {
1848
1849
  }
1849
1850
  const nextAttempt = `notification:${taskId}/${wakeId}/${randomUUID()}`;
1850
1851
  store.saveWorkMailbox({ ...mailbox, processing: { ...processing, batchId: nextAttempt } });
1851
- return { wakeId, attemptId: nextAttempt, disposition: "submit" };
1852
+ const wake = store.getTaskWake(taskId, wakeId);
1853
+ if (wake === null)
1854
+ throw new Error(`Notification wake is unavailable: ${taskId}/${wakeId}.`);
1855
+ return { wakeId, attemptId: nextAttempt, disposition: "submit",
1856
+ presentation: taskNotificationPresentation(wake, store) };
1852
1857
  }
1853
1858
  if (mailbox.pending === null || (provider !== null && provider !== undefined
1854
1859
  && ["submitting", "accepted", "delivery-unknown"].includes(provider.status)))
@@ -1918,7 +1923,7 @@ export class FileSchedulerStoreAdapter {
1918
1923
  // batch, rather than delivering them with the chosen immediate successor.
1919
1924
  if (remainingRefs.length > 0)
1920
1925
  enqueueWork(store, target, "user-message", now, remainingRefs, { source: "controller", dedupeKey: `then-remainder:${wakeId}` });
1921
- return { wakeId, attemptId, disposition: "submit" };
1926
+ return { wakeId, attemptId, disposition: "submit", presentation: taskNotificationPresentation(wake, store) };
1922
1927
  });
1923
1928
  }
1924
1929
  settleLeaderNotification(taskId, attemptId, outcome, now, detail, failure) {
@@ -4,6 +4,7 @@ import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/
4
4
  import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
5
5
  import { providerRetryPending } from "../runtime/providerRetry.js";
6
6
  import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
7
+ import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
7
8
  /** Messages are the only queue. Host's atomic begin arbitrates native writers. */
8
9
  export async function deliverGlobalInputs(home, store, ensure, onError) {
9
10
  for (const role of store.listGlobalRoles()) {
@@ -119,10 +120,7 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
119
120
  protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn", nativeSessionId,
120
121
  authority: { epoch: binding.authority.epoch, owner: "controller",
121
122
  holderId: binding.authority.holderId },
122
- run: { attemptId, boundedText: [
123
- `Yui Global Message: role=${role.name} message=${next.id}.`,
124
- "Read your Session Context and the referenced Message in full, then act on its durable input."
125
- ].join("\n") }
123
+ run: { attemptId, boundedText: globalMessagePresentation(role.name, next.id, next) }
126
124
  }
127
125
  });
128
126
  if (result.outcome === "rejected") {
@@ -10,6 +10,7 @@ import { cancelProviderRetry, providerRetryAttemptId, providerRetryPending, prov
10
10
  import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
11
11
  import { retryIntentBlocker } from "./providerRetryAdmission.js";
12
12
  import { recordGlobalRuntimeAttention } from "./globalRuntimeAttention.js";
13
+ import { globalMessagePresentation, taskNotificationPresentation } from "../interaction/notificationPresentation.js";
13
14
  /** Hooks in the existing Controller pass and nearest-deadline timer. SQLite's
14
15
  * current Provider input is the only pending intent; nothing is queued here. */
15
16
  export function createProviderRetryHooks(home, store, ports = {}) {
@@ -185,11 +186,12 @@ export function createProviderRetryHooks(home, store, ports = {}) {
185
186
  });
186
187
  }
187
188
  else {
189
+ const wake = retry.input.kind === "wake" ? store.getTaskWake(retry.input.taskId, retry.input.wakeId) : null;
188
190
  const original = retry.input.kind === "text" ? retry.input.text
189
191
  : retry.input.kind === "wake"
190
- ? `Yui Task notification: task=${retry.input.taskId} wake=${retry.input.wakeId}.\nRead current Task context and that fixed wake window, including original Messages in full. This is not an execution assignment.`
192
+ ? wake === null ? undefined : taskNotificationPresentation(wake, store)
191
193
  : retry.input.kind === "message"
192
- ? `Yui Global Message: role=${retry.input.roleName} message=${retry.input.messageId}.\nRead your Session Context and referenced original Message in full.`
194
+ ? globalMessagePresentation(retry.input.roleName, retry.input.messageId, store.listGlobalRoleMessages(retry.input.roleName).find(message => retry.input.kind === "message" && message.id === retry.input.messageId))
193
195
  : undefined;
194
196
  if (original === undefined) {
195
197
  stop(set, "original-input-unavailable", at);
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { realpathSync, statSync } from "node:fs";
3
3
  import { isAbsolute, resolve } from "node:path";
4
+ import { sessionContextInstructions } from "../context/sessionBootstrapManifest.js";
4
5
  import { isAgentAdapterId, supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
5
6
  import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgentBaseArguments } from "../agent/argumentPolicy.js";
6
7
  import { writeTextFileAtomically } from "../storage/durableFile.js";
@@ -434,10 +435,7 @@ function acpSessionBootstrap(input) {
434
435
  ])
435
436
  ]
436
437
  : [
437
- `Yui managed Session. Read and follow the Session Manifest at `
438
- + `${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). `
439
- + "Load each Skill and Role Profile by its manifest path before acting; Task "
440
- + "content is available only through the manifest's exact Context API."
438
+ sessionContextInstructions(input.sessionManifestPath, input.sessionManifestDigest)
441
439
  ];
442
440
  const bootstrap = sections
443
441
  .filter((value) => value !== undefined && value.trim().length > 0)
@@ -500,7 +498,7 @@ function codexThreadOptions(input, config) {
500
498
  }
501
499
  function codexSessionInstructions(input) {
502
500
  if (input.sessionManifestPath !== undefined) {
503
- return `Yui managed Session. Read and follow the Session Manifest at ${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). Load each Skill and Role Profile by its manifest path before acting; Task content is available only through the manifest's exact Context API.`;
501
+ return sessionContextInstructions(input.sessionManifestPath, input.sessionManifestDigest);
504
502
  }
505
503
  const instructions = [
506
504
  input.developerInstructions,
@@ -9,7 +9,7 @@ import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironme
9
9
  import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
10
10
  import { NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment } from "../agent/launchEnvironment.js";
11
11
  import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
12
- import { materializeSessionBootstrap } from "../context/sessionBootstrapManifest.js";
12
+ import { materializeSessionBootstrap, sessionContextInstructions } from "../context/sessionBootstrapManifest.js";
13
13
  import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
14
14
  import { activeRoleAgentBinding } from "../role/role.js";
15
15
  import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
@@ -358,7 +358,7 @@ export class FileRoleLaunchPlanner {
358
358
  }
359
359
  const sessionContext = {
360
360
  ...baseSessionContext,
361
- developerInstructions: `Read and follow the exact Yui Session Manifest at ${bootstrap.manifestPath}.`,
361
+ developerInstructions: sessionContextInstructions(bootstrap.manifestPath, bootstrap.manifest.digest),
362
362
  managedContextFile: bootstrap.manifestPath,
363
363
  sessionManifestPath: bootstrap.manifestPath,
364
364
  sessionManifestDigest: bootstrap.manifest.digest
@@ -0,0 +1,85 @@
1
+ import { parseWakeReason } from "../scheduler/wakeReason.js";
2
+ /** Optional producer-authored presentation, stored in the original text, not a
3
+ * parsed business outcome or a second fact store. Reject oversized summaries
4
+ * whole: clipping could remove a failure/authority qualifier. */
5
+ export function authoredNotificationSummary(text) {
6
+ const line = text?.trimStart().split(/\r?\n/u, 1)[0];
7
+ if (!line?.startsWith("Yui-Summary: "))
8
+ return undefined;
9
+ const summary = line.slice("Yui-Summary: ".length).trim();
10
+ return summary.length > 0 && Buffer.byteLength(summary) <= 512
11
+ && !/[\u0000-\u001f\u007f]/u.test(summary) ? summary : undefined;
12
+ }
13
+ export function taskMessagePresentation(message, getRun) {
14
+ const run = message.resultRef?.type === "agent-run-result"
15
+ ? getRun(message.taskId, message.resultRef.runId) : null;
16
+ const summary = authoredNotificationSummary(run?.result?.output ?? message.body);
17
+ const fact = run !== null
18
+ ? `${run.purpose === "review" ? "Review" : "Execution"} result returned (${run.status}); disposition pending`
19
+ : message.kind === "role-result" ? "Role result returned; disposition pending"
20
+ : message.kind === "user" || message.kind === "operator" ? "Additional input received"
21
+ : "System message received";
22
+ return {
23
+ fact,
24
+ ...(summary === undefined ? {} : {
25
+ summary, summarySource: run?.result?.output === undefined ? "message-body" : "agent-run-result"
26
+ })
27
+ };
28
+ }
29
+ export function wakeChangeLabels(reasons) {
30
+ return [...new Set(reasons.map(tag => {
31
+ const { kind } = parseWakeReason(tag);
32
+ switch (kind) {
33
+ case "operator-input":
34
+ case "user-message":
35
+ case "interrupt-then": return "Additional input received";
36
+ case "execution-started": return "Task execution enabled";
37
+ case "task-created": return "Task created";
38
+ case "task-reopened": return "Task reopened; check for scoped follow-up";
39
+ case "role-result": return "Role result returned; disposition pending";
40
+ case "review-failed": return "Review execution failed; inspect original failure";
41
+ case "agent-error":
42
+ case "leader-turn-failed":
43
+ case "role-turn-failed": return "Execution failure needs attention";
44
+ case "job-finished": return "Job ended; inspect outcome";
45
+ case "input-timeout": return "Input deadline reached; inspect recorded decision";
46
+ case "notification-retry": return "Notification retry after recorded correction";
47
+ default: return Buffer.byteLength(kind) <= 120 && !/[\u0000-\u001f\u007f]/u.test(kind)
48
+ ? `Recorded update: ${kind}` : "Other recorded update; inspect wake";
49
+ }
50
+ }))];
51
+ }
52
+ export function wakeMessages(wake, messages) {
53
+ const ids = new Set(wake.refs?.filter(ref => ref.type === "message" && ref.taskId === wake.taskId).map(ref => ref.id));
54
+ const from = Date.parse(wake.fromCursor), to = Date.parse(wake.toCursor);
55
+ return messages.filter(message => ids.has(message.id)
56
+ || Date.parse(message.createdAt) > from && Date.parse(message.createdAt) <= to);
57
+ }
58
+ export function taskNotificationPresentation(wake, store) {
59
+ const messages = wakeMessages(wake, store.listMessages(wake.taskId));
60
+ const labels = wakeChangeLabels(wake.reasons);
61
+ return [
62
+ `Yui Task notification: task=${wake.taskId} wake=${wake.id}.`,
63
+ `Changes: ${labels.slice(0, 4).join("; ")}${labels.length > 4 ? "; more in wake" : ""}.`,
64
+ ...messages.slice(0, 4).map(message => {
65
+ const item = taskMessagePresentation(message, (task, run) => store.getRun(task, run));
66
+ return `- ${message.id} [${message.author.type}${message.author.type === "role" ? `:${message.author.roleName}` : ""}]: ${item.fact}.`
67
+ + (item.summary === undefined ? "" : ` Author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`);
68
+ }),
69
+ ...(messages.length > 4 ? [`${messages.length - 4} more messages in the fixed wake.`] : []),
70
+ `Refresh: yui task context ${wake.taskId} --json`,
71
+ `Read inputs/results: yui task wake show ${wake.taskId} ${wake.id} --json`,
72
+ "Apply yui-leader (from the current Manifest); read relevant originals in full and advance authorized work."
73
+ ].join("\n");
74
+ }
75
+ export function globalMessagePresentation(roleName, messageId, message) {
76
+ const summary = authoredNotificationSummary(message?.body);
77
+ return [
78
+ `Yui Global Message: role=${roleName} message=${messageId}.`,
79
+ `Changes: ${message?.author.type ?? "Recorded"} input received.`,
80
+ ...(summary === undefined ? [] : [`Author summary (message-body): ${JSON.stringify(summary)}`]),
81
+ "Refresh Session Context using the current Manifest's loadCommand.",
82
+ `Read original: yui role message show ${roleName} ${messageId} --json`,
83
+ `Apply ${roleName === "operator" ? "yui-operator" : "yui-runtime"} from the current Manifest within existing authority.`
84
+ ].join("\n");
85
+ }
@@ -10,8 +10,7 @@ export function createOperatorBatchPresentation(batchId, items) {
10
10
  receiptId: `operator-batch:${batchId}`,
11
11
  text: [
12
12
  "[Yui updates]",
13
- "Yui recorded the following durable updates. Inspect the referenced records and present only information that changes the user's understanding, authorization, or next action.",
14
- "This notification is not a new execution assignment or authorization to replay prior operations.",
13
+ "Apply yui-operator from the current Manifest: inspect these originals and present material changes or decisions.",
15
14
  ...items.flatMap(renderItem)
16
15
  ].join("\n")
17
16
  };
@@ -47,7 +47,7 @@ const definitions = [
47
47
  name: "context.list", summary: "Discover one authorized record family with bounded summaries and exact references.",
48
48
  effect: "query", requiredPermissions: ["task:read"], source: "listTaskContext",
49
49
  inputSchema: object({ taskId: text, store: text, cursor: text, limit: { type: "integer" },
50
- status: text, after: text, workItemId: text }, ["taskId", "store"]),
50
+ view: { type: "string", enum: ["current", "all"] }, status: text, after: text, workItemId: text }, ["taskId", "store"]),
51
51
  outputSchema: { type: "object", required: ["items", "total", "complete", "nextCursor"] }
52
52
  },
53
53
  {
@@ -304,6 +304,7 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
304
304
  }, callerEnvironment(caller));
305
305
  if (name === "context.list")
306
306
  return listTaskContext(store, taskId, params.store, {
307
+ view: params.view,
307
308
  cursor: params.cursor, limit: params.limit,
308
309
  status: params.status, after: params.after,
309
310
  workItemId: params.workItemId
@@ -26,7 +26,7 @@ function mapClaudeDisplayError(input) {
26
26
  }
27
27
  /** Provider-specific Codex failure recognition. */
28
28
  export function mapCodexAgentError(input) {
29
- return withTransientEvidence(mapCodexDisplayError(input), input);
29
+ return withTransientEvidence(mapCodexDisplayError(input), input, true);
30
30
  }
31
31
  function mapCodexDisplayError(input) {
32
32
  const text = `${input.message}\n${input.raw}`;
@@ -208,7 +208,7 @@ function sessionUnavailable(code) {
208
208
  }
209
209
  /** Replay qualification is stricter than the readable, best-effort category.
210
210
  * Never promote a number occurring in provider prose to submission safety. */
211
- function structuredTransientError(input) {
211
+ function structuredTransientError(input, codex) {
212
212
  if (/insufficient[_ -]quota|quota.{0,25}(exhaust|exceed)|billing[_ -]hard[_ -]limit|usageLimitExceeded|credits?.{0,20}(exhaust|insufficient)/iu.test(`${input.message}\n${input.raw}`))
213
213
  return { category: "access", code: "provider.quota-exhausted", retryable: false };
214
214
  const nodes = [];
@@ -228,7 +228,8 @@ function structuredTransientError(input) {
228
228
  catch { /* Display classification may still use prose. */ }
229
229
  const codes = nodes.flatMap(n => [n.code, n.type, n.error, n.codexErrorInfo]).filter(v => typeof v === "string");
230
230
  if (codes.some(c => ["authentication_error", "permission_error", "invalid_api_key", "invalid_model",
231
- "model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))) {
231
+ "model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))
232
+ || codex && nodes.some(n => ["usageLimitExceeded", "unauthorized", "badRequest"].includes(String(n.codexErrorInfo)))) {
232
233
  return { category: "access", code: "provider.access-denied", retryable: false };
233
234
  }
234
235
  const statuses = nodes.flatMap(n => [n.httpStatusCode, n.statusCode, n.status]).filter(v => typeof v === "number");
@@ -240,6 +241,9 @@ function structuredTransientError(input) {
240
241
  if (statuses.includes(429) || codes.includes("rate_limit_error") || nativeCode === "rate_limit_error") {
241
242
  result = recoverable("rate-limit", "provider.rate-limit");
242
243
  }
244
+ else if (codex && nodes.some(n => n.codexErrorInfo === "serverOverloaded")) {
245
+ result = recoverable("availability", "provider.model-capacity");
246
+ }
243
247
  else if (statuses.some(s => [500, 502, 503, 504].includes(s))
244
248
  || codes.some(c => ["server_error", "overloaded_error"].includes(c))
245
249
  || ["server_error", "overloaded_error"].includes(nativeCode)) {
@@ -277,8 +281,8 @@ function structuredTransientError(input) {
277
281
  }
278
282
  /** Add positive evidence without renaming established diagnostics or letting
279
283
  * a transient nested cause override a known fatal Session/request failure. */
280
- function withTransientEvidence(base, input) {
281
- const evidence = structuredTransientError(input);
284
+ function withTransientEvidence(base, input, codex = false) {
285
+ const evidence = structuredTransientError(input, codex);
282
286
  if (evidence === undefined)
283
287
  return base;
284
288
  const possiblyTransient = ["rate-limit", "availability", "transport", "unknown"].includes(base.category);
@@ -3,6 +3,44 @@ export const PROVIDER_RETRY_BUDGET_MS = 600_000;
3
3
  export function providerRetryPending(binding) {
4
4
  return binding?.retry?.status === "waiting" || binding?.retry?.status === "in-flight";
5
5
  }
6
+ /** An explicit Run retry can carry exact failed-input proof even when the
7
+ * error did not qualify for automation. This does not grant automatic replay:
8
+ * the shared admission and native latest-Turn/quiescence checks still apply. */
9
+ export function providerRunRetry(binding, runId, at) {
10
+ const turn = binding.run;
11
+ const nativeSessionId = binding.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId;
12
+ if (turn?.runId !== runId || !["failed", "rejected", "deferred"].includes(turn.status)
13
+ || binding.authority.owner !== "controller" || turn.authorityEpoch !== binding.authority.epoch
14
+ || nativeSessionId === undefined)
15
+ return undefined;
16
+ const previous = binding.retry;
17
+ if (previous?.previousRunId === runId && previous.status !== "recovered"
18
+ && previous.failedAttemptId === turn.attemptId
19
+ && previous.authorityEpoch === binding.authority.epoch
20
+ && previous.nativeSessionId === nativeSessionId)
21
+ return previous;
22
+ const failure = turn.failure;
23
+ if (turn.status !== "failed" || turn.nativeTurnId === undefined || turn.input === undefined
24
+ || turn.retrySupported !== true || failure?.error.inputDisposition !== "accepted"
25
+ || failure.error.sessionDisposition !== "recoverable")
26
+ return undefined;
27
+ // A manual attempt can cancel automation when its error changes character.
28
+ // Refresh its failed-Turn proof without granting the same input a new budget.
29
+ const continuing = previous?.status !== "recovered" && previous?.currentAttemptId === turn.attemptId
30
+ && previous.authorityEpoch === binding.authority.epoch && previous.nativeSessionId === nativeSessionId;
31
+ return {
32
+ policyVersion: 1, chainId: continuing ? previous.chainId : turn.attemptId, status: "waiting",
33
+ input: continuing ? previous.input : turn.input, nativeSessionId, authorityEpoch: binding.authority.epoch,
34
+ attempts: continuing ? previous.attempts : 0,
35
+ transportSequence: continuing ? previous.transportSequence : 0,
36
+ startedAt: continuing ? previous.startedAt : iso(at), intentAt: iso(at),
37
+ deadline: continuing ? previous.deadline : iso(at + PROVIDER_RETRY_BUDGET_MS),
38
+ nextEligibleAt: iso(at), updatedAt: iso(at),
39
+ failedAttemptId: turn.attemptId, failureAttemptId: turn.attemptId,
40
+ failedNativeTurnId: turn.nativeTurnId, previousRunId: runId,
41
+ failureRef: failure.ref, error: failure.error, successorAutomatic: false
42
+ };
43
+ }
6
44
  /** Called only in the transaction recording the exact failed/rejected input. */
7
45
  export function recordProviderFailure(binding, input) {
8
46
  let turn = binding.run;