@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.
- package/dist/cli/commandCatalog.js +8 -7
- package/dist/cli/updateCommand.js +3 -0
- package/dist/commands/configCommands.js +1 -0
- package/dist/commands/globalRoleCommands.js +42 -35
- package/dist/commands/inboxCommands.js +60 -40
- package/dist/commands/taskCommands.js +7 -3
- package/dist/commands/taskRoleRuntimeStatus.js +2 -0
- package/dist/context/runContextPack.js +48 -3
- package/dist/context/taskContext.js +36 -12
- package/dist/controlPlaneCli.js +46 -12
- package/dist/controller/controller.js +5 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
- package/dist/controller/globalInputDelivery.js +17 -5
- package/dist/controller/globalRuntimeAttention.js +135 -5
- package/dist/controller/runtime.js +4 -0
- package/dist/controller/structuredProviderObservation.js +51 -15
- package/dist/controller/taskHostTerminalRecovery.js +33 -0
- package/dist/executor/fileRoleLaunchPlanner.js +4 -9
- package/dist/message/inboxReceipt.js +30 -0
- package/dist/message/taskInboxOwnership.js +25 -3
- package/dist/models/modelManagement.js +469 -0
- package/dist/models/modelManagementConfig.js +46 -0
- package/dist/models/modelManagementFiles.js +90 -0
- package/dist/models/modelManagementHttp.js +90 -0
- package/dist/models/modelManagementTypes.js +1 -0
- package/dist/output/boundedRead.js +43 -14
- package/dist/runtime/agentEndpointIdentity.js +8 -4
- package/dist/runtime/agentEndpointOwnership.js +5 -6
- package/dist/runtime/agentHost.js +62 -10
- package/dist/runtime/agentHostImplementation.js +18 -0
- package/dist/runtime/managedCaller.js +25 -1
- package/dist/runtime/managedIdentity.js +1 -1
- package/dist/runtime/providerRetry.js +19 -1
- package/dist/runtime/sessionCaller.js +53 -0
- package/dist/storage/contextRecords.js +28 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/sqliteStore.js +9 -5
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +4 -0
- package/dist/task/completionReadiness.js +3 -3
- package/dist/web/assets/assetManifest.js +28 -2
- package/dist/web/assets/client/app/manage.js +6 -0
- package/dist/web/assets/client/app/models.js +20 -0
- package/dist/web/assets/client/app/settings.js +41 -9
- package/dist/web/assets/client/i18n/en.js +180 -2
- package/dist/web/assets/client/i18n/zh-CN.js +178 -2
- package/dist/web/assets/client/index.js +8 -0
- package/dist/web/assets/client/layout/manageShell.js +94 -0
- package/dist/web/assets/client/lib/api.js +13 -1
- package/dist/web/assets/client/lib/i18n.js +5 -3
- package/dist/web/assets/client/views/dock/conversation.js +231 -80
- package/dist/web/assets/client/views/models.js +777 -0
- package/dist/web/assets/client/views/skills.js +22 -15
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shared/manageNav.js +13 -0
- package/dist/web/assets/shell/manage.js +54 -0
- package/dist/web/assets/shell/models.js +7 -0
- package/dist/web/assets/shell/settings.js +1 -35
- package/dist/web/assets/shell/settingsContent.js +30 -0
- package/dist/web/assets/shell/sidebar.js +1 -0
- package/dist/web/assets/styles/index.js +6 -0
- package/dist/web/assets/styles/views/conversation.js +133 -0
- package/dist/web/assets/styles/views/manage.js +71 -0
- package/dist/web/assets/styles/views/models.js +129 -0
- package/dist/web/assets/styles/views/settings.js +28 -14
- package/dist/web/assets/styles/views/sidebar.js +1 -0
- package/dist/web/webServer.js +59 -9
- package/docs/cli-information-contract.md +42 -11
- package/docs/cli-information-contract.zh-CN.md +28 -7
- package/docs/managed-turn-and-session-runtime.md +47 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
- package/docs/release-workflow.md +58 -7
- package/docs/release-workflow.zh-CN.md +39 -4
- package/docs/storage-baseline.md +6 -1
- package/docs/storage-baseline.zh-CN.md +4 -1
- package/docs/testing/review-evaluation.md +148 -0
- package/docs/testing/review-evaluation.zh-CN.md +117 -0
- package/docs/testing/verification-levels.md +6 -2
- package/docs/testing/verification-levels.zh-CN.md +3 -0
- package/package.json +1 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/execution.md +29 -0
- package/skills/yui-leader/references/replicated-execution.md +7 -0
- package/skills/yui-operator/SKILL.md +5 -1
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-reviewer/SKILL.md +35 -0
- package/skills/yui-reviewer/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +53 -7
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/recovery.md +24 -5
|
@@ -193,7 +193,8 @@ const globalSessionChildren = [
|
|
|
193
193
|
{
|
|
194
194
|
name: "skills", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment", "global", "unbound"] },
|
|
195
195
|
summary: "Check current Skill paths, versions and availability for this managed Session without reading bodies.",
|
|
196
|
-
usage: "yui session skills"
|
|
196
|
+
usage: "yui session skills [--cursor <cursor>]",
|
|
197
|
+
options: ["--cursor"]
|
|
197
198
|
},
|
|
198
199
|
{ name: "enter", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Enter a global Role's native session.", usage: "yui session enter <role>" },
|
|
199
200
|
{
|
|
@@ -911,9 +912,9 @@ const taskChildren = [
|
|
|
911
912
|
},
|
|
912
913
|
{
|
|
913
914
|
name: "context", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
|
|
914
|
-
summary: "Load the
|
|
915
|
-
usage: "yui task run context <task>/<run> [--cursor <cursor>] [--json]",
|
|
916
|
-
options: ["--cursor", "--json"],
|
|
915
|
+
summary: "Load bounded AgentRun entry facts; discover refs or read the full authorized pack explicitly.",
|
|
916
|
+
usage: "yui task run context <task>/<run> [--view current|refs|input|full] [--cursor <cursor>] [--json]",
|
|
917
|
+
options: ["--view", "--cursor", "--json"],
|
|
917
918
|
executable: true,
|
|
918
919
|
hidden: true,
|
|
919
920
|
sections: [{ id: "load", title: "Commands", entries: ["expand", "delta"] }],
|
|
@@ -1165,7 +1166,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1165
1166
|
sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack"] }],
|
|
1166
1167
|
children: [
|
|
1167
1168
|
{ name: "list", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
|
|
1168
|
-
summary: "Discover
|
|
1169
|
+
summary: "Discover unhandled own-address entries, including accepted delivery, with current identity and separate handling receipts.",
|
|
1169
1170
|
usage: "yui inbox list [--limit <1..100>] [--cursor <cursor>]", options: ["--limit", "--cursor"] },
|
|
1170
1171
|
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
|
|
1171
1172
|
summary: "Read an exact original or fixed notification window without acknowledgement.",
|
|
@@ -1190,8 +1191,8 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1190
1191
|
children: [
|
|
1191
1192
|
{
|
|
1192
1193
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Discover Global Messages without consuming input.",
|
|
1193
|
-
usage: "yui role message list <role> [--pending] [--limit <1..100>] [--cursor <cursor>]",
|
|
1194
|
-
options: ["--pending", "--limit", "--cursor"]
|
|
1194
|
+
usage: "yui role message list <role> [--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
|
|
1195
|
+
options: ["--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
|
|
1195
1196
|
},
|
|
1196
1197
|
{
|
|
1197
1198
|
name: "show", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Read one exact Global Message; follow contentPage.nextCursor for long content.",
|
|
@@ -65,6 +65,9 @@ export function renderUpdateResult(result) {
|
|
|
65
65
|
})();
|
|
66
66
|
return [
|
|
67
67
|
rendered,
|
|
68
|
+
...(result.outcome === "updated" ? [
|
|
69
|
+
"Runtime: existing Hosts are retained, not upgraded in place. Host control capabilities and event delivery are checked separately; accepted input is never replayed."
|
|
70
|
+
] : []),
|
|
68
71
|
...(result.controllerReconciliation === undefined ? [] : [
|
|
69
72
|
`Controller reconciliation: ${JSON.stringify(result.controllerReconciliation)}`
|
|
70
73
|
]),
|
|
@@ -56,6 +56,7 @@ export function effectiveConfigData(config, domain) {
|
|
|
56
56
|
const health = resolveRuntimeHealth(config.runtimeHealth);
|
|
57
57
|
const all = {
|
|
58
58
|
schemaVersion: config.schemaVersion,
|
|
59
|
+
modelManagement: config.modelManagement ?? null,
|
|
59
60
|
defaultAgent: config.defaultAgent ?? null,
|
|
60
61
|
defaultWorkspace: config.defaultWorkspace ?? null,
|
|
61
62
|
timeZone: resolveTimeZone(config.timeZone),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { roleNotFound, usageError } from "../errors/cliError.js";
|
|
2
2
|
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { requireManagedGlobalCaller } from "../runtime/managedCaller.js";
|
|
4
|
+
import { boundedDocument, recordPage, readOptions, readDigest, messageReceipt, summaryText, summaryMetadata } from "../output/boundedRead.js";
|
|
5
|
+
import { controlProviderRetry, providerRetryProjection, providerRetrySummary, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
6
|
+
import { pendingGlobalInboxMessages, inboxHandlingState, globalInboxDeliveryState } from "../message/inboxReceipt.js";
|
|
5
7
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
6
8
|
import { globalInputBacklog } from "../message/globalInputBacklog.js";
|
|
7
9
|
import { checkCurrentSkills, SKILL_REUSE_RULE } from "../context/currentSkills.js";
|
|
10
|
+
import { runtimeDiagnosticGroups, runtimeDiagnosticEvidence } from "../controller/globalRuntimeAttention.js";
|
|
8
11
|
import { roleSkillIds } from "../context/roleSessionContext.js";
|
|
9
12
|
import { createRoleSessionSet, recordRoleAgentSession } from "../executor/agentExecutor.js";
|
|
10
13
|
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
@@ -76,13 +79,7 @@ function assertGlobalInputAuthority(store, options, target) {
|
|
|
76
79
|
throw usageError("Global input mutation is outside the exact Session authority.");
|
|
77
80
|
}
|
|
78
81
|
if (env.YUI_SESSION_SCOPE === "global") {
|
|
79
|
-
|
|
80
|
-
const session = caller?.sessions[caller.activeAgentId];
|
|
81
|
-
if (session === undefined || session.status !== "active" || env.YUI_AGENT_ID !== session.agentId
|
|
82
|
-
|| env.YUI_ADAPTER_ID !== session.adapterId
|
|
83
|
-
|| (env.CODEX_THREAD_ID ?? env.YUI_NATIVE_SESSION_ID) !== session.nativeSessionId) {
|
|
84
|
-
throw usageError("Global input mutation requires the current native caller Session.");
|
|
85
|
-
}
|
|
82
|
+
requireManagedGlobalCaller(store, env);
|
|
86
83
|
}
|
|
87
84
|
else if (hasManagedIdentity(env)) {
|
|
88
85
|
throw usageError("Incomplete managed identity cannot acquire user authority.");
|
|
@@ -123,9 +120,6 @@ function roleContext(args, store, options) {
|
|
|
123
120
|
if (environment.YUI_SESSION_SCOPE !== "global" || environment.YUI_ROLE !== name) {
|
|
124
121
|
throw usageError("Managed GlobalRole context is outside the exact Session authority.");
|
|
125
122
|
}
|
|
126
|
-
if (environment.YUI_SESSION_MANIFEST === undefined) {
|
|
127
|
-
throw usageError("Managed GlobalRole context requires its Session Manifest.");
|
|
128
|
-
}
|
|
129
123
|
}
|
|
130
124
|
const role = requireRole(name, store);
|
|
131
125
|
const binding = activeRoleAgentBinding(role);
|
|
@@ -140,10 +134,9 @@ function roleContext(args, store, options) {
|
|
|
140
134
|
&& message.inbox?.ack === undefined
|
|
141
135
|
&& message.notDelivered === undefined
|
|
142
136
|
&& (message.interruptThen !== undefined || message.inputControl?.action === "queue"));
|
|
143
|
-
const pending = recordPage(
|
|
144
|
-
const recent = recordPage([...messages].reverse().map(globalMessageSummary), `global:${name}/messages/all`, { limit: 8 });
|
|
137
|
+
const pending = recordPage(pendingGlobalInboxMessages(messages).map(globalMessageSummary), `global:${name}/messages/pending`, { limit: 8 });
|
|
145
138
|
const observedAt = new Date();
|
|
146
|
-
const { oldestAgeMs, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
|
|
139
|
+
const { oldestAgeMs, messages: _backlogMessages, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
|
|
147
140
|
attention: name === "operator" ? store.getWorkMailbox({ kind: "operator" }) : null,
|
|
148
141
|
lifecycle: store.getWorkMailbox({ kind: "global-role-runtime", roleName: name })
|
|
149
142
|
});
|
|
@@ -163,10 +156,7 @@ function roleContext(args, store, options) {
|
|
|
163
156
|
: { nativeSessionId: session.nativeSessionId })
|
|
164
157
|
},
|
|
165
158
|
profile: {
|
|
166
|
-
|
|
167
|
-
responsibilities: role.responsibilities ?? [],
|
|
168
|
-
constraints: role.constraints ?? [],
|
|
169
|
-
expectedOutput: role.expectedOutput,
|
|
159
|
+
read: "Read roleProfileRef from the current Session Manifest; it is not repeated at each entry.",
|
|
170
160
|
skillIds: [
|
|
171
161
|
"yui-runtime",
|
|
172
162
|
...(role.name === "operator" ? ["yui-operator"] : []),
|
|
@@ -183,23 +173,27 @@ function roleContext(args, store, options) {
|
|
|
183
173
|
},
|
|
184
174
|
// Reading pending input is neither delivery nor implementation.
|
|
185
175
|
pending,
|
|
186
|
-
recent,
|
|
187
176
|
backlog,
|
|
188
177
|
reads: {
|
|
189
178
|
ownInbox: "yui inbox list --json",
|
|
190
179
|
messages: `role message list ${name}`,
|
|
191
180
|
pending: `role message list ${name} --pending`,
|
|
192
|
-
detail: `role message show ${name} <message-id
|
|
181
|
+
detail: `role message show ${name} <message-id>`,
|
|
182
|
+
diagnostics: `role message list ${name} --diagnostics`,
|
|
183
|
+
retry: `session retry ${name} show`
|
|
193
184
|
},
|
|
194
|
-
nativeTurn: sessions?.providerBinding?.run
|
|
195
|
-
|
|
185
|
+
nativeTurn: sessions?.providerBinding?.run == null ? null : {
|
|
186
|
+
attemptId: sessions.providerBinding.run.attemptId,
|
|
187
|
+
nativeTurnId: sessions.providerBinding.run.nativeTurnId,
|
|
188
|
+
status: sessions.providerBinding.run.status
|
|
189
|
+
},
|
|
190
|
+
retry: providerRetrySummary(sessions?.providerBinding),
|
|
196
191
|
// Historical interrupt receipts are available through Session inspection;
|
|
197
192
|
// only this Turn's exact identity is required at entry.
|
|
198
193
|
sessionManifestPath: environment.YUI_SESSION_MANIFEST,
|
|
199
194
|
skillCheckCommand: "yui session skills --json",
|
|
200
195
|
currentSkills,
|
|
201
196
|
skillLoadingRule: SKILL_REUSE_RULE,
|
|
202
|
-
skillPackages: session?.effective.skillPackages ?? null,
|
|
203
197
|
cliCommand: "yui"
|
|
204
198
|
});
|
|
205
199
|
// Elapsed wall-clock time is live display metadata, not a changing source
|
|
@@ -214,7 +208,7 @@ function roleContext(args, store, options) {
|
|
|
214
208
|
`Effective revision: ${role.launchRevision}`,
|
|
215
209
|
`Skills: ${context.profile.skillIds.join(", ") || "none"}`,
|
|
216
210
|
`Authority: ${context.authority.view}; Task implementation: no`,
|
|
217
|
-
`Pending messages: ${pending.total};
|
|
211
|
+
`Pending messages: ${pending.total}; history is available on demand`,
|
|
218
212
|
JSON.stringify(data),
|
|
219
213
|
""
|
|
220
214
|
].join("\n") };
|
|
@@ -222,12 +216,15 @@ function roleContext(args, store, options) {
|
|
|
222
216
|
function globalMessageSummary(message) {
|
|
223
217
|
return {
|
|
224
218
|
id: message.id, createdAt: message.createdAt, kind: message.kind,
|
|
225
|
-
digest: readDigest(message), summary: message.body
|
|
219
|
+
digest: readDigest(message), summary: summaryText(message.body),
|
|
220
|
+
read: `role message show ${message.roleName} ${message.id}`,
|
|
226
221
|
delivery: message.delivery === undefined ? undefined : { deliveredAt: message.delivery.deliveredAt },
|
|
227
|
-
notDelivered: message.notDelivered === undefined ? undefined : { summary: message.notDelivered.reason
|
|
222
|
+
notDelivered: message.notDelivered === undefined ? undefined : { summary: summaryText(message.notDelivered.reason) },
|
|
228
223
|
control: message.control === undefined ? undefined : { outcome: message.control.outcome, observedAt: message.control.observedAt },
|
|
229
224
|
inputAction: message.inputControl?.action,
|
|
230
|
-
|
|
225
|
+
handling: inboxHandlingState(message.inbox),
|
|
226
|
+
deliveryState: globalInboxDeliveryState(message),
|
|
227
|
+
deliveryTarget: message.deliveryTarget === undefined ? undefined : summaryMetadata(message.deliveryTarget),
|
|
231
228
|
interruptThen: message.interruptThen === undefined ? undefined : true
|
|
232
229
|
};
|
|
233
230
|
}
|
|
@@ -629,8 +626,13 @@ function globalRoleMessage(args, store, options) {
|
|
|
629
626
|
}
|
|
630
627
|
requireRole(name, store);
|
|
631
628
|
const pending = tail.includes("--pending");
|
|
632
|
-
const
|
|
633
|
-
|
|
629
|
+
const diagnostics = tail.includes("--diagnostics");
|
|
630
|
+
const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--diagnostics"), action === "list"
|
|
631
|
+
? ["--cursor", "--limit", "--diagnostic"] : ["--cursor"]);
|
|
632
|
+
const diagnostic = parsed.values.get("--diagnostic");
|
|
633
|
+
if (Number(pending) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
|
|
634
|
+
|| action === "show" && diagnostics)
|
|
635
|
+
throw usageError("Choose one Message discovery view.");
|
|
634
636
|
const messages = store.listGlobalRoleMessages(name);
|
|
635
637
|
let data;
|
|
636
638
|
if (action === "show") {
|
|
@@ -644,11 +646,16 @@ function globalRoleMessage(args, store, options) {
|
|
|
644
646
|
else {
|
|
645
647
|
if (parsed.positionals.length !== 0)
|
|
646
648
|
throw usageError("Role message list <role> [--pending] [--limit <n>] [--cursor <cursor>].");
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
649
|
+
if (diagnostics) {
|
|
650
|
+
data = recordPage(runtimeDiagnosticGroups(messages).reverse().map(group => ({
|
|
651
|
+
...group, source: summaryMetadata(group.source),
|
|
652
|
+
read: `role message show ${name} ${group.latestEvidence}`
|
|
653
|
+
})), `global:${name}/diagnostics`, parsed);
|
|
654
|
+
return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
|
|
655
|
+
}
|
|
656
|
+
const selected = diagnostic !== undefined ? runtimeDiagnosticEvidence(messages, diagnostic)
|
|
657
|
+
: pending ? pendingGlobalInboxMessages(messages) : [...messages].reverse();
|
|
658
|
+
data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${diagnostic === undefined ? pending ? "pending" : "all" : `diagnostic/${diagnostic}`}`, parsed);
|
|
652
659
|
}
|
|
653
660
|
return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
|
|
654
661
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { boundedDocument, readDigest, readOptions, recordPage } from "../output/boundedRead.js";
|
|
1
|
+
import { requireManagedCaller } from "../runtime/sessionCaller.js";
|
|
2
|
+
import { boundedDocument, readDigest, readOptions, recordPage, summaryMetadata, summaryText } from "../output/boundedRead.js";
|
|
3
3
|
import { claimPending, completeProcessing } from "../coordination/workMailbox.js";
|
|
4
4
|
import { createTaskWake, latestTaskWake } from "../scheduler/taskWake.js";
|
|
5
5
|
import { expandTaskMessageResult } from "../message/message.js";
|
|
6
6
|
import { messageAssignmentBlocker } from "../message/messageContinuation.js";
|
|
7
|
-
import {
|
|
7
|
+
import { globalInboxDeliveryState, inboxHandlingState, isGlobalInboxMessage, pendingGlobalInboxMessages } from "../message/inboxReceipt.js";
|
|
8
|
+
import { deliveredMessageWake, isLeaderSteer, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
8
9
|
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
9
10
|
import { isTaskRecordRetired, retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
10
11
|
const target = (address) => ({ kind: "role", taskId: address.taskId, roleName: address.roleName });
|
|
@@ -13,11 +14,10 @@ const pendingId = (pending) => `pending-${readDigest(pending)}`;
|
|
|
13
14
|
const sameSession = (a, b) => a.scope === b.scope && a.taskId === b.taskId
|
|
14
15
|
&& a.roleName === b.roleName && a.agentId === b.agentId && a.nativeSessionId === b.nativeSessionId;
|
|
15
16
|
function addressOf(store, env) {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const caller = requireManagedCaller(store, env);
|
|
18
|
+
if (caller.scope === "global") {
|
|
18
19
|
return { scope: "global", roleName: caller.roleName, agentId: caller.agentId, nativeSessionId: caller.nativeSessionId };
|
|
19
20
|
}
|
|
20
|
-
const caller = requireManagedTaskCaller(store, env);
|
|
21
21
|
if (caller.roleName !== "leader" && caller.currentRunId === undefined) {
|
|
22
22
|
throw new Error("An Assignment inbox requires its current active AgentRun.");
|
|
23
23
|
}
|
|
@@ -42,7 +42,7 @@ function ownsMessage(store, address, message) {
|
|
|
42
42
|
function entries(store, address) {
|
|
43
43
|
if (address.scope === "global")
|
|
44
44
|
return store.listGlobalRoleMessages(address.roleName)
|
|
45
|
-
.filter(
|
|
45
|
+
.filter(isGlobalInboxMessage);
|
|
46
46
|
const messages = store.listMessages(address.taskId).filter(message => ownsMessage(store, address, message));
|
|
47
47
|
return address.roleName === "leader" ? [...store.listTaskWakes(address.taskId), ...messages] : messages;
|
|
48
48
|
}
|
|
@@ -53,6 +53,10 @@ function getEntry(store, address, id) {
|
|
|
53
53
|
return entry;
|
|
54
54
|
}
|
|
55
55
|
function isWake(entry) { return "fromCursor" in entry; }
|
|
56
|
+
function acceptedInput(entry) {
|
|
57
|
+
return isWake(entry) ? entry.status === "consumed" : "roleName" in entry
|
|
58
|
+
? globalInboxDeliveryState(entry) === "accepted" : entry.control?.outcome === "accepted";
|
|
59
|
+
}
|
|
56
60
|
function assertSelectedTarget(address, message) {
|
|
57
61
|
const selected = message.inputControl?.expectedSessionId;
|
|
58
62
|
const globalTarget = "roleName" in message ? message.deliveryTarget : undefined;
|
|
@@ -82,23 +86,33 @@ function blocker(store, address, entry) {
|
|
|
82
86
|
return sameSession(entry.inbox.claim.actor, address) ? undefined : "claimed-by-previous-session";
|
|
83
87
|
}
|
|
84
88
|
if (isWake(entry)) {
|
|
89
|
+
// Native acceptance releases transport ownership, not semantic handling.
|
|
90
|
+
// The current Leader may explicitly claim the fixed delivered window.
|
|
85
91
|
if (entry.status === "consumed")
|
|
86
|
-
return
|
|
92
|
+
return undefined;
|
|
87
93
|
return "automatic-notification-owns-window";
|
|
88
94
|
}
|
|
89
95
|
if (address.scope === "task" && taskMessageInboxReceipt(store, entry) !== undefined) {
|
|
90
96
|
return "claimed-by-notification-window";
|
|
91
97
|
}
|
|
92
|
-
if (entry.
|
|
93
|
-
return "explicit-interrupt-handoff";
|
|
94
|
-
if (entry.control !== undefined && entry.control.outcome !== "rejected")
|
|
98
|
+
if (entry.control !== undefined && ["pending", "delivery-unknown"].includes(entry.control.outcome)) {
|
|
95
99
|
return `delivery-${entry.control.outcome}`;
|
|
100
|
+
}
|
|
101
|
+
const accepted = acceptedInput(entry);
|
|
102
|
+
if (entry.interruptThen !== undefined && !accepted
|
|
103
|
+
&& !(address.scope === "task" && entry.continuation?.runId === address.runId
|
|
104
|
+
&& address.runId !== undefined))
|
|
105
|
+
return "explicit-interrupt-handoff";
|
|
96
106
|
// Both steer paths reserve pending control before submission and arbitrate
|
|
97
107
|
// with inbox claims. No control is unsubmitted, not an unknown delivery.
|
|
98
108
|
if (address.scope === "global") {
|
|
99
109
|
const message = entry;
|
|
100
|
-
if (message.delivery !== undefined)
|
|
110
|
+
if (message.delivery !== undefined && !accepted)
|
|
101
111
|
return `already-delivered-${message.delivery.via}`;
|
|
112
|
+
// Explicitly settling delivered input is not a new submission or target
|
|
113
|
+
// selection. A current rightful successor retains the original target.
|
|
114
|
+
if (accepted)
|
|
115
|
+
return undefined;
|
|
102
116
|
if (message.deliveryTarget !== undefined && (message.deliveryTarget.nativeSessionId !== address.nativeSessionId
|
|
103
117
|
|| message.deliveryTarget.agentId !== address.agentId))
|
|
104
118
|
return "selected-native-session-changed";
|
|
@@ -111,9 +125,10 @@ function blocker(store, address, entry) {
|
|
|
111
125
|
}
|
|
112
126
|
else {
|
|
113
127
|
const message = entry;
|
|
114
|
-
if (message.continuation?.runId !== undefined)
|
|
128
|
+
if (message.continuation?.runId !== undefined && message.continuation.runId !== address.runId) {
|
|
115
129
|
return "continuation-already-dispatched";
|
|
116
|
-
|
|
130
|
+
}
|
|
131
|
+
if (!accepted && message.inputControl?.expectedSessionId !== undefined
|
|
117
132
|
&& message.inputControl.expectedSessionId !== address.nativeSessionId)
|
|
118
133
|
return "selected-native-session-changed";
|
|
119
134
|
return address.roleName === "leader" && isLeaderSteer(message) ? undefined : messageAssignmentBlocker(store, message);
|
|
@@ -135,8 +150,15 @@ function checkMutation(store, address) {
|
|
|
135
150
|
}
|
|
136
151
|
function summary(store, address, entry) {
|
|
137
152
|
return { id: entry.id, kind: isWake(entry) ? "notification-window" : "message",
|
|
138
|
-
createdAt: entry.createdAt, summary: isWake(entry) ? entry.reasons.join(", ")
|
|
139
|
-
blocked: blocker(store, address, entry) ?? null, inbox: entry.inbox ?? null,
|
|
153
|
+
createdAt: entry.createdAt, summary: summaryText(isWake(entry) ? entry.reasons.join(", ") : entry.body),
|
|
154
|
+
blocked: blocker(store, address, entry) ?? null, inbox: summaryMetadata(entry.inbox ?? null),
|
|
155
|
+
handling: inboxHandlingState(entry.inbox),
|
|
156
|
+
delivery: isWake(entry) ? entry.status : summaryMetadata({
|
|
157
|
+
control: entry.control ?? null,
|
|
158
|
+
...(address.scope === "global" ? { receipt: entry.delivery ?? null,
|
|
159
|
+
state: globalInboxDeliveryState(entry) }
|
|
160
|
+
: { continuation: entry.continuation ?? null })
|
|
161
|
+
}),
|
|
140
162
|
read: `yui inbox show ${entry.id}`, ...(isWake(entry)
|
|
141
163
|
? { originals: `yui task wake show ${entry.taskId} ${entry.id} --json` } : {}) };
|
|
142
164
|
}
|
|
@@ -155,21 +177,19 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
155
177
|
if (parsed.positionals.length)
|
|
156
178
|
throw new Error("Inbox list accepts only --limit and --cursor.");
|
|
157
179
|
const retired = address.scope === "task" ? retiredTaskRecordIds(store.listEvents(address.taskId), "message") : new Set();
|
|
158
|
-
const selected =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
: address.scope === "global" ? entry.delivery === undefined
|
|
162
|
-
: entry.continuation?.runId === undefined));
|
|
180
|
+
const selected = address.scope === "global" ? pendingGlobalInboxMessages(store.listGlobalRoleMessages(address.roleName))
|
|
181
|
+
: entries(store, address).filter(entry => (isWake(entry) || !retired.has(entry.id))
|
|
182
|
+
&& inboxHandlingState(entry.inbox) !== "handled");
|
|
163
183
|
const items = selected.map(entry => summary(store, address, entry));
|
|
164
184
|
if (address.scope === "task" && address.roleName === "leader") {
|
|
165
185
|
const mailbox = store.getWorkMailbox(target(address));
|
|
166
186
|
if (mailbox?.pending != null)
|
|
167
187
|
items.push({ id: pendingId(mailbox.pending), kind: "notification-window",
|
|
168
|
-
summary: mailbox.pending.reasons.join(", ")
|
|
188
|
+
summary: summaryText(mailbox.pending.reasons.join(", ")),
|
|
169
189
|
blocked: mailbox.processing === null && !unfinishedLeaderInbox(store, address.taskId) ? null : "earlier-window-owned",
|
|
170
190
|
read: `yui inbox show ${pendingId(mailbox.pending)}` });
|
|
171
191
|
}
|
|
172
|
-
return recordPage(items, source(address), parsed);
|
|
192
|
+
return recordPage(items, source(address), parsed, { identity: address });
|
|
173
193
|
}
|
|
174
194
|
if (action === "show") {
|
|
175
195
|
const parsed = readOptions(tail, ["--cursor"]);
|
|
@@ -230,7 +250,8 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
230
250
|
if (ref.type !== "message")
|
|
231
251
|
continue;
|
|
232
252
|
const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
|
|
233
|
-
if (message !== undefined && taskMessageInboxReceipt(tx, message) !== undefined
|
|
253
|
+
if (message !== undefined && (taskMessageInboxReceipt(tx, message) !== undefined
|
|
254
|
+
|| deliveredMessageWake(tx, message) !== undefined)
|
|
234
255
|
|| message?.interruptThen !== undefined
|
|
235
256
|
|| message?.control !== undefined && message.control.outcome !== "rejected"
|
|
236
257
|
|| message?.inputControl?.expectedSessionId !== undefined
|
|
@@ -241,16 +262,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
241
262
|
const latest = latestTaskWake(tx.listTaskWakes(current.taskId));
|
|
242
263
|
// Pin each mutable Draft original's existing update event, not a clock
|
|
243
264
|
// comparison. A later edit is distinct input even within the same tick.
|
|
244
|
-
const refs =
|
|
245
|
-
for (const ref of mailbox.pending.refs) {
|
|
246
|
-
if (ref.type !== "message" || ref.taskId !== current.taskId)
|
|
247
|
-
continue;
|
|
248
|
-
const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
|
|
249
|
-
const revision = message === undefined ? undefined : latestMessageUpdate(tx, message);
|
|
250
|
-
if (revision !== undefined && !refs.some(ref => ref.type === "event" && ref.id === revision.id)) {
|
|
251
|
-
refs.push({ type: "event", taskId: current.taskId, id: revision.id });
|
|
252
|
-
}
|
|
253
|
-
}
|
|
265
|
+
const refs = pinMessageRevisions(tx, current.taskId, mailbox.pending.refs);
|
|
254
266
|
const wake = createTaskWake({ id: tx.nextTaskWakeId(current.taskId), taskId: current.taskId,
|
|
255
267
|
reasons: mailbox.pending.reasons, refs,
|
|
256
268
|
fromCursor: latest?.toCursor ?? tx.getTask(current.taskId).createdAt,
|
|
@@ -273,6 +285,17 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
273
285
|
const blocked = blocker(tx, current, entry);
|
|
274
286
|
if (blocked !== undefined)
|
|
275
287
|
throw new Error(`Inbox entry cannot be claimed: ${blocked}.`);
|
|
288
|
+
if (isWake(entry) && !acceptedInput(entry)) {
|
|
289
|
+
for (const ref of entry.refs ?? []) {
|
|
290
|
+
if (ref.type !== "message")
|
|
291
|
+
continue;
|
|
292
|
+
const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
|
|
293
|
+
if (message !== undefined)
|
|
294
|
+
assertSelectedTarget(current, message);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (!isWake(entry) && !acceptedInput(entry))
|
|
298
|
+
assertSelectedTarget(current, entry);
|
|
276
299
|
for (const previous of entries(tx, current)) {
|
|
277
300
|
if (previous.id === entry.id)
|
|
278
301
|
break;
|
|
@@ -281,10 +304,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
281
304
|
if (current.scope === "task"
|
|
282
305
|
&& isTaskRecordRetired(tx.listEvents(current.taskId), "message", previous.id))
|
|
283
306
|
continue;
|
|
284
|
-
if (current.scope === "global" &&
|
|
285
|
-
|| previous.notDelivered !== undefined))
|
|
286
|
-
continue;
|
|
287
|
-
if (current.scope === "task" && previous.continuation?.runId !== undefined)
|
|
307
|
+
if (current.scope === "global" && previous.notDelivered !== undefined)
|
|
288
308
|
continue;
|
|
289
309
|
if (previous.inbox !== undefined && sameSession(previous.inbox.claim.actor, current))
|
|
290
310
|
continue;
|
|
@@ -299,14 +319,14 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
299
319
|
// the original. The current rightful successor may settle a claim
|
|
300
320
|
// acquired by the exact selected Session, retaining both identities.
|
|
301
321
|
const claimant = entry.inbox.claim.actor;
|
|
302
|
-
if (isWake(entry)) {
|
|
322
|
+
if (isWake(entry) && !acceptedInput(entry)) {
|
|
303
323
|
const messageIds = new Set(entry.refs?.filter(ref => ref.type === "message").map(ref => ref.id));
|
|
304
324
|
for (const message of tx.listMessages(current.taskId)) {
|
|
305
325
|
if (messageIds.has(message.id))
|
|
306
326
|
assertSelectedTarget(claimant, message);
|
|
307
327
|
}
|
|
308
328
|
}
|
|
309
|
-
else
|
|
329
|
+
else if (!isWake(entry) && !acceptedInput(entry))
|
|
310
330
|
assertSelectedTarget(claimant, entry);
|
|
311
331
|
if (!isWake(entry) && current.scope === "task"
|
|
312
332
|
&& !(current.roleName === "leader" && isLeaderSteer(entry))) {
|
|
@@ -7,7 +7,7 @@ import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from ".
|
|
|
7
7
|
import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
|
|
8
8
|
import { contextContentDigest, contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
9
9
|
import { sourceRunContextValue } from "../context/sourceRunContext.js";
|
|
10
|
-
import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, freezeCandidateContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
|
|
10
|
+
import { buildRunContextDelta, buildRunContextPack, runContextEntry, runContextPointerPage, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, freezeCandidateContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
|
|
11
11
|
import { createRunInput } from "../context/runInputContract.js";
|
|
12
12
|
import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
|
|
13
13
|
import { assertContextRecordReadable, readTaskContextResource } from "../context/taskContext.js";
|
|
@@ -4777,14 +4777,18 @@ function runContextCommand(args, store, options) {
|
|
|
4777
4777
|
const data = boundedDocument(delta, `run:${taskId}/${runId}/delta/${parsed.values.get("--after")}`, parsed.cursor);
|
|
4778
4778
|
return output(`${JSON.stringify(data)}\n`, { contextDelta: data });
|
|
4779
4779
|
}
|
|
4780
|
-
const parsed = readOptions(rest, ["--cursor"]);
|
|
4780
|
+
const parsed = readOptions(rest, ["--cursor", "--view"]);
|
|
4781
4781
|
if (first === undefined || parsed.positionals.length !== 0) {
|
|
4782
4782
|
throw usageError("Task run context usage: yui task run context <task>/<run>.");
|
|
4783
4783
|
}
|
|
4784
4784
|
const { taskId, runId } = parseRunContextReference(first);
|
|
4785
4785
|
authorizeRunContext(store, taskId, runId, options.environment);
|
|
4786
4786
|
const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
|
|
4787
|
-
const
|
|
4787
|
+
const view = parsed.values.get("--view") ?? "current";
|
|
4788
|
+
if (!["current", "full", "refs", "input"].includes(view))
|
|
4789
|
+
throw usageError("Run Context view must be current, refs, input or full.");
|
|
4790
|
+
const data = view === "refs" ? runContextPointerPage(pack, parsed)
|
|
4791
|
+
: boundedDocument(view === "full" ? pack : view === "input" ? pack.input : runContextEntry(pack), `run:${taskId}/${runId}/context/${view}`, parsed.cursor);
|
|
4788
4792
|
return output(`${JSON.stringify(data)}\n`, { context: data });
|
|
4789
4793
|
}
|
|
4790
4794
|
function parseRunContextReference(value) {
|
|
@@ -18,6 +18,7 @@ export function taskRoleHostDiagnostic(host) {
|
|
|
18
18
|
const delivery = snapshot.eventDelivery;
|
|
19
19
|
return [
|
|
20
20
|
`Provider Host=${snapshot.state}`,
|
|
21
|
+
`terminal reconciliation=${snapshot.controlCapabilities?.includes("reconcile-turn") ? "supported" : "unreported/unsupported"}`,
|
|
21
22
|
delivery === undefined ? "event delivery=unreported" :
|
|
22
23
|
`pending facts=${delivery.pending}; native terminals awaiting acknowledgement=${delivery.pendingTerminals}`,
|
|
23
24
|
delivery?.failure === undefined ? undefined :
|
|
@@ -35,6 +36,7 @@ export function withTaskRoleHostObservation(status, host) {
|
|
|
35
36
|
return { ...status, host: { detail: "Host does not match the recorded native Session." } };
|
|
36
37
|
}
|
|
37
38
|
const reportingNeedsAttention = snapshot !== undefined && (snapshot.state === "failed" || snapshot.state === "exited"
|
|
39
|
+
|| snapshot.eventDelivery === undefined
|
|
38
40
|
|| snapshot.eventDelivery?.failure !== undefined
|
|
39
41
|
|| (snapshot.eventDelivery?.pendingTerminals ?? 0) > 0);
|
|
40
42
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { providerRetrySummary } from "../runtime/providerRetry.js";
|
|
2
2
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
3
3
|
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
4
4
|
import { RUN_INPUT_MAX_DELTAS, requireRunContextSnapshotRef } from "./runInputContract.js";
|
|
@@ -9,6 +9,7 @@ import { formatGitArtifactRef, validateGitArtifactRef } from "../artifacts/gitAr
|
|
|
9
9
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
10
10
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
|
|
11
11
|
import { MAX_SYNTHESIS_SOURCE_RUNS, sourceRunContextValue } from "./sourceRunContext.js";
|
|
12
|
+
import { recordPage, summaryText } from "../output/boundedRead.js";
|
|
12
13
|
export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
|
|
13
14
|
function mergeRequiredContext(automatic, required, requiredPolicy = "current") {
|
|
14
15
|
const entries = new Map(automatic.map(entry => [`${entry.ref.store}/${entry.ref.refId}`, entry]));
|
|
@@ -356,6 +357,41 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
356
357
|
});
|
|
357
358
|
return pack;
|
|
358
359
|
}
|
|
360
|
+
/** The frozen pack remains authority. Entry is a bounded discovery projection;
|
|
361
|
+
* historical Skill packages and complete inventories are explicit reads only. */
|
|
362
|
+
export function runContextPointerPage(pack, options = {}) {
|
|
363
|
+
const priority = ["task", "work-item", "review-round", "role-profile"];
|
|
364
|
+
const pointers = [...pack.pointers].sort((a, b) => {
|
|
365
|
+
const rank = (store) => priority.includes(store) ? priority.indexOf(store) : priority.length;
|
|
366
|
+
return rank(a.store) - rank(b.store);
|
|
367
|
+
}).map(pointer => ({ ...pointer, summary: summaryText(pointer.summary ?? "") }));
|
|
368
|
+
return recordPage(pointers, `run:${pack.identity.taskId}/${pack.identity.runId}/refs/${pack.snapshot.digest}`, options);
|
|
369
|
+
}
|
|
370
|
+
export function runContextEntry(pack) {
|
|
371
|
+
const { skillPackages: _packages, pointers: _pointers, deltaRefs, liveTaskState, input, ...identity } = pack;
|
|
372
|
+
const { directive, ...inputIdentity } = input;
|
|
373
|
+
const { items, ...pointerPage } = runContextPointerPage(pack, { limit: 16 });
|
|
374
|
+
return {
|
|
375
|
+
...identity, input: { ...inputIdentity, ...(directive === undefined ? {} : {
|
|
376
|
+
directiveSummary: summaryText(directive), directiveOmitted: true
|
|
377
|
+
}) }, pointers: items, pointerPage,
|
|
378
|
+
deltaRefs: deltaRefs.slice(0, 16), deltaRefsTotal: deltaRefs.length,
|
|
379
|
+
inventory: { ...pack.inventory, complete: pointerPage.complete },
|
|
380
|
+
liveTaskState: {
|
|
381
|
+
providerRetries: liveTaskState.providerRetries.slice(0, 8),
|
|
382
|
+
activeRuns: liveTaskState.activeRuns.slice(0, 8),
|
|
383
|
+
activeTaskReviews: liveTaskState.activeTaskReviews.slice(0, 8),
|
|
384
|
+
totals: { providerRetries: liveTaskState.providerRetries.length,
|
|
385
|
+
activeRuns: liveTaskState.activeRuns.length, activeTaskReviews: liveTaskState.activeTaskReviews.length }
|
|
386
|
+
},
|
|
387
|
+
reads: {
|
|
388
|
+
refs: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view refs`,
|
|
389
|
+
full: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view full`,
|
|
390
|
+
input: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view input`,
|
|
391
|
+
live: `yui task context ${pack.identity.taskId}`
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
359
395
|
/** Reads the Task's current in-flight execution, outside the frozen contract. */
|
|
360
396
|
function readLiveTaskState(store, taskId) {
|
|
361
397
|
const activeRuns = store.listActiveRuns(taskId)
|
|
@@ -376,7 +412,7 @@ function readLiveTaskState(store, taskId) {
|
|
|
376
412
|
}));
|
|
377
413
|
return Object.freeze({
|
|
378
414
|
providerRetries: Object.freeze(store.listRoleSessionSets(taskId).flatMap(set => set.providerBinding?.retry === undefined
|
|
379
|
-
? [] : [{ roleName: set.owner.roleName, retry:
|
|
415
|
+
? [] : [{ roleName: set.owner.roleName, retry: providerRetrySummary(set.providerBinding) }])),
|
|
380
416
|
activeRuns: Object.freeze(activeRuns),
|
|
381
417
|
activeTaskReviews: Object.freeze(activeTaskReviews)
|
|
382
418
|
});
|
|
@@ -447,9 +483,18 @@ function collectAuthorizedContext(store, run) {
|
|
|
447
483
|
const view = contextView(run);
|
|
448
484
|
const result = [materialize("L2", "task", task.id, task)];
|
|
449
485
|
const brief = store.getTaskBrief(task.id);
|
|
450
|
-
if (brief !== null && view === "leader") {
|
|
486
|
+
if (brief !== null && (view === "leader" || view === "reviewer")) {
|
|
451
487
|
result.push(materialize("L2", "task-brief", task.id, brief));
|
|
452
488
|
}
|
|
489
|
+
// Reviewers need the current material index and effective decisions, not
|
|
490
|
+
// only the original Task description. Freeze them with the candidate so
|
|
491
|
+
// later edits cannot silently change the review contract. Artifact bodies
|
|
492
|
+
// remain on-demand, commit-pinned reads; do not crawl prose or Task history.
|
|
493
|
+
if (view === "reviewer") {
|
|
494
|
+
for (const decision of store.listDecisions(task.id).filter(({ status }) => status === "active")) {
|
|
495
|
+
result.push(materialize("L2", "task-decision", decision.id, decision));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
453
498
|
result.push(...collectRunContextOverlays(store, run));
|
|
454
499
|
result.push(...collectSourceRunContext(store, run));
|
|
455
500
|
if (run.workItemId !== undefined) {
|