@zq-silk/yui 2.4.0 → 2.5.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.
@@ -1096,7 +1096,7 @@ const taskChildren = [
1096
1096
  sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "resolve"] }],
1097
1097
  children: [
1098
1098
  { 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"] },
1099
+ { 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
1100
  { name: "retry", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Retry a rejected notification after correcting its cause; preserve original input.",
1101
1101
  usage: "yui task wake retry <task> <wake> --reason <correction>", options: ["--reason"] },
1102
1102
  { 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";
@@ -51,7 +52,7 @@ import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.j
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";
@@ -6932,12 +6933,16 @@ function taskWakeInspectionCommand(args, store) {
6932
6933
  return output(`${JSON.stringify(data)}\n`, data);
6933
6934
  }
6934
6935
  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]);
6936
+ const usage = "Task wake show usage: yui task wake show <task> <wake> [--history].";
6937
+ const history = rest.includes("--history");
6938
+ const positional = history ? rest.filter(arg => arg !== "--history") : rest;
6939
+ exactPositionals(positional, 2, usage);
6940
+ if (rest.filter(arg => arg === "--history").length > 1)
6941
+ throw usageError(usage);
6942
+ const task = requireTask(store, positional[0]);
6943
+ const wake = store.getTaskWake(task.id, positional[1]);
6939
6944
  if (wake === null)
6940
- throw dataError(`Wake not found: ${rest[1]}.`);
6945
+ throw dataError(`Wake not found: ${positional[1]}.`);
6941
6946
  const timeZone = store.getConfig().timeZone;
6942
6947
  const fromMs = Date.parse(wake.fromCursor);
6943
6948
  const toMs = Date.parse(wake.toCursor);
@@ -6953,8 +6958,20 @@ function taskWakeInspectionCommand(args, store) {
6953
6958
  const currentClaim = store.getWorkMailbox(leaderMailbox(task.id))?.processing;
6954
6959
  const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
6955
6960
  && (!("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));
6961
+ // Some existing producers carry the exact source only in a reason tag
6962
+ // (e.g. input-timeout:<request>). Do not hide its decision behind --history.
6963
+ const reasonRefs = new Set(wake.reasons.flatMap(tag => {
6964
+ const reason = parseWakeReason(tag);
6965
+ return reason.kind !== "force-wake" && reason.ref !== undefined ? [reason.ref] : [];
6966
+ }));
6967
+ const reasonEvent = (event) => Date.parse(event.createdAt) <= toMs && [
6968
+ event.id, event.payload.runId, event.payload.requestId, event.payload.jobId,
6969
+ event.payload.workItemId, event.payload.reviewRoundId
6970
+ ].some(id => id !== undefined && reasonRefs.has(id));
6971
+ const windowEvents = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id) || reasonEvent(e));
6972
+ const events = history ? windowEvents : windowEvents.filter(e => referenced("event", e.id)
6973
+ || reasonEvent(e) || ["run.completed", "run.failed", "run.cancelled"].includes(e.type));
6974
+ const messages = wakeMessages(wake, store.listMessages(task.id));
6958
6975
  const allRuns = store.listRuns(task.id);
6959
6976
  const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
6960
6977
  for (const run of allRuns)
@@ -6963,7 +6980,7 @@ function taskWakeInspectionCommand(args, store) {
6963
6980
  for (const message of messages)
6964
6981
  if (message.resultRef?.type === "agent-run-result")
6965
6982
  referencedRunIds.add(message.resultRef.runId);
6966
- const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6983
+ const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (history && inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6967
6984
  const lines = [
6968
6985
  `Wake: ${wake.id}`,
6969
6986
  `Task: ${task.id}`,
@@ -6979,29 +6996,40 @@ function taskWakeInspectionCommand(args, store) {
6979
6996
  `Recovery: correct the cause, then yui task wake retry ${task.id} ${wake.id} --reason <correction>.`
6980
6997
  ]),
6981
6998
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
6999
+ `Changes: ${wakeChangeLabels(wake.reasons).join("; ")}`,
6982
7000
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6983
7001
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6984
7002
  ...(wake.consumedAt === undefined
6985
7003
  ? []
6986
7004
  : [`Consumed: ${presentTime(wake.consumedAt, timeZone)}`]),
6987
7005
  `Events (${events.length}):`,
6988
- ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
7006
+ ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)} → yui task event show ${task.id} ${e.id}`),
6989
7007
  `Messages (${messages.length}):`,
6990
- ...messages.map((m) => ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${presentTime(m.createdAt, timeZone)}`),
7008
+ ...messages.map((m) => {
7009
+ const item = taskMessagePresentation(m, (taskId, runId) => store.getRun(taskId, runId));
7010
+ return ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${item.fact}`
7011
+ + (item.summary === undefined ? "" : `; author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`)
7012
+ + ` → yui task message show ${task.id}/${m.id}`;
7013
+ }),
6991
7014
  `AgentRuns (${runs.length}):`,
6992
7015
  ...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
6993
7016
  + `${presentTime(run.createdAt, timeZone)}`
6994
7017
  + `${referencedRunIds.has(run.id)
6995
7018
  ? ` → yui task run show ${task.id}/${run.id}`
6996
- : ""}`))
7019
+ : ""}`)),
7020
+ ...(!history ? [`History/diagnostics: yui task wake show ${task.id} ${wake.id} --history`,
7021
+ "Read relevant originals in full; follow contentPage.nextCursor when present."] : [])
6997
7022
  ];
6998
7023
  return output(lines.join("\n").concat("\n"), {
6999
7024
  taskId: task.id,
7000
7025
  deliveryEvents,
7001
7026
  wake,
7027
+ view: history ? "history" : "focus",
7028
+ historyRead: `task wake show ${task.id} ${wake.id} --history`,
7002
7029
  events: events.map(event => ({ id: event.id, type: event.type, createdAt: event.createdAt,
7003
7030
  read: `task event show ${task.id} ${event.id}` })),
7004
7031
  messages: messages.map(message => ({ id: message.id, kind: message.kind, author: message.author,
7032
+ ...taskMessagePresentation(message, (taskId, runId) => store.getRun(taskId, runId)),
7005
7033
  resultRef: message.resultRef, read: `task message show ${task.id}/${message.id}` })),
7006
7034
  runs: runs.map(run => ({ id: run.id, status: run.status, purpose: run.purpose, roleName: run.roleName,
7007
7035
  read: `task run show ${task.id}/${run.id}` }))
@@ -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);
@@ -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
  };
@@ -86,15 +86,7 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
86
86
  attempted = true;
87
87
  const outcome = await delivery.sendOnce({
88
88
  delivery: ready, receiptId: notification.attemptId, notificationId: notification.wakeId,
89
- text: [
90
- `Yui Task notification: task=${task.id} wake=${notification.wakeId}.`,
91
- `Read current context: yui task context ${task.id} --json.`,
92
- `Read the fixed notification window: yui task wake show ${task.id} ${notification.wakeId}.`,
93
- "Read referenced result Messages in full before deciding their disposition.",
94
- "Follow the Leader Skill to advance outstanding Task work from these durable facts, or record the exact decision/authority needed.",
95
- "This is a notification, not an execution assignment. No separate final report is required.",
96
- "Reading context is not an acknowledgement that its requirements have been implemented."
97
- ].join("\n")
89
+ text: notification.presentation
98
90
  });
99
91
  const accepted = outcome.status === "sent" || outcome.status === "already-sent";
100
92
  const disposition = accepted ? "accepted"
@@ -170,7 +170,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
170
170
  return [item];
171
171
  let messages = [];
172
172
  if (owner.scope === "task") {
173
- const ref = /^Yui Task notification: task=([A-Za-z0-9_-]+) wake=([A-Za-z0-9_-]+)\.\nRead current context: /m.exec(item.text);
173
+ const ref = /^Yui Task notification: task=([A-Za-z0-9_-]+) wake=([A-Za-z0-9_-]+)\.$/m.exec(item.text);
174
174
  if (ref?.[1] === owner.taskId) {
175
175
  const wake = store.listTaskWakes(owner.taskId).find(w => w.id === ref[2] && w.status === "consumed");
176
176
  const ids = new Set(wake?.refs?.filter(r => r.type === "message").map(r => r.id));
@@ -178,7 +178,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
178
178
  }
179
179
  }
180
180
  else {
181
- const ref = /^Yui Global Message: role=([A-Za-z0-9_-]+) message=([A-Za-z0-9_-]+)\.\nRead your Session Context/m.exec(item.text);
181
+ const ref = /^Yui Global Message: role=([A-Za-z0-9_-]+) message=([A-Za-z0-9_-]+)\.$/m.exec(item.text);
182
182
  if (ref?.[1] === owner.roleName)
183
183
  messages = store.listGlobalRoleMessages(owner.roleName)
184
184
  .filter(m => m.id === ref[2] && m.delivery?.via === "provider" && m.deliveryTarget?.nativeSessionId === id);
@@ -20,7 +20,7 @@ schema migration or new snapshot/cache store is introduced.
20
20
  | What changed? | `task context delta <task> --after <cursor>` | Immutable events with a fixed upper bound, not a snapshot of current mutable state |
21
21
  | Which Global input? | `session context <role>` | Identity, profile, authority, current native Turn/retry and separate bounded pending/recent Message pages |
22
22
  | Read Global input | `role message list <role> [--pending]`, `role message show <role> <id>` | Scoped discovery, then exact original; queue acceptance never erases history |
23
- | What arrived in this wake? | `task wake show <task> <wake>` | Fixed window and Message/Run/Event read pointers, not duplicated bodies |
23
+ | What arrived in this wake? | `task wake show <task> <wake>` | Fixed-window inputs/results, attributed optional summaries and exact original reads; `--history` expands diagnostics |
24
24
  | What was assigned? | `task run context <task>/<run>` | Frozen authority and a single `pointers` inventory with summaries; changed identities in `deltaRefs`; current observations remain separate |
25
25
  | Which configuration is real? | `task role session inspect <task> <role>` | Task/Role identity, desired binding, frozen Session, current Provider binding, retry and explicit Host observation; no full Task/Role copies or Provider conversation history |
26
26
 
@@ -18,7 +18,7 @@ CLI 调用成功,不代表 Task、原生 Turn 或远端投递完成。
18
18
  | 发生了什么变化? | `task context delta <task> --after <cursor>` | 固定上界内的不可变事件,不是可变当前状态的快照 |
19
19
  | Global 收到了什么? | `session context <role>` | 身份、Profile、权限、当前原生 Turn/retry,以及分别有界的 pending/recent Message 页 |
20
20
  | 读取 Global 输入 | `role message list <role> [--pending]`、`role message show <role> <id>` | 在自身范围内发现,再读完整原文;队列接受不删除历史 |
21
- | 本次唤醒带来什么? | `task wake show <task> <wake>` | 固定窗口与 Message/Run/Event 读取指针,不重复正文 |
21
+ | 本次唤醒带来什么? | `task wake show <task> <wake>` | 固定窗口的输入/结果、标明来源的可选摘要和确切原文入口;`--history` 展开诊断历史 |
22
22
  | 分配了什么? | `task run context <task>/<run>` | 冻结授权与一份带摘要的 `pointers` 目录;`deltaRefs` 表示变化身份;当前观察独立 |
23
23
  | 实际配置是什么? | `task role session inspect <task> <role>` | Task/Role 身份、期望绑定、冻结 Session、当前 Provider 绑定、retry 与显式 Host 观察;不复制整个 Task/Role 或 Provider 会话历史 |
24
24
 
@@ -69,6 +69,14 @@ Run Pack is a reference directory: read the relevant requirement and message
69
69
  bodies before acting, rather than treating a successful load as the deliverable.
70
70
  Planning Packs expose no Project write scope or Task-completion permission.
71
71
 
72
+ The Manifest pointer is a recovery locator, not a read receipt. Load its exact
73
+ `yui-runtime`, Role Skills and profile on first entry, a new/version-changed
74
+ Session, or missing context after compaction/recovery. Reuse instructions only
75
+ when the same-version full content is still available; Yui stores no "already
76
+ read" state. Always refresh dynamic facts. Leader notifications use
77
+ `currentCommand`; dispatched AgentRuns use the actual Run ID with
78
+ `loadCommand`/`expandCommand`, never their placeholder templates.
79
+
72
80
  Every new Run, including planning, Review and continuation, binds an explicit
73
81
  frozen Snapshot before dispatch. Context reads validate its full identity and
74
82
  content digest, then read its stored values without rebuilding current Task
@@ -123,6 +131,20 @@ Human input, explicit execution entry and activation failure are delivered
123
131
  promptly through the existing wake mechanism. Worker results retain their
124
132
  aggregation window. Busy native input still preserves pending messages.
125
133
 
134
+ Normal and retry notifications share the same bounded presentation: factual
135
+ change labels, attributed optional author summaries, and original read entries.
136
+ An author can begin a Message/report with `Yui-Summary: <one line>` (summary
137
+ at most 512 UTF-8 bytes). It remains in the original record; no schema migration,
138
+ summary store, read-acknowledgement or extra model call is involved. Missing or
139
+ oversized summaries fall back to facts without clipping qualifications.
140
+ Summaries never grant authority, accept results or mark reviews approved.
141
+
142
+ `task wake show <task> <wake>` now defaults to inputs/results and explicit
143
+ references, including older referenced originals. It omits unrelated diagnostic
144
+ events and merely started Runs; `--history` restores full fixed-window discovery.
145
+ Both views retain the fixed window, deduplication, exact detail commands and
146
+ bounded `contentPage` continuations. Read decision-relevant originals in full.
147
+
126
148
  | Evidence | Disposition |
127
149
  | --- | --- |
128
150
  | Busy with proven non-acceptance | Preserve input; a new transport attempt may use the same Session |
@@ -55,6 +55,21 @@ Run Context 冻结 Assignment、来源引用、生效配置和工作区边界。
55
55
  和消息正文,而不是把加载成功当成交付物。规划 Pack 不暴露 Project 写范围或 Task 完成
56
56
  权限。
57
57
 
58
+ Manifest 指针是恢复定位入口,不是已读凭据。首次进入、新会话、版本变化、压缩或恢复后
59
+ 说明缺失时,从其确切路径加载 `yui-runtime`、Role Skills 和 profile;仅当同版本全文仍在
60
+ 当前可用上下文中,才复用而不机械重读。系统不保存“已经读过”状态。动态事实仍须刷新:
61
+ Leader 通知使用 `currentCommand`,显式 AgentRun 使用真实 Run ID 的
62
+ `loadCommand`/`expandCommand`,不可直接执行占位模板。
63
+
64
+ 正常和重试通知共用有界呈现:流程事实、标明作者来源的可选摘要与原文入口。Agent 可在
65
+ Message/报告首行写 `Yui-Summary: <单行摘要>`(摘要最多 512 UTF-8 字节),保留失败、
66
+ 未验证、待决定等限定。摘要存在原文本身,不新增数据格式、摘要存储、已读状态或模型调用;
67
+ 缺失或超长时使用事实模板,不截断关键限定。摘要不是授权、验收或审查通过结论。
68
+
69
+ `task wake show <task> <wake>` 默认展示消息、结果与明确引用(包括窗口外原文),
70
+ 不再枚举无关诊断事件和仅启动的 Run;`--history` 展开完整固定窗口历史。两个视图保留
71
+ 去重、固定窗口、确切原文命令及 `contentPage` 续读。决策所依赖的原文仍须全文读取。
72
+
58
73
  新 Run(包括规划、Review 和消息续接)必须在派发前绑定明确冻结的 Snapshot。
59
74
  Context 读取校验完整身份与内容 digest,然后直接读取已保存的值,不重新收集当前
60
75
  Task 记录。聚合执行从该 Snapshot 读取已选择的 Producer 结果;可写 Project 来自
@@ -51,6 +51,32 @@ merely to avoid a version decision. Package versions remain distinct from protoc
51
51
  and persistent-storage contracts: their compatibility checks and required migrations
52
52
  are unchanged, and the storage rules below do not authorize a package release.
53
53
 
54
+ ### 2.5.0 compatibility and migration notes
55
+
56
+ This minor includes Task110's notification and context-loading improvements.
57
+ Normal and recovery notifications share bounded facts, optional attributed
58
+ author summaries and exact action entries. A summary is not acceptance or
59
+ authority: relevant requirements, user messages and execution results must
60
+ still be read in full.
61
+
62
+ Session instructions name the required Skills explicitly. Reuse is conditional
63
+ on the complete same-version instructions remaining available in context;
64
+ first entry, changed instructions or missing/compacted content require loading
65
+ the exact Manifest and referenced files again. There is no persistent read
66
+ receipt, instruction cache or additional model call for summarization.
67
+
68
+ `task wake show` now focuses on relevant messages, results and referenced
69
+ evidence by default. Use `--history` for the broader fixed-window diagnostic
70
+ view and follow pagination when present. Consumers must not treat the focused
71
+ view as all history. Original records, exact older references, deduplication,
72
+ snapshot boundaries and unknown-input no-replay rules are preserved.
73
+
74
+ No storage migration is added; storage remains **1.6**. Deterministic tests and
75
+ package smoke do not prove real-model instruction reuse or compaction recovery.
76
+ Byte measurements are not token savings and can increase for a simple first
77
+ entry. Publishing this package does not upgrade a global CLI, migrate a Home
78
+ or restart a Controller.
79
+
54
80
  ### 2.4.0 compatibility and migration notes
55
81
 
56
82
  This minor ships the unified Web Task/Operator conversations and same-thread
@@ -36,6 +36,24 @@ Agent 选择一个预先声明的计划,设施从持久状态驱动该计划
36
36
  不能将不兼容冒称为兼容,也不为回避版本选择新增历史兼容机制。包版本与协议、
37
37
  持久存储契约仍然独立:兼容性检查和必要迁移要求不变,下文存储规则不授予发版权限。
38
38
 
39
+ ### 2.5.0 兼容性及迁移说明
40
+
41
+ 本 minor 包含 Task110 的通知和上下文加载优化。正常与恢复通知共用有界事实、
42
+ 可选且注明来源的作者摘要及精确行动入口。摘要不是验收或授权:相关需求、
43
+ 用户消息和执行结果仍须全文读取。
44
+
45
+ 会话说明显式列出所需 Skill。只有完整的同版本说明仍在可用上下文中时才能复用;
46
+ 首次进入、说明变更或内容缺失/被压缩时,须重新加载精确 Manifest 及其引用文件。
47
+ 没有新增持久已读回执、说明缓存或专用模型摘要调用。
48
+
49
+ `task wake show` 默认聚焦相关消息、结果及引用证据;如需更完整的固定窗口诊断,
50
+ 使用 `--history`,有分页时继续读取。调用方不能将聚焦视图当作全部历史。
51
+ 原始记录、精确旧引用、去重、快照边界及未知输入不得重放的规则保持不变。
52
+
53
+ 本版不新增存储迁移,存储仍为 **1.6**。确定性测试及安装包冒烟不证明真实模型
54
+ 会复用说明或在压缩后正确补读;字节测量不是 token 节省,简单首次进入的开销
55
+ 可能增加。发布本包不会升级全局 CLI、迁移 Home 或重启 Controller。
56
+
39
57
  ### 2.4.0 兼容性及迁移说明
40
58
 
41
59
  本 minor 发布统一 Web Task/Operator 对话及同 Thread Codex 终端、任务工作台、
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -14,10 +14,11 @@ their summaries. Resolve links relative to the file containing them.
14
14
 
15
15
  Current Context is a bounded working set, not all Task history. Use
16
16
  `task context list <task> --store <store>` or a domain list for discovery,
17
- then an exact detail read. A wake contains original Message/Run read pointers,
18
- not copied reports. Follow Runtime's `nextCursor` and `contentPage` rules:
19
- read the full relevant window and originals before disposition, without
20
- unconditionally walking unrelated history.
17
+ then an exact detail read. A wake contains attributed change summaries and
18
+ original Message/Run pointers, not complete reports. Follow `yui-runtime`'s
19
+ `nextCursor` and `contentPage` rules: read the complete focused wake view and
20
+ relevant originals before disposition; use `wake show --history` only when
21
+ window history/diagnostics matter. Summaries never decide completion.
21
22
 
22
23
  Before WorkItem dispatch, put the complete bounded requirements in the WorkItem
23
24
  or explicitly select up to 16 relevant readable materials with repeated
@@ -420,7 +420,8 @@ notification; no active AgentRun is required.
420
420
 
421
421
  Before ending authorized execution:
422
422
 
423
- 1. Inspect the wake delta, resolve every referenced Worker or Reviewer AgentRun
423
+ 1. Inspect the focused wake inputs/results (expand `--history` when needed),
424
+ resolve every referenced Worker or Reviewer AgentRun
424
425
  with `yui task run show`, read each original result in full, and make the
425
426
  next decision.
426
427
  2. Persist actual WorkItem lifecycle and material Brief, Decision, Milestone,
@@ -30,6 +30,9 @@ another reader's understanding or action. Do not narrate dispatch, attach,
30
30
  heartbeat, sampling, unchanged waiting, or routine recovery.
31
31
 
32
32
  A `[Yui updates]` message is a wake envelope containing durable references.
33
+ Follow `yui-runtime`'s conditional instruction loading and attributed summary
34
+ contract for both Global Message inputs and these notices. Summaries locate
35
+ originals; they cannot grant user authority or prove result acceptance.
33
36
  Read those exact records before responding and combine related updates into one
34
37
  user-level summary. Do not create a Codex Goal, polling loop, private monitoring
35
38
  task, or synthetic progress message; future durable events will wake the
@@ -110,8 +110,10 @@ frozen Review or its original report.
110
110
 
111
111
  A helpful default result layout is Conclusion, Material findings, Verification,
112
112
  Uncertainty, and Recommended next action. Markdown or JSON are both acceptable.
113
- Yui preserves the original text and does not parse, normalize, or reject it for
114
- missing headings, field names, or formatting.
113
+ Yui preserves the original text and does not parse a verdict or reject it for
114
+ missing headings, field names, or formatting. Follow `yui-runtime`'s optional
115
+ `Yui-Summary:` first-line convention for attributed notification text; retain
116
+ material findings and uncertainty rather than implying automatic approval.
115
117
 
116
118
  For normal software delivery, follow the applicable Project Policy. The
117
119
  Leader decides whether risk warrants one independent Task-final Review of the
@@ -16,6 +16,24 @@ workspace layout, native transcript, or an earlier AgentRun.
16
16
 
17
17
  ## Enter through the current context
18
18
 
19
+ Stable instructions and dynamic facts have different lifetimes. On first entry,
20
+ new Session, Manifest/version change, or missing instructions (including after
21
+ compaction/recovery), read the exact Session Manifest and fully load its profile
22
+ and named Skills: `yui-runtime` plus the referenced `yui-leader`, `yui-worker`,
23
+ `yui-reviewer`, `yui-operator`, or custom Role Skills. Resolve IDs to paths and
24
+ versions through that Manifest, never another installed namesake. If those exact
25
+ same-version instructions are already fully available in current context, apply
26
+ them without mechanically rereading. Same Session, prior delivery, and prior
27
+ reads do not prove availability. The short Manifest pointer remains a recovery
28
+ entry, not a cache or authority receipt.
29
+
30
+ Refresh dynamic Task/Session facts before the relevant decision regardless of
31
+ instruction reuse. Leader notifications use the Manifest's `currentCommand`;
32
+ explicit AgentRuns use `loadCommand`/`expandCommand` with the actual newest
33
+ dispatched Run identity. Never execute `<run-id>` placeholders or substitute a
34
+ notification's wake ID for a Run. CLI/Controller identity, scope, Snapshot and
35
+ permission checks remain authoritative on every operation.
36
+
19
37
  There are two normal Task entry points. A user may continue directly with the
20
38
  current, unrevoked Leader Session: read current context using
21
39
  `yui task context <task-id> --json`. Do not request a self-wake, reopen a Task,
@@ -167,8 +185,13 @@ and digest. A source-change error requires a fresh read, not mixing old and
167
185
  new chunks. Never use a read cursor to repeat a mutation.
168
186
 
169
187
  Read all pages of a relevant original requirement, result, or authorization
170
- before acting on it. For a wake, read its complete fixed window and the
171
- referenced original Messages; the wake itself contains pointers, not reports.
188
+ before acting on it. For a wake, read its complete focused input/result view and
189
+ relevant original Messages. `task wake show <task> <wake>` preserves the fixed
190
+ window and explicit older refs, deduplicates inputs, and provides exact original
191
+ reads. Use `--history` when window history or diagnostics is needed; unrelated
192
+ events and merely started Runs are not required for ordinary result disposition.
193
+ Follow any `contentPage` continuation for either view. The wake contains pointers
194
+ and attributed summaries, not complete reports.
172
195
  For unrelated discovery, stop once the necessary evidence is found. Task
173
196
  event deltas retain their fixed upper bound and are history, not replacement
174
197
  current state. No read acknowledges implementation or consumes a queue.
@@ -244,6 +267,24 @@ create an InputRequest merely to solicit permission for it.
244
267
 
245
268
  ## Return evidence and recover within scope
246
269
 
270
+ When authoring a durable Message or managed final report, put a concise
271
+ `Yui-Summary: <one line>` first when useful for notification readers. Keep the
272
+ summary itself at most 512 UTF-8 bytes, and preserve material failure,
273
+ unverified-result, pending-decision and authority qualifications. Do this as
274
+ part of producing the original, not a separate model call. The optional line
275
+ remains part of the original text; missing or oversized summaries use factual
276
+ templates without blocking delivery. Core may reuse that line as explicitly
277
+ attributed presentation, never parse it into acceptance, review verdict or
278
+ Task state. A returned execution result is not completed requirements; a
279
+ returned review is not approval. Summaries do not replace reading full
280
+ requirements, results or authorizations before decisions.
281
+
282
+ Ordinary Task/Global notifications grant no new Assignment or authority and
283
+ require no separate execution report. Read the referenced originals and advance
284
+ outstanding work within existing authority under the relevant Role Skill.
285
+ Reading context or accepting notification delivery does not implement input.
286
+ Unknown delivery must not be replayed; use the recovery contract below.
287
+
247
288
  Provider acceptance, Context load, AgentRun completion, and Task completion are
248
289
  separate facts. For an explicitly dispatched managed Task AgentRun, end with one truthful
249
290
  final report. Yui automatically correlates that native terminal with the exact
@@ -104,8 +104,9 @@ once and stop without wrappers, permission broadening, or another AgentRun targe
104
104
 
105
105
  Use clear prose. A helpful default is Outcome, Changes, Verification, Risks or
106
106
  blockers, and Recommended next action. This is a communication convention, not
107
- a protocol: Yui preserves the original text and does not parse, normalize, or
108
- reject it for missing headings or invalid JSON.
107
+ a disposition protocol: Yui preserves the original text and does not reject
108
+ missing headings or invalid JSON. The optional `Yui-Summary:` first line
109
+ described in `yui-runtime` is reused only as attributed notification text.
109
110
 
110
111
  Leave managed workspaces intact after handoff. Their owner lifecycle and
111
112
  cleanup belong to the Leader and Yui Core.