@zq-silk/yui 2.9.0 → 2.10.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/README.md +5 -3
- package/dist/cli/commandCatalog.js +21 -9
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/commands/globalRoleCommands.js +30 -9
- package/dist/commands/inboxCommands.js +79 -22
- package/dist/commands/taskCommands.js +6 -1
- package/dist/commands/taskProjectDeliveryCommand.js +56 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -3
- package/dist/context/runContextPack.js +1 -0
- package/dist/context/runInputContract.js +1 -0
- package/dist/context/taskContext.js +1 -1
- package/dist/context/taskPhase.js +1 -1
- package/dist/controlPlaneCli.js +32 -17
- package/dist/controller/fileSchedulerStoreAdapter.js +59 -15
- package/dist/controller/globalInputDelivery.js +17 -7
- package/dist/controller/inboxTurnCheck.js +64 -0
- package/dist/controller/providerRetryAdmission.js +2 -0
- package/dist/external/gitlabPublicationVerifier.js +10 -28
- package/dist/grant/taskAuthorization.js +7 -2
- package/dist/interaction/notificationPresentation.js +9 -0
- package/dist/message/globalInboxRecovery.js +44 -0
- package/dist/message/globalInterrupt.js +1 -1
- package/dist/message/inboxReceipt.js +14 -6
- package/dist/message/inboxTurn.js +86 -0
- package/dist/message/message.js +11 -0
- package/dist/message/taskInboxOwnership.js +20 -6
- package/dist/repository/workspaceCleanupInspection.js +6 -1
- package/dist/storage/sqliteSchema.js +21 -0
- package/dist/storage/sqliteStore.js +13 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/task/archiveDiagnostics.js +1 -1
- package/dist/task/archivePreflight.js +1 -1
- package/dist/task/projectDeliveryScope.js +20 -0
- package/dist/task/publicationToolObservation.js +4 -6
- package/dist/task/remoteDelivery.js +19 -8
- package/dist/task/remoteDeliveryService.js +5 -4
- package/dist/web/assets/client/i18n/en.js +4 -2
- package/dist/web/assets/client/i18n/zh-CN.js +4 -2
- package/dist/web/assets/client/views/task/deliveryResults.js +6 -2
- package/dist/web/controllerWeb.js +73 -21
- package/dist/web/taskPreviews.js +21 -13
- package/dist/web/webAccess.js +109 -0
- package/dist/web/webServer.js +45 -44
- package/docs/architecture/capabilities-and-resources.md +6 -4
- package/docs/architecture/capabilities-and-resources.zh-CN.md +5 -4
- package/docs/cli-information-contract.md +14 -2
- package/docs/cli-information-contract.zh-CN.md +9 -1
- package/docs/project-delivery-scope.md +59 -0
- package/docs/release-workflow.md +43 -0
- package/docs/release-workflow.zh-CN.md +35 -0
- package/docs/storage-baseline.md +14 -1
- package/docs/storage-baseline.zh-CN.md +11 -1
- package/docs/task-delivery.md +8 -2
- package/docs/task-delivery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +2 -2
- package/i18n/README.zh-CN.md +6 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +26 -0
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-operator/SKILL.md +31 -0
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +58 -5
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/recovery.md +21 -0
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ not from terminal windows you juggle or details you have to remember.
|
|
|
30
30
|
- **Bring your own Agent** — Codex CLI, Claude Code CLI and ACP peers run behind
|
|
31
31
|
one boundary and stay replaceable without losing the Task.
|
|
32
32
|
- **Local-first and private** — everything runs on your machine for one trusted
|
|
33
|
-
user; Web
|
|
33
|
+
user; Web defaults to loopback, with explicitly enabled authenticated LAN access.
|
|
34
34
|
- **Isolated by default** — repository work happens in managed Git worktrees;
|
|
35
35
|
the stable checkout stays read-only.
|
|
36
36
|
|
|
@@ -181,6 +181,8 @@ For a visual overview, run `yui web` in another terminal. The local Web view
|
|
|
181
181
|
shows the same tasks and pending questions and lets you send messages, answer
|
|
182
182
|
questions and explicitly queue, steer or interrupt Task input. These authenticated
|
|
183
183
|
Task controls use the same operations as the CLI; Web is not a separate task system.
|
|
184
|
+
For explicit trusted-LAN binding, password protection, access URLs and Operator
|
|
185
|
+
start/status/stop, see [Controlled Web access](docs/web-access.md). HTTP is not encrypted.
|
|
184
186
|
The task list remains at the left, task details occupy the primary reading area,
|
|
185
187
|
and recorded Task messages live in a secondary discussion pane. You can resize,
|
|
186
188
|
swap or hide that pane; on narrow screens it opens over the detail. Native Sessions
|
|
@@ -311,7 +313,7 @@ the Controller are what touch the store:
|
|
|
311
313
|
it moves work and records facts — it never judges an answer
|
|
312
314
|
|
|
313
315
|
Agents work in Projects: read-only checkout + isolated worktrees.
|
|
314
|
-
Web (yui web):
|
|
316
|
+
Web (yui web): local by default + explicitly authenticated LAN access.
|
|
315
317
|
```
|
|
316
318
|
|
|
317
319
|
### Layered design
|
|
@@ -321,7 +323,7 @@ never a fixed workflow:
|
|
|
321
323
|
|
|
322
324
|
```text
|
|
323
325
|
Experience — how you interact
|
|
324
|
-
CLI (Operator) · Web (
|
|
326
|
+
CLI (Operator) · Web (local / authenticated LAN) · native Agent sessions
|
|
325
327
|
collect input · show facts · confirm actions · invoke capabilities
|
|
326
328
|
▼
|
|
327
329
|
Intelligence — who decides
|
|
@@ -275,7 +275,7 @@ const taskChildren = [
|
|
|
275
275
|
{
|
|
276
276
|
name: "project", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
277
277
|
summary: "Manage Projects bound to a Task.",
|
|
278
|
-
sections: [{ id: "manage", title: "Commands", entries: ["list", "add"] }],
|
|
278
|
+
sections: [{ id: "manage", title: "Commands", entries: ["list", "add", "delivery"] }],
|
|
279
279
|
children: [
|
|
280
280
|
{
|
|
281
281
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
@@ -287,6 +287,13 @@ const taskChildren = [
|
|
|
287
287
|
summary: "Add a Project to a Task.",
|
|
288
288
|
usage: "yui task project add <task> <project> [--base <ref>] [--directory <name>]",
|
|
289
289
|
options: ["--base", "--directory"]
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "delivery", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
293
|
+
summary: "Record a sourced Task Project delivery requirement; never grants cleanup or execution authority.",
|
|
294
|
+
usage: "yui task project delivery <task> <project-id> --requirement <required|excluded> --reason <text> --source-message <message-id> --purpose <exact-quote> --if-current <event-id|none>",
|
|
295
|
+
options: ["--requirement", "--reason", "--source-message", "--purpose", "--if-current"],
|
|
296
|
+
optionValues: { "--requirement": ["required", "excluded"] }
|
|
290
297
|
}
|
|
291
298
|
]
|
|
292
299
|
},
|
|
@@ -739,7 +746,7 @@ const taskChildren = [
|
|
|
739
746
|
},
|
|
740
747
|
{
|
|
741
748
|
name: "new", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
742
|
-
summary: "
|
|
749
|
+
summary: "Stop exact execution, then initialize a successor without replaying retained inbox obligations; preserve claims, context and workspaces.",
|
|
743
750
|
usage: "yui task role session new <task> <role> --reason <text>",
|
|
744
751
|
options: ["--reason"]
|
|
745
752
|
}
|
|
@@ -1164,11 +1171,12 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1164
1171
|
{
|
|
1165
1172
|
name: "inbox",
|
|
1166
1173
|
summary: "Read and explicitly handle the current managed Agent's own inbox.",
|
|
1167
|
-
sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack"] }],
|
|
1174
|
+
sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack", "retire"] }],
|
|
1168
1175
|
children: [
|
|
1169
1176
|
{ name: "list", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
|
|
1170
1177
|
summary: "Discover unhandled own-address entries, including accepted delivery, with current identity and separate handling receipts.",
|
|
1171
|
-
usage: "yui inbox list [--limit <1..100>] [--cursor <cursor>]",
|
|
1178
|
+
usage: "yui inbox list [--view current|history|all] [--limit <1..100>] [--cursor <cursor>]",
|
|
1179
|
+
options: ["--view", "--limit", "--cursor"], optionValues: { "--view": ["current", "history", "all"] } },
|
|
1172
1180
|
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
|
|
1173
1181
|
summary: "Read an exact original or fixed notification window without acknowledgement.",
|
|
1174
1182
|
usage: "yui inbox show <entry> [--cursor <cursor>]", options: ["--cursor"] },
|
|
@@ -1176,8 +1184,12 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1176
1184
|
summary: "Reserve 1..16 exact entries for handling in this turn; never starts a model turn.",
|
|
1177
1185
|
usage: "yui inbox claim <entry> [<entry> ...] --request-id <id>", options: ["--request-id"] },
|
|
1178
1186
|
{ name: "ack", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
|
|
1179
|
-
summary: "
|
|
1187
|
+
summary: "Record handling, including a rightful successor's disposition of an unchanged prior claim; never proves native acceptance or authorizes replay.",
|
|
1180
1188
|
usage: "yui inbox ack <entry> [<entry> ...] --request-id <id> --reason <disposition>",
|
|
1189
|
+
options: ["--request-id", "--reason"] },
|
|
1190
|
+
{ name: "retire", discovery: { surface: "managed", audiences: ["operator", "global"] },
|
|
1191
|
+
summary: "Explicitly retire obsolete proven-unsubmitted Global input, preserving its original target and evidence.",
|
|
1192
|
+
usage: "yui inbox retire <entry> [<entry> ...] --request-id <id> --reason <disposition>",
|
|
1181
1193
|
options: ["--request-id", "--reason"] }
|
|
1182
1194
|
]
|
|
1183
1195
|
},
|
|
@@ -1192,8 +1204,8 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1192
1204
|
children: [
|
|
1193
1205
|
{
|
|
1194
1206
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Discover Global Messages without consuming input.",
|
|
1195
|
-
usage: "yui role message list <role> [--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
|
|
1196
|
-
options: ["--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
|
|
1207
|
+
usage: "yui role message list <role> [--current|--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
|
|
1208
|
+
options: ["--current", "--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
|
|
1197
1209
|
},
|
|
1198
1210
|
{
|
|
1199
1211
|
name: "show", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Read one exact Global Message; follow contentPage.nextCursor for long content.",
|
|
@@ -1243,8 +1255,8 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1243
1255
|
{
|
|
1244
1256
|
name: "web", discovery: { surface: "public", audiences: ["public", "operator"] },
|
|
1245
1257
|
summary: "Serve the control room using the running Controller's capability Host.",
|
|
1246
|
-
usage: "yui web [--host <
|
|
1247
|
-
options: ["--host", "--port", "--status", "--stop"]
|
|
1258
|
+
usage: "yui web [--host <IP|localhost>] [--port <port>] [--auth-file <absolute-path>] [--detach] | --status | --stop",
|
|
1259
|
+
options: ["--host", "--port", "--auth-file", "--detach", "--status", "--stop"]
|
|
1248
1260
|
},
|
|
1249
1261
|
{
|
|
1250
1262
|
name: "controller", discovery: { surface: "public", audiences: ["public", "operator"] },
|
package/dist/cli/helpRenderer.js
CHANGED
|
@@ -25,7 +25,9 @@ function describeOption(option) {
|
|
|
25
25
|
"--agent": "Select an Agent.",
|
|
26
26
|
"--base": "Set the base Git reference.",
|
|
27
27
|
"--input": "Set dispatch input.",
|
|
28
|
-
"--host": "Set the loopback
|
|
28
|
+
"--host": "Set the listen IP (default 127.0.0.1); non-loopback requires --auth-file.",
|
|
29
|
+
"--auth-file": "Owner-only file with a unique 32–128 character password; browser username is yui. HTTP is not encrypted.",
|
|
30
|
+
"--detach": "Return after Controller starts Web; use --status / --stop. Operator and --json starts always return.",
|
|
29
31
|
"--port": "Set the local listen port.",
|
|
30
32
|
"--json": "Emit machine-readable JSON.",
|
|
31
33
|
"--project": "Select a Project.",
|
|
@@ -6,6 +6,7 @@ import { controlProviderRetry, providerRetryProjection, providerRetrySummary, re
|
|
|
6
6
|
import { pendingGlobalInboxMessages, inboxHandlingState, globalInboxDeliveryState } from "../message/inboxReceipt.js";
|
|
7
7
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
8
8
|
import { globalInputBacklog } from "../message/globalInputBacklog.js";
|
|
9
|
+
import { inboxEntryCategory } from "../message/inboxTurn.js";
|
|
9
10
|
import { checkCurrentSkills, SKILL_REUSE_RULE } from "../context/currentSkills.js";
|
|
10
11
|
import { runtimeDiagnosticGroups, runtimeDiagnosticEvidence } from "../controller/globalRuntimeAttention.js";
|
|
11
12
|
import { roleSkillIds } from "../context/roleSessionContext.js";
|
|
@@ -131,10 +132,16 @@ function roleContext(args, store, options) {
|
|
|
131
132
|
// through distinct receipts; a context read does neither.
|
|
132
133
|
const messages = store.listGlobalRoleMessages(name);
|
|
133
134
|
const queued = messages.filter((message) => message.delivery === undefined
|
|
134
|
-
&& message.inbox
|
|
135
|
+
&& message.inbox === undefined
|
|
135
136
|
&& message.notDelivered === undefined
|
|
136
137
|
&& (message.interruptThen !== undefined || message.inputControl?.action === "queue"));
|
|
137
|
-
const
|
|
138
|
+
const unhandled = pendingGlobalInboxMessages(messages);
|
|
139
|
+
const current = currentGlobalInboxMessages(messages, name, sessions);
|
|
140
|
+
const pending = recordPage(current.map(globalMessageSummary), `global:${name}/messages/current`, { limit: 8 }, {
|
|
141
|
+
historyCount: unhandled.length - current.length,
|
|
142
|
+
historyRead: "yui inbox list --view history --json",
|
|
143
|
+
note: "Historical absence of handling evidence is not new work or permission to replay effects."
|
|
144
|
+
});
|
|
138
145
|
const observedAt = new Date();
|
|
139
146
|
const { oldestAgeMs, messages: _backlogMessages, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
|
|
140
147
|
attention: name === "operator" ? store.getWorkMailbox({ kind: "operator" }) : null,
|
|
@@ -175,9 +182,9 @@ function roleContext(args, store, options) {
|
|
|
175
182
|
pending,
|
|
176
183
|
backlog,
|
|
177
184
|
reads: {
|
|
178
|
-
ownInbox: "yui inbox list --json",
|
|
185
|
+
ownInbox: "yui inbox list --view current --json",
|
|
179
186
|
messages: `role message list ${name}`,
|
|
180
|
-
pending: `role message list ${name} --
|
|
187
|
+
pending: `role message list ${name} --current`,
|
|
181
188
|
detail: `role message show ${name} <message-id>`,
|
|
182
189
|
diagnostics: `role message list ${name} --diagnostics`,
|
|
183
190
|
retry: `session retry ${name} show`
|
|
@@ -213,6 +220,16 @@ function roleContext(args, store, options) {
|
|
|
213
220
|
""
|
|
214
221
|
].join("\n") };
|
|
215
222
|
}
|
|
223
|
+
function currentGlobalInboxMessages(messages, name, sessions) {
|
|
224
|
+
const actor = { scope: "global", roleName: name, agentId: sessions?.activeAgentId ?? "",
|
|
225
|
+
nativeSessionId: sessions?.sessions[sessions.activeAgentId]?.nativeSessionId ?? "" };
|
|
226
|
+
const binding = sessions?.providerBinding;
|
|
227
|
+
const turn = binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId === actor.nativeSessionId
|
|
228
|
+
? binding?.run : undefined;
|
|
229
|
+
const category = (message) => inboxEntryCategory(message, actor, turn);
|
|
230
|
+
return pendingGlobalInboxMessages(messages).filter(message => category(message) !== "history")
|
|
231
|
+
.sort((a, b) => Number(category(b) === "current") - Number(category(a) === "current"));
|
|
232
|
+
}
|
|
216
233
|
function globalMessageSummary(message) {
|
|
217
234
|
return {
|
|
218
235
|
id: message.id, createdAt: message.createdAt, kind: message.kind,
|
|
@@ -626,17 +643,18 @@ function globalRoleMessage(args, store, options) {
|
|
|
626
643
|
}
|
|
627
644
|
requireRole(name, store);
|
|
628
645
|
const pending = tail.includes("--pending");
|
|
646
|
+
const current = tail.includes("--current");
|
|
629
647
|
const diagnostics = tail.includes("--diagnostics");
|
|
630
|
-
const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--diagnostics"), action === "list"
|
|
648
|
+
const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--current" && arg !== "--diagnostics"), action === "list"
|
|
631
649
|
? ["--cursor", "--limit", "--diagnostic"] : ["--cursor"]);
|
|
632
650
|
const diagnostic = parsed.values.get("--diagnostic");
|
|
633
|
-
if (Number(pending) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
|
|
651
|
+
if (Number(pending) + Number(current) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
|
|
634
652
|
|| action === "show" && diagnostics)
|
|
635
653
|
throw usageError("Choose one Message discovery view.");
|
|
636
654
|
const messages = store.listGlobalRoleMessages(name);
|
|
637
655
|
let data;
|
|
638
656
|
if (action === "show") {
|
|
639
|
-
if (parsed.positionals.length !== 1 || pending)
|
|
657
|
+
if (parsed.positionals.length !== 1 || pending || current)
|
|
640
658
|
throw usageError("Role message show <role> <message-id> [--cursor <cursor>].");
|
|
641
659
|
const message = messages.find(m => m.id === parsed.positionals[0]);
|
|
642
660
|
if (message === undefined)
|
|
@@ -654,8 +672,9 @@ function globalRoleMessage(args, store, options) {
|
|
|
654
672
|
return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
|
|
655
673
|
}
|
|
656
674
|
const selected = diagnostic !== undefined ? runtimeDiagnosticEvidence(messages, diagnostic)
|
|
657
|
-
:
|
|
658
|
-
|
|
675
|
+
: current ? currentGlobalInboxMessages(messages, name, store.getGlobalRoleSessionSet(name))
|
|
676
|
+
: pending ? pendingGlobalInboxMessages(messages) : [...messages].reverse();
|
|
677
|
+
data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${diagnostic === undefined ? current ? "current" : pending ? "pending" : "all" : `diagnostic/${diagnostic}`}`, parsed);
|
|
659
678
|
}
|
|
660
679
|
return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
|
|
661
680
|
}
|
|
@@ -711,6 +730,8 @@ function messageGlobalInput(name, input, store, options) {
|
|
|
711
730
|
}
|
|
712
731
|
store.updateGlobalRoleMessage({ ...persisted.message, control: {
|
|
713
732
|
requestId, receiptId: `steer:${name}/${persisted.message.id}`,
|
|
733
|
+
target: { agentId: resolution.target.agentId, nativeSessionId: resolution.target.nativeSessionId,
|
|
734
|
+
attemptId: resolution.target.attemptId },
|
|
714
735
|
outcome: "pending", observedAt: now.toISOString()
|
|
715
736
|
} });
|
|
716
737
|
return {
|
|
@@ -5,9 +5,10 @@ import { createTaskWake, latestTaskWake } from "../scheduler/taskWake.js";
|
|
|
5
5
|
import { expandTaskMessageResult } from "../message/message.js";
|
|
6
6
|
import { messageAssignmentBlocker } from "../message/messageContinuation.js";
|
|
7
7
|
import { globalInboxDeliveryState, inboxHandlingState, isGlobalInboxMessage, pendingGlobalInboxMessages } from "../message/inboxReceipt.js";
|
|
8
|
-
import {
|
|
8
|
+
import { nonReplayableMessageWake, isLeaderSteer, pinMessageRevisions, releasedNotificationWake, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
9
9
|
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
10
10
|
import { isTaskRecordRetired, retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
11
|
+
import { blocksInboxOrder, inboxEntryCategory, inboxSettled } from "../message/inboxTurn.js";
|
|
11
12
|
const target = (address) => ({ kind: "role", taskId: address.taskId, roleName: address.roleName });
|
|
12
13
|
const source = (address) => `inbox:${address.scope}:${address.taskId ?? ""}:${address.roleName}`;
|
|
13
14
|
const pendingId = (pending) => `pending-${readDigest(pending)}`;
|
|
@@ -15,15 +16,27 @@ const sameSession = (a, b) => a.scope === b.scope && a.taskId === b.taskId
|
|
|
15
16
|
&& a.roleName === b.roleName && a.agentId === b.agentId && a.nativeSessionId === b.nativeSessionId;
|
|
16
17
|
function addressOf(store, env) {
|
|
17
18
|
const caller = requireManagedCaller(store, env);
|
|
19
|
+
const binding = caller.scope === "global" ? store.getGlobalRoleSessionSet(caller.roleName)?.providerBinding
|
|
20
|
+
: store.getTaskRoleSessionSet(caller.taskId, caller.roleName)?.providerBinding;
|
|
21
|
+
const turn = binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId === caller.nativeSessionId
|
|
22
|
+
? binding?.run : undefined;
|
|
23
|
+
const attempt = turn && ["submitting", "accepted", "delivery-unknown"].includes(turn.status)
|
|
24
|
+
? { attemptId: turn.attemptId } : {};
|
|
18
25
|
if (caller.scope === "global") {
|
|
19
|
-
return { scope: "global", roleName: caller.roleName, agentId: caller.agentId, nativeSessionId: caller.nativeSessionId };
|
|
26
|
+
return { scope: "global", roleName: caller.roleName, agentId: caller.agentId, nativeSessionId: caller.nativeSessionId, ...attempt };
|
|
20
27
|
}
|
|
21
28
|
if (caller.roleName !== "leader" && caller.currentRunId === undefined) {
|
|
22
29
|
throw new Error("An Assignment inbox requires its current active AgentRun.");
|
|
23
30
|
}
|
|
24
31
|
return { scope: "task", taskId: caller.taskId, roleName: caller.roleName,
|
|
25
32
|
agentId: caller.agentId, nativeSessionId: caller.nativeSessionId,
|
|
26
|
-
...(caller.currentRunId === undefined ? {} : { runId: caller.currentRunId }) };
|
|
33
|
+
...(caller.currentRunId === undefined ? {} : { runId: caller.currentRunId }), ...attempt };
|
|
34
|
+
}
|
|
35
|
+
function currentTurn(store, address) {
|
|
36
|
+
const binding = address.scope === "global" ? store.getGlobalRoleSessionSet(address.roleName)?.providerBinding
|
|
37
|
+
: store.getTaskRoleSessionSet(address.taskId, address.roleName)?.providerBinding;
|
|
38
|
+
return binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId === address.nativeSessionId
|
|
39
|
+
? binding?.run : undefined;
|
|
27
40
|
}
|
|
28
41
|
function ownsMessage(store, address, message) {
|
|
29
42
|
if (address.roleName === "leader" && message.taskId === address.taskId && isLeaderSteer(message))
|
|
@@ -80,15 +93,15 @@ function blocker(store, address, entry) {
|
|
|
80
93
|
if (!isWake(entry) && address.scope === "task"
|
|
81
94
|
&& isTaskRecordRetired(store.listEvents(address.taskId), "message", entry.id))
|
|
82
95
|
return "message-retired";
|
|
83
|
-
if (entry.inbox
|
|
84
|
-
return "acknowledged";
|
|
96
|
+
if (inboxSettled(entry.inbox))
|
|
97
|
+
return entry.inbox?.retirement ? "retired" : "acknowledged";
|
|
85
98
|
if (entry.inbox !== undefined) {
|
|
86
99
|
return sameSession(entry.inbox.claim.actor, address) ? undefined : "claimed-by-previous-session";
|
|
87
100
|
}
|
|
88
101
|
if (isWake(entry)) {
|
|
89
102
|
// Native acceptance releases transport ownership, not semantic handling.
|
|
90
103
|
// The current Leader may explicitly claim the fixed delivered window.
|
|
91
|
-
if (entry.status === "consumed")
|
|
104
|
+
if (entry.status === "consumed" || releasedNotificationWake(store, entry.taskId, entry.id))
|
|
92
105
|
return undefined;
|
|
93
106
|
return "automatic-notification-owns-window";
|
|
94
107
|
}
|
|
@@ -150,9 +163,12 @@ function checkMutation(store, address) {
|
|
|
150
163
|
}
|
|
151
164
|
function summary(store, address, entry) {
|
|
152
165
|
return { id: entry.id, kind: isWake(entry) ? "notification-window" : "message",
|
|
166
|
+
category: inboxEntryCategory(entry, address, currentTurn(store, address)),
|
|
153
167
|
createdAt: entry.createdAt, summary: summaryText(isWake(entry) ? entry.reasons.join(", ") : entry.body),
|
|
154
168
|
blocked: blocker(store, address, entry) ?? null, inbox: summaryMetadata(entry.inbox ?? null),
|
|
155
169
|
handling: inboxHandlingState(entry.inbox),
|
|
170
|
+
...(entry.inbox !== undefined && entry.inbox.ack === undefined && !sameSession(entry.inbox.claim.actor, address)
|
|
171
|
+
? { recovery: "Read this original and prior effects under current authority; finish handling, then inbox ack this exact entry. Retain the original claim; do not claim again or replay business." } : {}),
|
|
156
172
|
delivery: isWake(entry) ? entry.status : summaryMetadata({
|
|
157
173
|
control: entry.control ?? null,
|
|
158
174
|
...(address.scope === "global" ? { receipt: entry.delivery ?? null,
|
|
@@ -173,23 +189,33 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
173
189
|
const [action, ...tail] = args;
|
|
174
190
|
const address = addressOf(store, env);
|
|
175
191
|
if (action === "list") {
|
|
176
|
-
const parsed = readOptions(tail);
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
const parsed = readOptions(tail, ["--limit", "--cursor", "--view"]);
|
|
193
|
+
const view = parsed.values.get("--view") ?? "all";
|
|
194
|
+
if (!["all", "current", "history"].includes(view) || parsed.positionals.length) {
|
|
195
|
+
throw new Error("Inbox list accepts --view current|history|all, --limit and --cursor.");
|
|
196
|
+
}
|
|
179
197
|
const retired = address.scope === "task" ? retiredTaskRecordIds(store.listEvents(address.taskId), "message") : new Set();
|
|
180
198
|
const selected = address.scope === "global" ? pendingGlobalInboxMessages(store.listGlobalRoleMessages(address.roleName))
|
|
181
199
|
: entries(store, address).filter(entry => (isWake(entry) || !retired.has(entry.id))
|
|
182
|
-
&&
|
|
183
|
-
const
|
|
200
|
+
&& !inboxSettled(entry.inbox));
|
|
201
|
+
const turn = currentTurn(store, address);
|
|
202
|
+
const categories = selected.map(entry => ({ entry, category: inboxEntryCategory(entry, address, turn) }));
|
|
203
|
+
const rank = { current: 0, available: 1, history: 2 };
|
|
204
|
+
categories.sort((a, b) => rank[a.category] - rank[b.category]);
|
|
205
|
+
const items = categories.filter(item => view === "all"
|
|
206
|
+
|| (view === "history") === (item.category === "history")).map(({ entry }) => summary(store, address, entry));
|
|
184
207
|
if (address.scope === "task" && address.roleName === "leader") {
|
|
185
208
|
const mailbox = store.getWorkMailbox(target(address));
|
|
186
|
-
if (mailbox?.pending != null)
|
|
187
|
-
items.
|
|
209
|
+
if (mailbox?.pending != null && view !== "history")
|
|
210
|
+
items.splice(categories.filter(item => item.category === "current").length, 0, { id: pendingId(mailbox.pending), kind: "notification-window",
|
|
211
|
+
category: "available",
|
|
188
212
|
summary: summaryText(mailbox.pending.reasons.join(", ")),
|
|
189
213
|
blocked: mailbox.processing === null && !unfinishedLeaderInbox(store, address.taskId) ? null : "earlier-window-owned",
|
|
190
214
|
read: `yui inbox show ${pendingId(mailbox.pending)}` });
|
|
191
215
|
}
|
|
192
|
-
return recordPage(items, source(address)
|
|
216
|
+
return recordPage(items, `${source(address)}/${view}`, parsed, { identity: address, view,
|
|
217
|
+
historyCount: categories.filter(item => item.category === "history").length,
|
|
218
|
+
note: "History lacks handling evidence; it is not unexecuted work. Inspect original effects before disposition." });
|
|
193
219
|
}
|
|
194
220
|
if (action === "show") {
|
|
195
221
|
const parsed = readOptions(tail, ["--cursor"]);
|
|
@@ -208,8 +234,8 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
208
234
|
? expandTaskMessageResult(entry, (task, run) => store.getRun(task, run)) : entry;
|
|
209
235
|
return boundedDocument(value, `${source(address)}/${id}`, parsed.cursor);
|
|
210
236
|
}
|
|
211
|
-
if (action !== "claim" && action !== "ack")
|
|
212
|
-
throw new Error("Inbox commands: list, show, claim, ack.");
|
|
237
|
+
if (action !== "claim" && action !== "ack" && action !== "retire")
|
|
238
|
+
throw new Error("Inbox commands: list, show, claim, ack, retire.");
|
|
213
239
|
const parsed = readOptions(tail, ["--request-id", "--reason"]);
|
|
214
240
|
const ids = parsed.positionals;
|
|
215
241
|
const requestId = parsed.values.get("--request-id");
|
|
@@ -220,7 +246,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
220
246
|
if (!ids.length || ids.length > 16 || new Set(ids).size !== ids.length) {
|
|
221
247
|
throw new Error("Select 1..16 distinct exact inbox entries; no wildcard acknowledgement.");
|
|
222
248
|
}
|
|
223
|
-
if (action
|
|
249
|
+
if (action !== "claim" && (!reason?.trim() || Buffer.byteLength(reason) > 2048)) {
|
|
224
250
|
throw new Error("Acknowledgement requires --reason describing handling or why no action remains (up to 2048 bytes).");
|
|
225
251
|
}
|
|
226
252
|
if (action === "claim" && reason !== undefined)
|
|
@@ -251,7 +277,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
251
277
|
continue;
|
|
252
278
|
const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
|
|
253
279
|
if (message !== undefined && (taskMessageInboxReceipt(tx, message) !== undefined
|
|
254
|
-
||
|
|
280
|
+
|| nonReplayableMessageWake(tx, message) !== undefined)
|
|
255
281
|
|| message?.interruptThen !== undefined
|
|
256
282
|
|| message?.control !== undefined && message.control.outcome !== "rejected"
|
|
257
283
|
|| message?.inputControl?.expectedSessionId !== undefined
|
|
@@ -274,7 +300,36 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
274
300
|
}
|
|
275
301
|
else {
|
|
276
302
|
entry = getEntry(tx, current, id);
|
|
277
|
-
if (action === "
|
|
303
|
+
if (action === "retire") {
|
|
304
|
+
if (current.scope !== "global" || isWake(entry)) {
|
|
305
|
+
throw new Error("Task input retirement uses the Leader's existing task message retire operation.");
|
|
306
|
+
}
|
|
307
|
+
const message = entry;
|
|
308
|
+
if (message.inbox?.retirement) {
|
|
309
|
+
const saved = message.inbox.retirement;
|
|
310
|
+
if (saved.requestId !== requestId || saved.reason !== reason || !sameSession(saved.actor, current)) {
|
|
311
|
+
throw new Error("Conflicting retirement; original disposition retained.");
|
|
312
|
+
}
|
|
313
|
+
return receipt(entry);
|
|
314
|
+
}
|
|
315
|
+
// A retained attempt in an older conversation is still evidence of
|
|
316
|
+
// submission; changing the active Session cannot erase that boundary.
|
|
317
|
+
const turn = tx.getGlobalRoleSessionSet(current.roleName)?.providerBinding?.run;
|
|
318
|
+
const foreignTarget = message.deliveryTarget && (message.deliveryTarget.agentId !== current.agentId
|
|
319
|
+
|| message.deliveryTarget.nativeSessionId !== current.nativeSessionId)
|
|
320
|
+
|| message.inputControl?.expectedSessionId !== undefined
|
|
321
|
+
&& message.inputControl.expectedSessionId !== current.nativeSessionId;
|
|
322
|
+
if (message.inbox?.ack || message.delivery !== undefined || message.interruptThen !== undefined
|
|
323
|
+
|| message.control !== undefined && message.control.outcome !== "rejected"
|
|
324
|
+
|| turn?.attemptId === `global-input:${current.roleName}/${message.id}`
|
|
325
|
+
|| !foreignTarget && message.notDelivered === undefined) {
|
|
326
|
+
throw new Error("Retirement requires obsolete, proven-unsubmitted input; unknown/accepted/interrupt-owned input must retain its exact handling path.");
|
|
327
|
+
}
|
|
328
|
+
entry = { ...message, inbox: { ...message.inbox,
|
|
329
|
+
claim: message.inbox?.claim ?? { requestId, actor: current, at: now.toISOString() },
|
|
330
|
+
retirement: { requestId, actor: current, at: now.toISOString(), reason: reason } } };
|
|
331
|
+
}
|
|
332
|
+
else if (action === "claim") {
|
|
278
333
|
if (entry.inbox !== undefined) {
|
|
279
334
|
if (!sameSession(entry.inbox.claim.actor, current))
|
|
280
335
|
throw new Error("Claim belongs to a previous Session; read the original and prior effects, then explicitly acknowledge only after finishing its handling.");
|
|
@@ -285,7 +340,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
285
340
|
const blocked = blocker(tx, current, entry);
|
|
286
341
|
if (blocked !== undefined)
|
|
287
342
|
throw new Error(`Inbox entry cannot be claimed: ${blocked}.`);
|
|
288
|
-
if (isWake(entry) && !acceptedInput(entry)) {
|
|
343
|
+
if (isWake(entry) && !acceptedInput(entry) && !releasedNotificationWake(tx, entry.taskId, entry.id)) {
|
|
289
344
|
for (const ref of entry.refs ?? []) {
|
|
290
345
|
if (ref.type !== "message")
|
|
291
346
|
continue;
|
|
@@ -299,7 +354,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
299
354
|
for (const previous of entries(tx, current)) {
|
|
300
355
|
if (previous.id === entry.id)
|
|
301
356
|
break;
|
|
302
|
-
if (
|
|
357
|
+
if (!blocksInboxOrder(previous, current, currentTurn(tx, current)))
|
|
303
358
|
continue;
|
|
304
359
|
if (current.scope === "task"
|
|
305
360
|
&& isTaskRecordRetired(tx.listEvents(current.taskId), "message", previous.id))
|
|
@@ -315,11 +370,13 @@ export function runInboxCommand(args, store, env, now = new Date()) {
|
|
|
315
370
|
else {
|
|
316
371
|
if (!entry.inbox)
|
|
317
372
|
throw new Error("Claim the exact entry before handling and acknowledgement.");
|
|
373
|
+
if (entry.inbox.retirement)
|
|
374
|
+
throw new Error("Retirement is not acknowledgement; original disposition retained.");
|
|
318
375
|
// Ack settles already-owned handling; it never submits or retargets
|
|
319
376
|
// the original. The current rightful successor may settle a claim
|
|
320
377
|
// acquired by the exact selected Session, retaining both identities.
|
|
321
378
|
const claimant = entry.inbox.claim.actor;
|
|
322
|
-
if (isWake(entry) && !acceptedInput(entry)) {
|
|
379
|
+
if (isWake(entry) && !acceptedInput(entry) && !releasedNotificationWake(tx, entry.taskId, entry.id)) {
|
|
323
380
|
const messageIds = new Set(entry.refs?.filter(ref => ref.type === "message").map(ref => ref.id));
|
|
324
381
|
for (const message of tx.listMessages(current.taskId)) {
|
|
325
382
|
if (messageIds.has(message.id))
|
|
@@ -83,6 +83,7 @@ import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, lead
|
|
|
83
83
|
import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
|
|
84
84
|
import { runTaskContextCommand, taskRecordList } from "./taskContextCommand.js";
|
|
85
85
|
import { taskBriefCommand, taskDecisionCommand, taskEventCommand, taskMilestoneCommand } from "./taskFactCommands.js";
|
|
86
|
+
import { taskProjectDeliveryCommand } from "./taskProjectDeliveryCommand.js";
|
|
86
87
|
import { taskUsageCommand } from "./taskUsageCommand.js";
|
|
87
88
|
import { openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
|
|
88
89
|
import { runTaskNextActionCommand } from "./taskNextActionCommand.js";
|
|
@@ -456,6 +457,8 @@ function assertCandidateReviewRoleIsolation(producerRoles, reviewerRoleName, lan
|
|
|
456
457
|
}
|
|
457
458
|
function taskProjectCommand(args, store, options) {
|
|
458
459
|
const [command, ...rest] = args;
|
|
460
|
+
if (command === "delivery")
|
|
461
|
+
return taskProjectDeliveryCommand(rest, store, options);
|
|
459
462
|
if (command === "list") {
|
|
460
463
|
exactPositionals(rest, 1, "Task project list usage: yui task project list <task>.");
|
|
461
464
|
const task = requireTask(store, rest[0]);
|
|
@@ -1880,7 +1883,9 @@ function steerTaskInput(taskId, input, store, options) {
|
|
|
1880
1883
|
if (saved === undefined || requestId === undefined || taskMessageInboxReceipt(tx, saved) !== undefined)
|
|
1881
1884
|
return false;
|
|
1882
1885
|
tx.updateMessage(result.task.id, recordTaskMessageControlOutcome(saved, {
|
|
1883
|
-
requestId, receiptId, outcome: "pending", observedAt: clock(options)
|
|
1886
|
+
requestId, receiptId, outcome: "pending", observedAt: clock(options),
|
|
1887
|
+
target: { agentId: resolution.target.agentId, nativeSessionId: resolution.target.nativeSessionId,
|
|
1888
|
+
attemptId: resolution.target.attemptId }
|
|
1884
1889
|
}));
|
|
1885
1890
|
return true;
|
|
1886
1891
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readTaskAuthorizationMessage, taskAuthorizationSource } from "../grant/taskAuthorization.js";
|
|
2
|
+
import { assertTaskDeliveryAuthority } from "../task/taskAuthority.js";
|
|
3
|
+
import { PROJECT_DELIVERY_DECIDED_EVENT, projectDeliveryDecision } from "../task/projectDeliveryScope.js";
|
|
4
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
5
|
+
import { usageError } from "../errors/cliError.js";
|
|
6
|
+
import { clock, exactPositionals, leaderMailbox, notifyMailbox, output, parseTail, recordTaskEvent, requiredOption, requireTask, taskMailbox, taskRef } from "./taskCommandSupport.js";
|
|
7
|
+
export function taskProjectDeliveryCommand(args, store, options) {
|
|
8
|
+
const usage = "yui task project delivery <task> <project-id> --requirement <required|excluded> "
|
|
9
|
+
+ "--reason <text> --source-message <message-id> --purpose <exact-quote> --if-current <event-id|none>";
|
|
10
|
+
const parsed = parseTail(args, new Set([
|
|
11
|
+
"--requirement", "--reason", "--source-message", "--purpose", "--if-current"
|
|
12
|
+
]), usage);
|
|
13
|
+
exactPositionals(parsed.positionals, 2, usage);
|
|
14
|
+
const requirement = requiredOption(parsed.options, "--requirement");
|
|
15
|
+
if (requirement !== "required" && requirement !== "excluded")
|
|
16
|
+
throw usageError(usage);
|
|
17
|
+
const reason = requiredOption(parsed.options, "--reason");
|
|
18
|
+
const sourceMessage = requiredOption(parsed.options, "--source-message");
|
|
19
|
+
const purpose = requiredOption(parsed.options, "--purpose");
|
|
20
|
+
const guard = requiredOption(parsed.options, "--if-current");
|
|
21
|
+
const now = clock(options);
|
|
22
|
+
const result = store.transaction(tx => {
|
|
23
|
+
const task = requireTask(tx, parsed.positionals[0]);
|
|
24
|
+
const actor = assertTaskDeliveryAuthority(tx, options.environment, task.id);
|
|
25
|
+
if (!["active", "completed"].includes(task.status) || task.executionGate.state !== "enabled") {
|
|
26
|
+
throw usageError("Delivery scope requires an active or completed, unarchived Task with execution enabled; it does not reopen or resume work.");
|
|
27
|
+
}
|
|
28
|
+
const projectId = parsed.positionals[1];
|
|
29
|
+
if (!task.projectBindings.some(binding => binding.projectId === projectId)) {
|
|
30
|
+
throw usageError(`Project is not bound to this Task: ${projectId}.`);
|
|
31
|
+
}
|
|
32
|
+
const source = actor === "leader"
|
|
33
|
+
? taskAuthorizationSource(tx, task.id, options.environment ?? {}, sourceMessage, purpose)
|
|
34
|
+
: readTaskAuthorizationMessage(tx, task.id, sourceMessage, purpose);
|
|
35
|
+
const previous = projectDeliveryDecision(tx.listEventsByType(task.id, [PROJECT_DELIVERY_DECIDED_EVENT]), task.id, projectId);
|
|
36
|
+
if (guard !== (previous?.id ?? "none")) {
|
|
37
|
+
throw usageError("Project delivery scope changed; read task remote-delivery and reconcile before retrying with its scopeDecision.id (or none).");
|
|
38
|
+
}
|
|
39
|
+
const event = recordTaskEvent(tx, task.id, PROJECT_DELIVERY_DECIDED_EVENT, {
|
|
40
|
+
projectId, requirement, reason, by: actor,
|
|
41
|
+
sourceMessageId: source.messageId, sourceDigest: source.digest, purpose: source.purpose,
|
|
42
|
+
...("nativeSessionId" in source && typeof source.nativeSessionId === "string"
|
|
43
|
+
? { nativeSessionId: source.nativeSessionId } : {}),
|
|
44
|
+
...(previous === null ? {} : { supersedes: previous.id })
|
|
45
|
+
}, now);
|
|
46
|
+
enqueueWork(tx, taskMailbox(task.id), "project-delivery-decided", now, [taskRef(task.id)]);
|
|
47
|
+
if (task.status === "active" && actor !== "leader") {
|
|
48
|
+
enqueueWork(tx, leaderMailbox(task.id), "project-delivery-decided", now, [taskRef(task.id)]);
|
|
49
|
+
}
|
|
50
|
+
return { task, event };
|
|
51
|
+
});
|
|
52
|
+
notifyMailbox(options.runtime, taskMailbox(result.task.id));
|
|
53
|
+
if (result.task.status === "active")
|
|
54
|
+
notifyMailbox(options.runtime, leaderMailbox(result.task.id));
|
|
55
|
+
return output(`Recorded Project delivery requirement (${requirement}); merge facts and cleanup authority are unchanged.\n`, result.event);
|
|
56
|
+
}
|
|
@@ -27,15 +27,17 @@ export function renderTaskRemoteDelivery(delivery, indent = "") {
|
|
|
27
27
|
const lines = [
|
|
28
28
|
`${indent}Remote delivery: ${delivery.status}`,
|
|
29
29
|
`${indent}Expected heads: ${source}`,
|
|
30
|
-
`${indent}
|
|
31
|
-
`${indent}
|
|
32
|
-
`${indent}
|
|
30
|
+
`${indent}Required code Projects all merged: ${delivery.allMerged ? "yes" : "no"}`,
|
|
31
|
+
`${indent}Required code Projects all verified: ${delivery.allVerified ? "yes" : "no"}`,
|
|
32
|
+
`${indent}Required code Projects: ${delivery.mergedProjectCount}/${delivery.requiredCodeProjectCount} merged; ${delivery.verifiedProjectCount}/${delivery.requiredCodeProjectCount} verified; excluded=${delivery.excludedProjectCount}`,
|
|
33
|
+
...(delivery.requiredCodeProjectCount === 0 ? [`${indent}No delivery obligation.`] : []),
|
|
33
34
|
`${indent}Archive --integrated coverage: ${delivery.integratedCoverageSatisfied ? "satisfied" : "blocked"}`,
|
|
34
35
|
...(delivery.archiveDisposition === null
|
|
35
36
|
? []
|
|
36
37
|
: [`${indent}Archive disposition: ${delivery.archiveDisposition}`]),
|
|
37
38
|
`${indent}Projects:${delivery.projects.length === 0 ? " none" : ""}`,
|
|
38
39
|
...delivery.projects.map((project) => (`${indent}- ${project.directory} (${project.projectId}): `
|
|
40
|
+
+ `requirement=${project.deliveryRequirement}; scopeDecision=${project.scopeDecision?.id ?? "none"}; `
|
|
39
41
|
+ `expected=${shortCommit(project.expectedLocalCommit)}; `
|
|
40
42
|
+ `candidate=${shortCommit(project.deliveryLocalCommit)}; `
|
|
41
43
|
+ `adoption=${project.adoption?.id ?? "none"}; `
|
|
@@ -385,6 +385,7 @@ export function runContextEntry(pack) {
|
|
|
385
385
|
activeRuns: liveTaskState.activeRuns.length, activeTaskReviews: liveTaskState.activeTaskReviews.length }
|
|
386
386
|
},
|
|
387
387
|
reads: {
|
|
388
|
+
inbox: "yui inbox list --view current --json",
|
|
388
389
|
refs: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view refs`,
|
|
389
390
|
full: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view full`,
|
|
390
391
|
input: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view input`,
|
|
@@ -90,6 +90,7 @@ export function serializeRunInputEnvelope(value) {
|
|
|
90
90
|
]),
|
|
91
91
|
"Use the Session Manifest's CLI entry. Fail closed if the exact Context is unavailable or mismatched.",
|
|
92
92
|
"Then read the relevant referenced requirements and perform the assigned work. Context loading is not the deliverable.",
|
|
93
|
+
"Current continuation/steer inputs: yui inbox list --view current --json. Read and claim exact originals, then ack their disposition before the final report; this does not accept the assignment.",
|
|
93
94
|
"During planning, fulfill the user's Draft discussion request without starting delivery before it is requested."
|
|
94
95
|
];
|
|
95
96
|
const serialized = lines.join("\n");
|
|
@@ -93,7 +93,7 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
93
93
|
commandDiscovery: {
|
|
94
94
|
help: "yui help", describe: "yui --json help",
|
|
95
95
|
skillCheck: "yui session skills --json",
|
|
96
|
-
ownInbox: "yui inbox list --json",
|
|
96
|
+
ownInbox: "yui inbox list --view current --json",
|
|
97
97
|
note: "Help and completion share the current caller's offline command view. Follow that view for operation-specific discovery; execution always revalidates authority."
|
|
98
98
|
},
|
|
99
99
|
detail: `task context inspect ${taskId} --store <store> --ref <refId> --digest <digest>`,
|
|
@@ -61,7 +61,7 @@ export function activationWakeRefs(store, task) {
|
|
|
61
61
|
}
|
|
62
62
|
const BRIEF_RELEVANT_EVENTS = [
|
|
63
63
|
"message.sent", "message.updated", "message.native-user-input", "brief.updated",
|
|
64
|
-
"decision.recorded", "decision.superseded", "input.requested", "input.answered", "input.cancelled", "input.auto-answered",
|
|
64
|
+
"decision.recorded", "decision.superseded", "task.project-delivery-decided", "input.requested", "input.answered", "input.cancelled", "input.auto-answered",
|
|
65
65
|
"run.completed", "run.failed", "run.cancelled", "job.state-changed", "work.accepted",
|
|
66
66
|
"runtime.role-delivery-failed", "run.retired",
|
|
67
67
|
"work.updated", "work.edited", "work.scope-updated", "work.rejected", "work.retired",
|