@zq-silk/yui 2.6.0 → 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/README.md +23 -0
- package/dist/cli/commandCatalog.js +34 -7
- package/dist/cli/updateCommand.js +3 -0
- package/dist/commands/configCommands.js +1 -0
- package/dist/commands/globalRoleCommands.js +62 -35
- package/dist/commands/inboxCommands.js +372 -0
- package/dist/commands/roleSkillValidation.js +2 -0
- package/dist/commands/taskCommands.js +39 -7
- package/dist/commands/taskRoleRuntimeStatus.js +2 -0
- package/dist/context/currentSkills.js +48 -0
- package/dist/context/roleSessionContext.js +13 -16
- package/dist/context/runContextPack.js +50 -3
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +27 -4
- package/dist/context/skillCatalog.js +2 -3
- package/dist/context/taskContext.js +38 -12
- package/dist/controlPlaneCli.js +58 -7
- package/dist/controller/controller.js +8 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +26 -6
- package/dist/controller/globalInputDelivery.js +86 -16
- package/dist/controller/globalRuntimeAttention.js +136 -6
- package/dist/controller/operatorNotification.js +6 -1
- package/dist/controller/providerRetryDelivery.js +3 -5
- package/dist/controller/runtime.js +6 -2
- package/dist/controller/structuredProviderObservation.js +51 -15
- package/dist/controller/taskHostTerminalRecovery.js +33 -0
- package/dist/doctor/doctor.js +25 -2
- package/dist/executor/effectiveLaunch.js +2 -2
- package/dist/executor/fileRoleLaunchPlanner.js +8 -15
- package/dist/interaction/notificationPresentation.js +2 -2
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/globalInputBacklog.js +69 -0
- package/dist/message/globalMessageQuery.js +1 -0
- package/dist/message/inboxReceipt.js +62 -0
- package/dist/message/message.js +6 -1
- package/dist/message/messageContinuation.js +34 -13
- package/dist/message/taskInboxOwnership.js +43 -0
- 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/nativeAgent/session/backends.js +1 -1
- package/dist/observability/runtimeIdentity.js +1 -1
- package/dist/output/boundedRead.js +43 -14
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/runtime/agentEndpointIdentity.js +8 -4
- package/dist/runtime/agentEndpointOwnership.js +5 -6
- package/dist/runtime/agentHost.js +165 -24
- 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/scheduler/operatorEvent.js +49 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +5 -4
- package/dist/scheduler/taskWake.js +5 -0
- package/dist/sqlite/driver.js +108 -0
- package/dist/storage/contextRecords.js +28 -0
- package/dist/storage/sqliteSchema.js +21 -1
- package/dist/storage/sqliteStore.js +64 -1
- package/dist/storage/storageSchema.js +1 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +4 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +1 -1
- package/dist/task/completionReadiness.js +13 -6
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- 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 +127 -5
- package/docs/managed-turn-and-session-runtime.zh-CN.md +83 -3
- package/docs/release-workflow.md +105 -9
- package/docs/release-workflow.zh-CN.md +73 -5
- package/docs/storage-baseline.md +22 -7
- package/docs/storage-baseline.zh-CN.md +14 -5
- package/docs/task-delivery.md +17 -8
- package/docs/task-delivery.zh-CN.md +11 -5
- package/docs/testing/review-evaluation.md +148 -0
- package/docs/testing/review-evaluation.zh-CN.md +117 -0
- package/docs/testing/verification-levels.md +26 -4
- package/docs/testing/verification-levels.zh-CN.md +11 -1
- package/i18n/README.zh-CN.md +19 -0
- package/package.json +5 -2
- package/skills/yui-leader/SKILL.md +28 -11
- package/skills/yui-leader/VERSION +1 -0
- 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 +15 -3
- package/skills/yui-operator/VERSION +1 -0
- package/skills/yui-operator/references/task-delivery.md +2 -0
- package/skills/yui-reviewer/SKILL.md +43 -1
- package/skills/yui-reviewer/VERSION +1 -0
- package/skills/yui-runtime/SKILL.md +144 -15
- package/skills/yui-runtime/VERSION +1 -0
- package/skills/yui-runtime/references/publication.md +16 -5
- package/skills/yui-runtime/references/recovery.md +24 -5
- package/skills/yui-worker/SKILL.md +8 -1
- package/skills/yui-worker/VERSION +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { requireManagedCaller } from "../runtime/sessionCaller.js";
|
|
2
|
+
import { boundedDocument, readDigest, readOptions, recordPage, summaryMetadata, summaryText } from "../output/boundedRead.js";
|
|
3
|
+
import { claimPending, completeProcessing } from "../coordination/workMailbox.js";
|
|
4
|
+
import { createTaskWake, latestTaskWake } from "../scheduler/taskWake.js";
|
|
5
|
+
import { expandTaskMessageResult } from "../message/message.js";
|
|
6
|
+
import { messageAssignmentBlocker } from "../message/messageContinuation.js";
|
|
7
|
+
import { globalInboxDeliveryState, inboxHandlingState, isGlobalInboxMessage, pendingGlobalInboxMessages } from "../message/inboxReceipt.js";
|
|
8
|
+
import { deliveredMessageWake, isLeaderSteer, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
9
|
+
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
10
|
+
import { isTaskRecordRetired, retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
11
|
+
const target = (address) => ({ kind: "role", taskId: address.taskId, roleName: address.roleName });
|
|
12
|
+
const source = (address) => `inbox:${address.scope}:${address.taskId ?? ""}:${address.roleName}`;
|
|
13
|
+
const pendingId = (pending) => `pending-${readDigest(pending)}`;
|
|
14
|
+
const sameSession = (a, b) => a.scope === b.scope && a.taskId === b.taskId
|
|
15
|
+
&& a.roleName === b.roleName && a.agentId === b.agentId && a.nativeSessionId === b.nativeSessionId;
|
|
16
|
+
function addressOf(store, env) {
|
|
17
|
+
const caller = requireManagedCaller(store, env);
|
|
18
|
+
if (caller.scope === "global") {
|
|
19
|
+
return { scope: "global", roleName: caller.roleName, agentId: caller.agentId, nativeSessionId: caller.nativeSessionId };
|
|
20
|
+
}
|
|
21
|
+
if (caller.roleName !== "leader" && caller.currentRunId === undefined) {
|
|
22
|
+
throw new Error("An Assignment inbox requires its current active AgentRun.");
|
|
23
|
+
}
|
|
24
|
+
return { scope: "task", taskId: caller.taskId, roleName: caller.roleName,
|
|
25
|
+
agentId: caller.agentId, nativeSessionId: caller.nativeSessionId,
|
|
26
|
+
...(caller.currentRunId === undefined ? {} : { runId: caller.currentRunId }) };
|
|
27
|
+
}
|
|
28
|
+
function ownsMessage(store, address, message) {
|
|
29
|
+
if (address.roleName === "leader" && message.taskId === address.taskId && isLeaderSteer(message))
|
|
30
|
+
return true;
|
|
31
|
+
if (address.runId === undefined || message.taskId !== address.taskId
|
|
32
|
+
|| message.recipient?.roleName !== address.roleName)
|
|
33
|
+
return false;
|
|
34
|
+
const current = store.getRun(address.taskId, address.runId);
|
|
35
|
+
const owner = message.recipient.ownerRunId === undefined ? null
|
|
36
|
+
: store.getRun(address.taskId, message.recipient.ownerRunId);
|
|
37
|
+
return current !== null && owner !== null && current.roleName === owner.roleName
|
|
38
|
+
&& owner.workItemId === message.recipient.workItemId && owner.reviewRoundId === message.recipient.reviewRoundId
|
|
39
|
+
&& current.workItemId === owner.workItemId && current.reviewRoundId === owner.reviewRoundId
|
|
40
|
+
&& (owner.id === current.id || messageAssignmentBlocker(store, message) === undefined);
|
|
41
|
+
}
|
|
42
|
+
function entries(store, address) {
|
|
43
|
+
if (address.scope === "global")
|
|
44
|
+
return store.listGlobalRoleMessages(address.roleName)
|
|
45
|
+
.filter(isGlobalInboxMessage);
|
|
46
|
+
const messages = store.listMessages(address.taskId).filter(message => ownsMessage(store, address, message));
|
|
47
|
+
return address.roleName === "leader" ? [...store.listTaskWakes(address.taskId), ...messages] : messages;
|
|
48
|
+
}
|
|
49
|
+
function getEntry(store, address, id) {
|
|
50
|
+
const entry = entries(store, address).find(entry => entry.id === id);
|
|
51
|
+
if (entry === undefined)
|
|
52
|
+
throw new Error("Inbox entry is not in this Agent's current authorized receiving scope.");
|
|
53
|
+
return entry;
|
|
54
|
+
}
|
|
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
|
+
}
|
|
60
|
+
function assertSelectedTarget(address, message) {
|
|
61
|
+
const selected = message.inputControl?.expectedSessionId;
|
|
62
|
+
const globalTarget = "roleName" in message ? message.deliveryTarget : undefined;
|
|
63
|
+
if (selected !== undefined && selected !== address.nativeSessionId
|
|
64
|
+
|| globalTarget !== undefined && (globalTarget.agentId !== address.agentId
|
|
65
|
+
|| globalTarget.nativeSessionId !== address.nativeSessionId)) {
|
|
66
|
+
throw new Error("The original input explicitly targets another native Session; inbox handling cannot retarget it.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function save(store, address, entry) {
|
|
70
|
+
if (isWake(entry))
|
|
71
|
+
store.saveTaskWake(address.taskId, entry);
|
|
72
|
+
else if (address.scope === "global")
|
|
73
|
+
store.updateGlobalRoleMessage(entry);
|
|
74
|
+
else
|
|
75
|
+
store.updateMessage(address.taskId, entry);
|
|
76
|
+
}
|
|
77
|
+
/** The same durable claim is checked at automatic delivery admission. No lease
|
|
78
|
+
* expires, and neither a receipt nor a cursor gives a caller identity. */
|
|
79
|
+
function blocker(store, address, entry) {
|
|
80
|
+
if (!isWake(entry) && address.scope === "task"
|
|
81
|
+
&& isTaskRecordRetired(store.listEvents(address.taskId), "message", entry.id))
|
|
82
|
+
return "message-retired";
|
|
83
|
+
if (entry.inbox?.ack !== undefined)
|
|
84
|
+
return "acknowledged";
|
|
85
|
+
if (entry.inbox !== undefined) {
|
|
86
|
+
return sameSession(entry.inbox.claim.actor, address) ? undefined : "claimed-by-previous-session";
|
|
87
|
+
}
|
|
88
|
+
if (isWake(entry)) {
|
|
89
|
+
// Native acceptance releases transport ownership, not semantic handling.
|
|
90
|
+
// The current Leader may explicitly claim the fixed delivered window.
|
|
91
|
+
if (entry.status === "consumed")
|
|
92
|
+
return undefined;
|
|
93
|
+
return "automatic-notification-owns-window";
|
|
94
|
+
}
|
|
95
|
+
if (address.scope === "task" && taskMessageInboxReceipt(store, entry) !== undefined) {
|
|
96
|
+
return "claimed-by-notification-window";
|
|
97
|
+
}
|
|
98
|
+
if (entry.control !== undefined && ["pending", "delivery-unknown"].includes(entry.control.outcome)) {
|
|
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";
|
|
106
|
+
// Both steer paths reserve pending control before submission and arbitrate
|
|
107
|
+
// with inbox claims. No control is unsubmitted, not an unknown delivery.
|
|
108
|
+
if (address.scope === "global") {
|
|
109
|
+
const message = entry;
|
|
110
|
+
if (message.delivery !== undefined && !accepted)
|
|
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;
|
|
116
|
+
if (message.deliveryTarget !== undefined && (message.deliveryTarget.nativeSessionId !== address.nativeSessionId
|
|
117
|
+
|| message.deliveryTarget.agentId !== address.agentId))
|
|
118
|
+
return "selected-native-session-changed";
|
|
119
|
+
if (message.inputControl?.expectedSessionId !== undefined
|
|
120
|
+
&& message.inputControl.expectedSessionId !== address.nativeSessionId)
|
|
121
|
+
return "selected-native-session-changed";
|
|
122
|
+
const turn = store.getGlobalRoleSessionSet(address.roleName)?.providerBinding?.run;
|
|
123
|
+
if (turn?.attemptId === `global-input:${address.roleName}/${entry.id}`)
|
|
124
|
+
return `native-attempt-${turn.status}`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const message = entry;
|
|
128
|
+
if (message.continuation?.runId !== undefined && message.continuation.runId !== address.runId) {
|
|
129
|
+
return "continuation-already-dispatched";
|
|
130
|
+
}
|
|
131
|
+
if (!accepted && message.inputControl?.expectedSessionId !== undefined
|
|
132
|
+
&& message.inputControl.expectedSessionId !== address.nativeSessionId)
|
|
133
|
+
return "selected-native-session-changed";
|
|
134
|
+
return address.roleName === "leader" && isLeaderSteer(message) ? undefined : messageAssignmentBlocker(store, message);
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
function checkMutation(store, address) {
|
|
139
|
+
const owner = address.scope === "global" ? { scope: "global", roleName: address.roleName }
|
|
140
|
+
: { scope: "task", taskId: address.taskId, roleName: address.roleName };
|
|
141
|
+
if (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))) {
|
|
142
|
+
throw new Error("Inbox cannot change while this runtime is being released.");
|
|
143
|
+
}
|
|
144
|
+
if (address.scope === "task") {
|
|
145
|
+
const task = store.getTask(address.taskId);
|
|
146
|
+
if (!task || !["active", "draft"].includes(task.status) || task.executionGate.state !== "enabled") {
|
|
147
|
+
throw new Error("Inbox handling requires an open Task with execution enabled.");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function summary(store, address, entry) {
|
|
152
|
+
return { id: entry.id, kind: isWake(entry) ? "notification-window" : "message",
|
|
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
|
+
}),
|
|
162
|
+
read: `yui inbox show ${entry.id}`, ...(isWake(entry)
|
|
163
|
+
? { originals: `yui task wake show ${entry.taskId} ${entry.id} --json` } : {}) };
|
|
164
|
+
}
|
|
165
|
+
function receipt(entry) {
|
|
166
|
+
return { id: entry.id, inbox: entry.inbox,
|
|
167
|
+
...(isWake(entry) ? { originals: `yui task wake show ${entry.taskId} ${entry.id} --json` } : {}) };
|
|
168
|
+
}
|
|
169
|
+
/** Own-address command: there is deliberately no --role, --task or --session.
|
|
170
|
+
* Reads do not claim. Exact claim precedes semantic handling; exact ack follows
|
|
171
|
+
* full original reads and actual handling. A batch is independent per item. */
|
|
172
|
+
export function runInboxCommand(args, store, env, now = new Date()) {
|
|
173
|
+
const [action, ...tail] = args;
|
|
174
|
+
const address = addressOf(store, env);
|
|
175
|
+
if (action === "list") {
|
|
176
|
+
const parsed = readOptions(tail);
|
|
177
|
+
if (parsed.positionals.length)
|
|
178
|
+
throw new Error("Inbox list accepts only --limit and --cursor.");
|
|
179
|
+
const retired = address.scope === "task" ? retiredTaskRecordIds(store.listEvents(address.taskId), "message") : new Set();
|
|
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");
|
|
183
|
+
const items = selected.map(entry => summary(store, address, entry));
|
|
184
|
+
if (address.scope === "task" && address.roleName === "leader") {
|
|
185
|
+
const mailbox = store.getWorkMailbox(target(address));
|
|
186
|
+
if (mailbox?.pending != null)
|
|
187
|
+
items.push({ id: pendingId(mailbox.pending), kind: "notification-window",
|
|
188
|
+
summary: summaryText(mailbox.pending.reasons.join(", ")),
|
|
189
|
+
blocked: mailbox.processing === null && !unfinishedLeaderInbox(store, address.taskId) ? null : "earlier-window-owned",
|
|
190
|
+
read: `yui inbox show ${pendingId(mailbox.pending)}` });
|
|
191
|
+
}
|
|
192
|
+
return recordPage(items, source(address), parsed, { identity: address });
|
|
193
|
+
}
|
|
194
|
+
if (action === "show") {
|
|
195
|
+
const parsed = readOptions(tail, ["--cursor"]);
|
|
196
|
+
if (parsed.positionals.length !== 1)
|
|
197
|
+
throw new Error("Inbox show requires one exact entry.");
|
|
198
|
+
const id = parsed.positionals[0];
|
|
199
|
+
if (id.startsWith("pending-") && address.scope === "task" && address.roleName === "leader") {
|
|
200
|
+
const pending = store.getWorkMailbox(target(address))?.pending;
|
|
201
|
+
if (!pending || id !== pendingId(pending))
|
|
202
|
+
throw new Error("Pending window changed; list the inbox again.");
|
|
203
|
+
return boundedDocument({ id, ...pending,
|
|
204
|
+
note: "Claim this exact window before processing; claim returns its durable wake and original-read command." }, `${source(address)}/${id}`, parsed.cursor);
|
|
205
|
+
}
|
|
206
|
+
const entry = getEntry(store, address, id);
|
|
207
|
+
const value = !isWake(entry) && address.scope === "task"
|
|
208
|
+
? expandTaskMessageResult(entry, (task, run) => store.getRun(task, run)) : entry;
|
|
209
|
+
return boundedDocument(value, `${source(address)}/${id}`, parsed.cursor);
|
|
210
|
+
}
|
|
211
|
+
if (action !== "claim" && action !== "ack")
|
|
212
|
+
throw new Error("Inbox commands: list, show, claim, ack.");
|
|
213
|
+
const parsed = readOptions(tail, ["--request-id", "--reason"]);
|
|
214
|
+
const ids = parsed.positionals;
|
|
215
|
+
const requestId = parsed.values.get("--request-id");
|
|
216
|
+
const reason = parsed.values.get("--reason");
|
|
217
|
+
if (!requestId || requestId.length > 128 || !/^[A-Za-z0-9_.:-]+$/.test(requestId)) {
|
|
218
|
+
throw new Error("Inbox mutation requires a stable --request-id (1..128 safe characters).");
|
|
219
|
+
}
|
|
220
|
+
if (!ids.length || ids.length > 16 || new Set(ids).size !== ids.length) {
|
|
221
|
+
throw new Error("Select 1..16 distinct exact inbox entries; no wildcard acknowledgement.");
|
|
222
|
+
}
|
|
223
|
+
if (action === "ack" && (!reason?.trim() || Buffer.byteLength(reason) > 2048)) {
|
|
224
|
+
throw new Error("Acknowledgement requires --reason describing handling or why no action remains (up to 2048 bytes).");
|
|
225
|
+
}
|
|
226
|
+
if (action === "claim" && reason !== undefined)
|
|
227
|
+
throw new Error("Claim does not record a disposition; use ack after handling.");
|
|
228
|
+
const apply = (id) => store.transaction(tx => {
|
|
229
|
+
const current = addressOf(tx, env);
|
|
230
|
+
checkMutation(tx, current);
|
|
231
|
+
let entry;
|
|
232
|
+
if (id.startsWith("pending-") && current.scope === "task" && current.roleName === "leader" && action === "claim") {
|
|
233
|
+
// Retry the exact logical operation even after its pending batch moved.
|
|
234
|
+
const existing = tx.listTaskWakes(current.taskId).find(wake => wake.inbox?.claim.requestId === requestId && sameSession(wake.inbox.claim.actor, current));
|
|
235
|
+
if (existing) {
|
|
236
|
+
if (existing.inboxPendingId !== id) {
|
|
237
|
+
throw new Error("Inbox request id was reused for a different window.");
|
|
238
|
+
}
|
|
239
|
+
return receipt(existing);
|
|
240
|
+
}
|
|
241
|
+
const mailbox = tx.getWorkMailbox(target(current));
|
|
242
|
+
if (!mailbox?.pending || pendingId(mailbox.pending) !== id)
|
|
243
|
+
throw new Error("Pending window changed; list the inbox again.");
|
|
244
|
+
if (mailbox.processing !== null || unfinishedLeaderInbox(tx, current.taskId)) {
|
|
245
|
+
throw new Error("An earlier automatic or active inbox claim owns the notification window.");
|
|
246
|
+
}
|
|
247
|
+
// A pending Leader input with an exact transport/interrupt owner cannot be
|
|
248
|
+
// hidden inside an actively consumed window.
|
|
249
|
+
for (const ref of mailbox.pending.refs) {
|
|
250
|
+
if (ref.type !== "message")
|
|
251
|
+
continue;
|
|
252
|
+
const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
|
|
253
|
+
if (message !== undefined && (taskMessageInboxReceipt(tx, message) !== undefined
|
|
254
|
+
|| deliveredMessageWake(tx, message) !== undefined)
|
|
255
|
+
|| message?.interruptThen !== undefined
|
|
256
|
+
|| message?.control !== undefined && message.control.outcome !== "rejected"
|
|
257
|
+
|| message?.inputControl?.expectedSessionId !== undefined
|
|
258
|
+
&& message.inputControl.expectedSessionId !== current.nativeSessionId) {
|
|
259
|
+
throw new Error("Window contains an input with an existing or foreign delivery owner; inspect its original receipt.");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const latest = latestTaskWake(tx.listTaskWakes(current.taskId));
|
|
263
|
+
// Pin each mutable Draft original's existing update event, not a clock
|
|
264
|
+
// comparison. A later edit is distinct input even within the same tick.
|
|
265
|
+
const refs = pinMessageRevisions(tx, current.taskId, mailbox.pending.refs);
|
|
266
|
+
const wake = createTaskWake({ id: tx.nextTaskWakeId(current.taskId), taskId: current.taskId,
|
|
267
|
+
reasons: mailbox.pending.reasons, refs,
|
|
268
|
+
fromCursor: latest?.toCursor ?? tx.getTask(current.taskId).createdAt,
|
|
269
|
+
toCursor: new Date(Math.max(now.getTime(), Date.parse(mailbox.pending.lastQueuedAt))).toISOString(), now });
|
|
270
|
+
entry = { ...wake, inbox: { claim: { requestId, actor: current, at: now.toISOString() } },
|
|
271
|
+
inboxPendingId: id };
|
|
272
|
+
tx.saveWorkMailbox(claimPending(mailbox, { batchId: `inbox:${wake.id}`,
|
|
273
|
+
owner: `agent-inbox:${wake.id}`, startedAt: now.toISOString() }));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
entry = getEntry(tx, current, id);
|
|
277
|
+
if (action === "claim") {
|
|
278
|
+
if (entry.inbox !== undefined) {
|
|
279
|
+
if (!sameSession(entry.inbox.claim.actor, current))
|
|
280
|
+
throw new Error("Claim belongs to a previous Session; read the original and prior effects, then explicitly acknowledge only after finishing its handling.");
|
|
281
|
+
if (entry.inbox.claim.requestId !== requestId)
|
|
282
|
+
throw new Error("Entry already claimed; retain the original request id.");
|
|
283
|
+
return receipt(entry);
|
|
284
|
+
}
|
|
285
|
+
const blocked = blocker(tx, current, entry);
|
|
286
|
+
if (blocked !== undefined)
|
|
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);
|
|
299
|
+
for (const previous of entries(tx, current)) {
|
|
300
|
+
if (previous.id === entry.id)
|
|
301
|
+
break;
|
|
302
|
+
if (isWake(previous) || previous.inbox?.ack !== undefined)
|
|
303
|
+
continue;
|
|
304
|
+
if (current.scope === "task"
|
|
305
|
+
&& isTaskRecordRetired(tx.listEvents(current.taskId), "message", previous.id))
|
|
306
|
+
continue;
|
|
307
|
+
if (current.scope === "global" && previous.notDelivered !== undefined)
|
|
308
|
+
continue;
|
|
309
|
+
if (previous.inbox !== undefined && sameSession(previous.inbox.claim.actor, current))
|
|
310
|
+
continue;
|
|
311
|
+
throw new Error(`Earlier inbox entry ${previous.id} must be handled first; input order is preserved.`);
|
|
312
|
+
}
|
|
313
|
+
entry = { ...entry, inbox: { claim: { requestId, actor: current, at: now.toISOString() } } };
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
if (!entry.inbox)
|
|
317
|
+
throw new Error("Claim the exact entry before handling and acknowledgement.");
|
|
318
|
+
// Ack settles already-owned handling; it never submits or retargets
|
|
319
|
+
// the original. The current rightful successor may settle a claim
|
|
320
|
+
// acquired by the exact selected Session, retaining both identities.
|
|
321
|
+
const claimant = entry.inbox.claim.actor;
|
|
322
|
+
if (isWake(entry) && !acceptedInput(entry)) {
|
|
323
|
+
const messageIds = new Set(entry.refs?.filter(ref => ref.type === "message").map(ref => ref.id));
|
|
324
|
+
for (const message of tx.listMessages(current.taskId)) {
|
|
325
|
+
if (messageIds.has(message.id))
|
|
326
|
+
assertSelectedTarget(claimant, message);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (!isWake(entry) && !acceptedInput(entry))
|
|
330
|
+
assertSelectedTarget(claimant, entry);
|
|
331
|
+
if (!isWake(entry) && current.scope === "task"
|
|
332
|
+
&& !(current.roleName === "leader" && isLeaderSteer(entry))) {
|
|
333
|
+
const invalid = messageAssignmentBlocker(tx, entry);
|
|
334
|
+
if (invalid !== undefined)
|
|
335
|
+
throw new Error(`Assignment no longer authorizes this inbox disposition: ${invalid}.`);
|
|
336
|
+
}
|
|
337
|
+
// Current rightful successor may settle a prior Session's unfinished
|
|
338
|
+
// claim explicitly; this never replays input or discards prior evidence.
|
|
339
|
+
if (entry.inbox.ack) {
|
|
340
|
+
if (entry.inbox.ack.requestId !== requestId || entry.inbox.ack.reason !== reason
|
|
341
|
+
|| !sameSession(entry.inbox.ack.actor, current))
|
|
342
|
+
throw new Error("Conflicting acknowledgement; original disposition retained.");
|
|
343
|
+
return receipt(entry);
|
|
344
|
+
}
|
|
345
|
+
const inbox = { ...entry.inbox,
|
|
346
|
+
ack: { requestId, actor: current, at: now.toISOString(), reason: reason } };
|
|
347
|
+
if (isWake(entry)) {
|
|
348
|
+
const mailbox = tx.getWorkMailbox(target(current));
|
|
349
|
+
// stop/start removes operational mailboxes, not durable claim
|
|
350
|
+
// ownership. Settle that receipt without touching a later batch.
|
|
351
|
+
if (mailbox?.processing?.owner === `agent-inbox:${entry.id}`
|
|
352
|
+
&& mailbox.processing.batchId === `inbox:${entry.id}`) {
|
|
353
|
+
tx.saveWorkMailbox(completeProcessing(mailbox, mailbox.processing.batchId));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
entry = { ...entry, inbox };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
save(tx, current, entry);
|
|
360
|
+
return receipt(entry);
|
|
361
|
+
});
|
|
362
|
+
if (ids.length === 1)
|
|
363
|
+
return apply(ids[0]);
|
|
364
|
+
return { results: ids.map(id => {
|
|
365
|
+
try {
|
|
366
|
+
return { id, ok: true, result: apply(id) };
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
return { id, ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
370
|
+
}
|
|
371
|
+
}) };
|
|
372
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
2
|
import { loadYuiSkillContexts } from "../context/roleSessionContext.js";
|
|
3
|
+
import { checkCurrentSkills } from "../context/currentSkills.js";
|
|
3
4
|
const ROLE_LAUNCH_CONTEXT_OPTIONS = new Set([
|
|
4
5
|
"--workspace",
|
|
5
6
|
"--description",
|
|
@@ -34,6 +35,7 @@ export function validateConfiguredRoleSkills(yuiHome, skillIds) {
|
|
|
34
35
|
throw usageError(`Configured Skill id is invalid: ${id}.`);
|
|
35
36
|
}
|
|
36
37
|
try {
|
|
38
|
+
checkCurrentSkills(yuiHome, [id]);
|
|
37
39
|
loadYuiSkillContexts(yuiHome, [id]);
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { taskMessageInboxReceipt } from "../message/taskInboxOwnership.js";
|
|
3
4
|
import { agentExecutionComponentLabel } from "../agent/executionComponents.js";
|
|
4
5
|
import { assertLeaderArchiveAdmission } from "../task/leaderArchiveAuthority.js";
|
|
5
6
|
import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
6
7
|
import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
|
|
7
8
|
import { contextContentDigest, contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
8
9
|
import { sourceRunContextValue } from "../context/sourceRunContext.js";
|
|
9
|
-
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";
|
|
10
11
|
import { createRunInput } from "../context/runInputContract.js";
|
|
11
12
|
import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
|
|
12
13
|
import { assertContextRecordReadable, readTaskContextResource } from "../context/taskContext.js";
|
|
@@ -1631,9 +1632,25 @@ function taskMessageCommand(args, store, options) {
|
|
|
1631
1632
|
const message = store.transaction((tx) => {
|
|
1632
1633
|
assertTaskDeliveryAuthority(tx, options.environment, ref.taskId);
|
|
1633
1634
|
const current = tx.listMessages(ref.taskId).find((entry) => entry.id === ref.localId);
|
|
1634
|
-
if (current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
|
|
1635
|
+
if (current?.inbox?.ack !== undefined || current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
|
|
1635
1636
|
throw usageError("Only an unassigned pending Message can be explicitly handed off.");
|
|
1636
1637
|
}
|
|
1638
|
+
if (current.inbox !== undefined) {
|
|
1639
|
+
const actor = current.inbox.claim.actor;
|
|
1640
|
+
for (const runId of new Set([actor.runId, current.recipient.ownerRunId])) {
|
|
1641
|
+
const handler = runId === undefined ? null : tx.getRun(ref.taskId, runId);
|
|
1642
|
+
const observation = handler === null ? undefined : runExecutionObservation(handler, tx.getTaskRoleSessionSet(ref.taskId, handler.roleName)?.providerBinding, tx.listEvents(ref.taskId));
|
|
1643
|
+
if (handler === null || handler.status === "active"
|
|
1644
|
+
|| handler.result?.failureReason === "delivery-unknown"
|
|
1645
|
+
|| handler.result?.provider === undefined
|
|
1646
|
+
|| runId === actor.runId && handler.result.provider.conversationId !== actor.nativeSessionId
|
|
1647
|
+
|| !["completed", "failed", "cancelled"].includes(observation?.delivery ?? "")) {
|
|
1648
|
+
throw usageError("The original and current inbox handlers must have exact stopped native execution before explicit handoff.");
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
// Preserve the original claim: the new authorized Assignment must
|
|
1652
|
+
// inspect prior effects and ack explicitly, never auto-replay the input.
|
|
1653
|
+
}
|
|
1637
1654
|
const recipient = resolveMessageRecipient(tx, ref.taskId, requiredOption(parsed.options, "--to"), {
|
|
1638
1655
|
...(current.recipient.workItemId === undefined ? {} : { workItemId: current.recipient.workItemId }),
|
|
1639
1656
|
...(current.recipient.reviewRoundId === undefined ? {} : { reviewRoundId: current.recipient.reviewRoundId })
|
|
@@ -1853,15 +1870,23 @@ function steerTaskInput(taskId, input, store, options) {
|
|
|
1853
1870
|
// and idempotent, so the Host's later settlement fold promotes it to a proven
|
|
1854
1871
|
// terminal and never rewinds it. An idempotent replay above already returned
|
|
1855
1872
|
// the saved Message, so this records the attempt exactly once.
|
|
1856
|
-
store.transaction((tx) => {
|
|
1873
|
+
const admitted = store.transaction((tx) => {
|
|
1857
1874
|
const saved = tx.listMessages(result.task.id).find((entry) => entry.id === result.message.id);
|
|
1858
1875
|
const requestId = saved?.inputControl?.requestId;
|
|
1859
|
-
|
|
1860
|
-
|
|
1876
|
+
// The recipient may claim between Message persistence and control
|
|
1877
|
+
// admission. A Leader window owns its referenced originals even after ack;
|
|
1878
|
+
// never re-submit them through a delayed steer.
|
|
1879
|
+
if (saved === undefined || requestId === undefined || taskMessageInboxReceipt(tx, saved) !== undefined)
|
|
1880
|
+
return false;
|
|
1861
1881
|
tx.updateMessage(result.task.id, recordTaskMessageControlOutcome(saved, {
|
|
1862
1882
|
requestId, receiptId, outcome: "pending", observedAt: clock(options)
|
|
1863
1883
|
}));
|
|
1884
|
+
return true;
|
|
1864
1885
|
});
|
|
1886
|
+
if (!admitted) {
|
|
1887
|
+
return output(`Steer message ${result.message.id} saved but not delivered (TARGET_CHANGED: inbox handling owns the input).\n`, { taskId: result.task.id, message: messageReceipt(result.message),
|
|
1888
|
+
steer: { state: "not-steered", code: "TARGET_CHANGED", detail: "Inbox handling already owns the original input." } });
|
|
1889
|
+
}
|
|
1865
1890
|
return {
|
|
1866
1891
|
kind: "input-steer",
|
|
1867
1892
|
taskId: result.task.id,
|
|
@@ -2407,6 +2432,9 @@ function registerInterruptThen(store, taskId, roleName, thenMessageRef, target,
|
|
|
2407
2432
|
}
|
|
2408
2433
|
// A Message that already delivered, or was already ruled undeliverable, is not
|
|
2409
2434
|
// a fresh input to reuse; reusing it would replay or resurrect a settled fact.
|
|
2435
|
+
if (taskMessageInboxReceipt(store, message) !== undefined) {
|
|
2436
|
+
return { code: "TARGET_CHANGED", detail: `Message ${thenMessageRef} is owned by active inbox handling.` };
|
|
2437
|
+
}
|
|
2410
2438
|
if (message.continuation?.runId !== undefined) {
|
|
2411
2439
|
return { code: "TARGET_CHANGED", detail: `Message ${thenMessageRef} was already delivered by ${message.continuation.runId}.` };
|
|
2412
2440
|
}
|
|
@@ -4749,14 +4777,18 @@ function runContextCommand(args, store, options) {
|
|
|
4749
4777
|
const data = boundedDocument(delta, `run:${taskId}/${runId}/delta/${parsed.values.get("--after")}`, parsed.cursor);
|
|
4750
4778
|
return output(`${JSON.stringify(data)}\n`, { contextDelta: data });
|
|
4751
4779
|
}
|
|
4752
|
-
const parsed = readOptions(rest, ["--cursor"]);
|
|
4780
|
+
const parsed = readOptions(rest, ["--cursor", "--view"]);
|
|
4753
4781
|
if (first === undefined || parsed.positionals.length !== 0) {
|
|
4754
4782
|
throw usageError("Task run context usage: yui task run context <task>/<run>.");
|
|
4755
4783
|
}
|
|
4756
4784
|
const { taskId, runId } = parseRunContextReference(first);
|
|
4757
4785
|
authorizeRunContext(store, taskId, runId, options.environment);
|
|
4758
4786
|
const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
|
|
4759
|
-
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);
|
|
4760
4792
|
return output(`${JSON.stringify(data)}\n`, { context: data });
|
|
4761
4793
|
}
|
|
4762
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 {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { accessSync, constants, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export const BUILTIN_YUI_SKILLS = new Set([
|
|
5
|
+
"yui-runtime", "yui-operator", "yui-leader", "yui-worker", "yui-reviewer"
|
|
6
|
+
]);
|
|
7
|
+
export function currentSkillPath(home, id) {
|
|
8
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id))
|
|
9
|
+
throw new Error(`Invalid configured Skill id: ${id}.`);
|
|
10
|
+
if (BUILTIN_YUI_SKILLS.has(id))
|
|
11
|
+
return fileURLToPath(new URL(`../../skills/${id}`, import.meta.url));
|
|
12
|
+
if (home === undefined)
|
|
13
|
+
throw new Error("YUI_HOME is required to load configured Role skills.");
|
|
14
|
+
return resolve(home, "skills", id);
|
|
15
|
+
}
|
|
16
|
+
/** Per-turn metadata only: never read the body, traverse resources, snapshot a
|
|
17
|
+
* package, or claim that the model still has instructions in its context. */
|
|
18
|
+
export function checkCurrentSkills(home, ids) {
|
|
19
|
+
return [...new Set(ids)].map(id => {
|
|
20
|
+
const path = currentSkillPath(home, id);
|
|
21
|
+
const versionPath = join(path, "VERSION");
|
|
22
|
+
try {
|
|
23
|
+
for (const file of [join(path, "SKILL.md"), versionPath]) {
|
|
24
|
+
if (realpathSync(file) !== resolve(file) || !lstatSync(file).isFile()) {
|
|
25
|
+
throw new Error(`Skill files must be regular files without symlinks: ${file}.`);
|
|
26
|
+
}
|
|
27
|
+
accessSync(file, constants.R_OK);
|
|
28
|
+
}
|
|
29
|
+
if (lstatSync(versionPath).size > 128)
|
|
30
|
+
throw new Error("VERSION exceeds 128 bytes.");
|
|
31
|
+
const version = readFileSync(versionPath, "utf8").trim();
|
|
32
|
+
if (!/^[1-9][0-9]{0,8}$/u.test(version))
|
|
33
|
+
throw new Error("VERSION must contain a positive internal integer.");
|
|
34
|
+
return { id, path, version, versionPath };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Current Skill ${id} is missing, unreadable or invalid at ${path}. `
|
|
38
|
+
+ `${error instanceof Error ? error.message : String(error)} `
|
|
39
|
+
+ "Restore the current installation/configured Skill (including VERSION) in this Home, then check again; "
|
|
40
|
+
+ "do not replace the Session or replay input to repair Skill loading.", { cause: error });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export const SKILL_CHECK_COMMAND = "yui session skills --json";
|
|
45
|
+
export const SKILL_REUSE_RULE = "At the start of every turn, check current Skill versions and availability. "
|
|
46
|
+
+ "Read full SKILL.md and necessary references on first entry, version change, recovery, "
|
|
47
|
+
+ "or missing/compacted/uncertain context; reuse only unchanged full instructions available now. "
|
|
48
|
+
+ "A version check is not a loading receipt or authority.";
|
|
@@ -3,29 +3,18 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { snapshotSkillPackage, verifySkillPackage } from "./skillPackage.js";
|
|
6
|
+
import { BUILTIN_YUI_SKILLS, checkCurrentSkills } from "./currentSkills.js";
|
|
7
|
+
export { BUILTIN_YUI_SKILLS } from "./currentSkills.js";
|
|
6
8
|
import { SYSTEM_LEADER_ROLE, SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
7
|
-
export const BUILTIN_YUI_SKILLS = new Set([
|
|
8
|
-
"yui-runtime",
|
|
9
|
-
"yui-operator",
|
|
10
|
-
"yui-leader",
|
|
11
|
-
"yui-worker",
|
|
12
|
-
"yui-reviewer"
|
|
13
|
-
]);
|
|
14
9
|
/**
|
|
15
10
|
* Compiles stable Role policy for an Agent-native instruction channel.
|
|
16
11
|
* Dynamic wakeups and AgentRun assignments deliberately remain outside this value.
|
|
17
12
|
*/
|
|
18
13
|
export function compileRoleSessionContext(yuiHome, role, owner, options = {}) {
|
|
19
14
|
const kind = roleSessionKind(role, owner, options.purpose ?? "execution");
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
...(builtInSkillId === undefined ? [] : [builtInSkillId]),
|
|
24
|
-
...(role.skills ?? [])
|
|
25
|
-
]);
|
|
26
|
-
const skills = options.skillPackages === undefined
|
|
27
|
-
? loadYuiSkillContexts(yuiHome, skillIds)
|
|
28
|
-
: options.skillPackages.map(loadSkillPackageContext);
|
|
15
|
+
const skills = checkCurrentSkills(yuiHome, roleSkillIds(role, kind)).map(skill => ({
|
|
16
|
+
...skill, content: readFileSync(join(skill.path, "SKILL.md"), "utf8").trim()
|
|
17
|
+
}));
|
|
29
18
|
return {
|
|
30
19
|
developerInstructions: renderDeveloperInstructions(kind, role, owner),
|
|
31
20
|
skills,
|
|
@@ -34,6 +23,14 @@ export function compileRoleSessionContext(yuiHome, role, owner, options = {}) {
|
|
|
34
23
|
: { managedContextFile: roleContextFile(yuiHome, role, owner, kind) })
|
|
35
24
|
};
|
|
36
25
|
}
|
|
26
|
+
export function roleSkillIds(role, kind) {
|
|
27
|
+
const builtInSkillId = kind === "global" ? undefined : `yui-${kind}`;
|
|
28
|
+
return unique([
|
|
29
|
+
"yui-runtime",
|
|
30
|
+
...(builtInSkillId === undefined ? [] : [builtInSkillId]),
|
|
31
|
+
...(role.skills ?? [])
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
37
34
|
function renderDeveloperInstructions(kind, role, owner) {
|
|
38
35
|
const core = renderRoleCore(kind, role, owner);
|
|
39
36
|
const profile = [
|