@zq-silk/yui 0.15.9 → 0.15.11
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/agent/launchEnvironment.js +7 -0
- package/dist/cli/commandCatalog.js +26 -6
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +336 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +511 -83
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/taskContext.js +38 -2
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +3 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +136 -4
- package/dist/storage/sqliteStore.js +40 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/web/assets/client/app.js +55 -0
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +106 -0
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +117 -1
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/testing/verification-levels.md +24 -0
- package/docs/testing/verification-levels.zh-CN.md +11 -0
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +151 -49
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +6 -1
- package/skills/yui-runtime/SKILL.md +27 -0
- package/skills/yui-runtime/references/publication.md +20 -0
|
@@ -5,27 +5,66 @@ import { formatRunReceiptId } from "../task/taskRecordReference.js";
|
|
|
5
5
|
import { currentProviderConversation, managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
|
|
6
6
|
import { taskRoleRuntimeIdentity } from "../runtime/managedCaller.js";
|
|
7
7
|
import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
8
|
+
export class RuntimeHookRunFenceError extends Error {
|
|
9
|
+
}
|
|
8
10
|
/**
|
|
9
11
|
* Resolve observations by Session and exact input identity. A startup Hook
|
|
10
12
|
* may introduce a Session for an explicit new-Session AgentRun.
|
|
11
13
|
*/
|
|
12
|
-
export function resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options = {}) {
|
|
14
|
+
export function resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options = {}, currentStore) {
|
|
15
|
+
if (currentStore === undefined) {
|
|
16
|
+
const store = openCurrentTaskStore(requireIdentity(environment.YUI_HOME, "YUI_HOME"));
|
|
17
|
+
try {
|
|
18
|
+
return resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options, store);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
store.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (environment.YUI_SESSION_SCOPE === "global") {
|
|
25
|
+
if (environment.YUI_ADAPTER_ID !== adapterId)
|
|
26
|
+
throw new RuntimeHookRunFenceError("Global observation adapter mismatch.");
|
|
27
|
+
const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
|
|
28
|
+
const agentId = requireIdentity(environment.YUI_AGENT_ID, "Agent id");
|
|
29
|
+
const workspace = requireIdentity(environment.YUI_WORKSPACE, "YUI workspace");
|
|
30
|
+
const nativeSessionId = requireIdentity(payloadNativeSessionId, "Provider session id");
|
|
31
|
+
const sessions = currentStore.getGlobalRoleSessionSet(roleName);
|
|
32
|
+
const role = currentStore.getGlobalRole(roleName);
|
|
33
|
+
const session = sessions?.sessions[agentId];
|
|
34
|
+
const turn = sessions?.providerBinding?.run;
|
|
35
|
+
if (role?.activeAgentId !== agentId || sessions?.activeAgentId !== agentId || session?.adapterId !== adapterId
|
|
36
|
+
|| session.status === "ended"
|
|
37
|
+
|| session.nativeSessionId !== nativeSessionId || session.effective.workspace.root !== workspace) {
|
|
38
|
+
throw new RuntimeHookRunFenceError("Global observation Session does not match durable state.");
|
|
39
|
+
}
|
|
40
|
+
if (options.attemptId !== undefined && turn?.attemptId !== options.attemptId
|
|
41
|
+
|| options.nativeTurnId !== undefined && turn?.nativeTurnId !== undefined
|
|
42
|
+
&& turn.nativeTurnId !== options.nativeTurnId) {
|
|
43
|
+
throw new RuntimeHookRunFenceError("Global observation does not match the exact current input.");
|
|
44
|
+
}
|
|
45
|
+
if (options.exactInput && (turn == null
|
|
46
|
+
|| options.attemptId === undefined && (options.nativeTurnId === undefined || turn.nativeTurnId !== options.nativeTurnId)
|
|
47
|
+
|| ["rejected", "deferred"].includes(turn.status))) {
|
|
48
|
+
throw new RuntimeHookRunFenceError("Global observation input has no exact execution binding.");
|
|
49
|
+
}
|
|
50
|
+
return { roleName, agentId, workspace, nativeSessionId,
|
|
51
|
+
...(options.sessionOnly || turn == null ? {} : { receiptId: turn.attemptId }) };
|
|
52
|
+
}
|
|
13
53
|
if (environment.YUI_SESSION_SCOPE !== "task") {
|
|
14
|
-
throw new
|
|
54
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook requires a Task session scope.");
|
|
15
55
|
}
|
|
16
56
|
if (environment.YUI_ADAPTER_ID !== adapterId) {
|
|
17
|
-
throw new
|
|
57
|
+
throw new RuntimeHookRunFenceError(`Runtime observation Hook requires the ${adapterId} adapter.`);
|
|
18
58
|
}
|
|
19
|
-
const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
|
|
20
59
|
const taskId = requireIdentity(environment.YUI_TASK_ID, "Task id");
|
|
21
60
|
const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
|
|
22
61
|
const agentId = requireIdentity(environment.YUI_AGENT_ID, "Agent id");
|
|
23
62
|
const workspace = requireIdentity(environment.YUI_WORKSPACE, "YUI workspace");
|
|
24
63
|
const nativeSessionId = requireIdentity(payloadNativeSessionId, "Provider session id");
|
|
25
|
-
const store =
|
|
64
|
+
const store = currentStore;
|
|
26
65
|
const task = store.getTask(taskId);
|
|
27
66
|
if (task === null) {
|
|
28
|
-
throw new
|
|
67
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook Task does not accept this lifecycle boundary.");
|
|
29
68
|
}
|
|
30
69
|
const role = store.getRole(taskId, roleName);
|
|
31
70
|
const sessions = store.getTaskRoleSessionSet(taskId, roleName);
|
|
@@ -56,6 +95,14 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
56
95
|
taskId, roleName, agentId, nativeSessionId,
|
|
57
96
|
continuationId: options.continuationId,
|
|
58
97
|
}));
|
|
98
|
+
if (options.exactInput && acceptedBinding === null && !matchesProviderTurn) {
|
|
99
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook input has no exact accepted execution binding.");
|
|
100
|
+
}
|
|
101
|
+
if (options.startupRunId !== undefined && options.startupRunId !== activeRun?.id) {
|
|
102
|
+
// A historical Session can report terminal evidence, but its old startup
|
|
103
|
+
// must not bind a new Session under a successor's active Run.
|
|
104
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook startup does not match its exact launch Run.");
|
|
105
|
+
}
|
|
59
106
|
// Collection of an exactly accepted terminal fact is not a new action by
|
|
60
107
|
// the Role. A successor's active Agent or revoked execution permission
|
|
61
108
|
// cannot erase the original AgentRun's evidence.
|
|
@@ -70,17 +117,26 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
70
117
|
&& activeRun.purpose === "planning"
|
|
71
118
|
&& activeRun.roleName === roleName
|
|
72
119
|
&& runPurposeAdmitsTaskState(activeRun.purpose, task);
|
|
120
|
+
// Restoring the Draft Leader's existing planning Session needs no new Run.
|
|
121
|
+
// Admit its Session facts; the shared identity/workspace fence below still
|
|
122
|
+
// requires the exact current Session and never grants delivery authority.
|
|
123
|
+
const planningSessionObservation = options.sessionOnly === true
|
|
124
|
+
&& task.status === "draft" && roleName === "leader"
|
|
125
|
+
&& runPurposeAdmitsTaskState("planning", task)
|
|
126
|
+
&& session?.status === "active"
|
|
127
|
+
&& session.effective.executionAuthority === "planning";
|
|
73
128
|
if (!(task.status === "active" && task.executionGate.state === "enabled")
|
|
74
129
|
&& !(task.status === "completed" && options.sessionOnly === true)
|
|
75
130
|
&& !planningObservation
|
|
131
|
+
&& !planningSessionObservation
|
|
76
132
|
&& !existingExecutionObservation) {
|
|
77
|
-
throw new
|
|
133
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook Task does not accept this lifecycle boundary.");
|
|
78
134
|
}
|
|
79
135
|
const runtimeIdentity = role === null ? null : taskRoleRuntimeIdentity(role, activeRun);
|
|
80
136
|
if (!existingExecutionObservation && (runtimeIdentity === null || runtimeIdentity.agentId !== agentId
|
|
81
137
|
|| runtimeIdentity.adapterId !== adapterId
|
|
82
138
|
|| (sessions !== null && sessions.activeAgentId !== agentId))) {
|
|
83
|
-
throw new
|
|
139
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook Role or Agent is not current.");
|
|
84
140
|
}
|
|
85
141
|
const lifecycleMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
86
142
|
scope: "task",
|
|
@@ -97,7 +153,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
97
153
|
|| observedSession.adapterId !== adapterId
|
|
98
154
|
|| observedSession.nativeSessionId !== nativeSessionId
|
|
99
155
|
|| observedSession.effective.workspace.root !== workspace) {
|
|
100
|
-
throw new
|
|
156
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook Session does not match durable state.");
|
|
101
157
|
}
|
|
102
158
|
return {
|
|
103
159
|
taskId,
|
|
@@ -146,7 +202,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
146
202
|
? managedProviderTurnId(providerTurn) ?? undefined
|
|
147
203
|
: undefined;
|
|
148
204
|
if (options.terminal === true && acceptedBinding === null && registeredRunId === undefined) {
|
|
149
|
-
throw new
|
|
205
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook terminal has no exact accepted execution binding.");
|
|
150
206
|
}
|
|
151
207
|
const terminalRun = acceptedBinding !== null
|
|
152
208
|
? store.getRun(taskId, acceptedBinding.fence.runId)
|
|
@@ -166,7 +222,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
166
222
|
&& !exactTerminal
|
|
167
223
|
&& registeredRunId === undefined
|
|
168
224
|
&& acceptedBinding === null) {
|
|
169
|
-
throw new
|
|
225
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook has no matching durable in-flight AgentRun.");
|
|
170
226
|
}
|
|
171
227
|
const runId = acceptedBinding?.fence.runId
|
|
172
228
|
?? registeredRunId
|
|
@@ -181,21 +237,21 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
181
237
|
|| run.roleName !== roleName
|
|
182
238
|
|| run.effective.agentId !== agentId
|
|
183
239
|
|| run.effective.adapterId !== adapterId) {
|
|
184
|
-
throw new
|
|
240
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook AgentRun does not match durable active state.");
|
|
185
241
|
}
|
|
186
242
|
if (run.effective.workspace.root !== workspace) {
|
|
187
|
-
throw new
|
|
243
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook workspace does not match the durable AgentRun snapshot.");
|
|
188
244
|
}
|
|
189
245
|
if (session !== undefined && acceptedBinding === null && !matchesProviderTurn && !replacementStartup && !resumedStartup) {
|
|
190
246
|
if (session.adapterId !== adapterId
|
|
191
247
|
|| session.nativeSessionId !== nativeSessionId
|
|
192
248
|
|| session.effective.workspace.root !== workspace) {
|
|
193
|
-
throw new
|
|
249
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook Session does not match durable state.");
|
|
194
250
|
}
|
|
195
251
|
}
|
|
196
252
|
else if (acceptedBinding === null && !matchesProviderTurn && (session === undefined || replacementStartup)) {
|
|
197
253
|
if (!discoveredStartup && !preallocatedStartup) {
|
|
198
|
-
throw new
|
|
254
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook has no matching new-Session intent.");
|
|
199
255
|
}
|
|
200
256
|
}
|
|
201
257
|
return {
|
|
@@ -230,7 +286,7 @@ function knownContinuationBinding(events, expected) {
|
|
|
230
286
|
return null;
|
|
231
287
|
if (matches.some((candidate) => candidate.fence.runId !== binding.fence.runId
|
|
232
288
|
|| candidate.fence.receiptId !== binding.fence.receiptId)) {
|
|
233
|
-
throw new
|
|
289
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook continuation has conflicting durable AgentRun bindings.");
|
|
234
290
|
}
|
|
235
291
|
return binding;
|
|
236
292
|
}
|
|
@@ -260,17 +316,17 @@ function acceptedRunBinding(events, expected) {
|
|
|
260
316
|
&& candidate.fence.nativeTurnId !== expected.nativeTurnId)
|
|
261
317
|
|| candidate.fence.runId !== binding.fence.runId
|
|
262
318
|
|| candidate.fence.receiptId !== binding.fence.receiptId))) {
|
|
263
|
-
throw new
|
|
319
|
+
throw new RuntimeHookRunFenceError("Runtime observation Hook native Turn has conflicting durable AgentRun bindings.");
|
|
264
320
|
}
|
|
265
321
|
return binding;
|
|
266
322
|
}
|
|
267
323
|
function requireIdentity(value, label) {
|
|
268
324
|
if (typeof value !== "string" || value.includes("\0")) {
|
|
269
|
-
throw new
|
|
325
|
+
throw new RuntimeHookRunFenceError(`${label} is required.`);
|
|
270
326
|
}
|
|
271
327
|
const normalized = value.trim();
|
|
272
328
|
if (normalized.length === 0 || normalized.length > 1_024) {
|
|
273
|
-
throw new
|
|
329
|
+
throw new RuntimeHookRunFenceError(`${label} is invalid.`);
|
|
274
330
|
}
|
|
275
331
|
return normalized;
|
|
276
332
|
}
|
|
@@ -7,39 +7,70 @@ import { createRuntimeObservation, runtimeObservationSemanticKey } from "../runt
|
|
|
7
7
|
import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
|
|
8
8
|
import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
|
|
9
9
|
import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
10
|
-
import {
|
|
10
|
+
import { AGENT_HOST_EVENT_PROTOCOL } from "../runtime/agentHostProtocol.js";
|
|
11
|
+
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
11
12
|
let structuredSequence = 0;
|
|
13
|
+
let deliveryFailure;
|
|
14
|
+
// Only live diagnostics for this process's immutable files, not another queue.
|
|
15
|
+
// No payload, retry, or authoritative delivery state is kept in this map.
|
|
16
|
+
const emittedEvents = new Map();
|
|
17
|
+
/** A live transport observation, independent of Provider state and Run truth. */
|
|
18
|
+
export function structuredProviderEventDelivery(home, environment, nativeSessionId) {
|
|
19
|
+
try {
|
|
20
|
+
const inbox = new FileRuntimeEventInbox(home);
|
|
21
|
+
const pending = [];
|
|
22
|
+
for (const [id, entry] of emittedEvents) {
|
|
23
|
+
if (entry.home !== home)
|
|
24
|
+
continue;
|
|
25
|
+
if (!inbox.has(id)) {
|
|
26
|
+
emittedEvents.delete(id);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (entry.taskId === environment.YUI_TASK_ID && entry.roleName === environment.YUI_ROLE
|
|
30
|
+
&& (nativeSessionId === undefined || entry.nativeSessionId === nativeSessionId))
|
|
31
|
+
pending.push(entry);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
pending: pending.length,
|
|
35
|
+
pendingTerminals: pending.filter(event => event.terminal).length,
|
|
36
|
+
...(deliveryFailure === undefined ? {} : { failure: deliveryFailure })
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { pending: 0, pendingTerminals: 0, failure: deliveryFault("persistence", error) };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function deliveryFault(stage, error) {
|
|
44
|
+
return {
|
|
45
|
+
stage, detail: redactAgentErrorText(error instanceof Error ? error.message : String(error)),
|
|
46
|
+
observedAt: new Date().toISOString()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
12
49
|
export async function publishStructuredProviderAttachmentExit(input) {
|
|
13
50
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
14
51
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
15
|
-
const owner =
|
|
16
|
-
terminal: true, attemptId: input.attemptId, nativeTurnId: input.nativeTurnId
|
|
17
|
-
});
|
|
52
|
+
const owner = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
|
|
18
53
|
await persistAndApply(input.home, [observation({
|
|
19
54
|
kind: input.failed ? "session.failed" : "session.ended", observedAt: input.observedAt,
|
|
20
55
|
sequence: nextStructuredSequence(), ordinal: 0,
|
|
21
56
|
fence: {
|
|
22
57
|
taskId: owner.taskId, roleName: owner.roleName, agentId: owner.agentId, driverId: driver.id,
|
|
23
58
|
nativeSessionId: input.nativeSessionId, conversationId: input.nativeSessionId,
|
|
24
|
-
...(owner.runId === undefined ? {} : { runId: owner.runId }),
|
|
25
59
|
...(input.attemptId === undefined ? {} : { receiptId: input.attemptId }),
|
|
26
60
|
...(input.nativeTurnId === undefined ? {} : { nativeTurnId: input.nativeTurnId })
|
|
27
61
|
}
|
|
28
|
-
})],
|
|
62
|
+
})], input.environment);
|
|
29
63
|
}
|
|
30
64
|
export async function publishStructuredProviderActivity(input) {
|
|
31
65
|
const activity = input.activity;
|
|
32
66
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
33
67
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
34
|
-
const owner =
|
|
35
|
-
nativeTurnId: activity.nativeTurnId, attemptId: activity.attemptId
|
|
36
|
-
});
|
|
68
|
+
const owner = describeHostIdentity(input.environment, adapterId, activity.nativeSessionId);
|
|
37
69
|
const event = observation({
|
|
38
70
|
kind: activity.phase === "model" ? "activity.observed" : `operation.${activity.phase}`,
|
|
39
71
|
observedAt: activity.observedAt, sequence: nextStructuredSequence(), ordinal: 0,
|
|
40
72
|
fence: {
|
|
41
73
|
taskId: owner.taskId, roleName: owner.roleName,
|
|
42
|
-
...(owner.runId === undefined ? {} : { runId: owner.runId }),
|
|
43
74
|
agentId: owner.agentId, driverId: driver.id,
|
|
44
75
|
nativeSessionId: activity.nativeSessionId, conversationId: activity.conversationId,
|
|
45
76
|
receiptId: activity.attemptId,
|
|
@@ -49,7 +80,7 @@ export async function publishStructuredProviderActivity(input) {
|
|
|
49
80
|
? { activity: "model", activityId: activity.id }
|
|
50
81
|
: { operation: "tool", operationId: activity.id }
|
|
51
82
|
});
|
|
52
|
-
await persistAndApply(input.home, [event],
|
|
83
|
+
await persistAndApply(input.home, [event], input.environment);
|
|
53
84
|
}
|
|
54
85
|
/** An exact provider item id is evidence of visible input, not an inferred
|
|
55
86
|
* human author. The original managed request is never replaced.
|
|
@@ -57,28 +88,21 @@ export async function publishStructuredProviderActivity(input) {
|
|
|
57
88
|
export async function publishStructuredProviderInputObserved(input) {
|
|
58
89
|
const observed = input.observed;
|
|
59
90
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
60
|
-
const fence =
|
|
61
|
-
nativeTurnId: observed.nativeTurnId, terminal: true, sessionOnly: true
|
|
62
|
-
});
|
|
63
|
-
if (fence.runId === undefined)
|
|
64
|
-
return; // Ordinary chat need not be mirrored.
|
|
91
|
+
const fence = describeHostIdentity(input.environment, adapterId, observed.nativeSessionId);
|
|
65
92
|
await persistAndApply(input.home, [observation({
|
|
66
93
|
kind: "input.accepted", observedAt: observed.observedAt, sequence: nextStructuredSequence(), ordinal: 0,
|
|
67
94
|
fence: { ...fence, driverId: builtinAgentDriverRegistry().requireByAdapterId(adapterId).id,
|
|
68
95
|
conversationId: observed.conversationId, nativeTurnId: observed.nativeTurnId,
|
|
69
96
|
receiptId: `native-input:${observed.nativeTurnId}/${observed.inputId}` },
|
|
70
97
|
payload: { input: observed.input }
|
|
71
|
-
})],
|
|
98
|
+
})], input.environment);
|
|
72
99
|
}
|
|
73
100
|
export async function publishStructuredProviderStarted(input) {
|
|
74
101
|
if (input.started.clientOwned)
|
|
75
102
|
return;
|
|
76
103
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
77
104
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
78
|
-
const fence =
|
|
79
|
-
nativeTurnId: input.started.nativeTurnId,
|
|
80
|
-
sessionOnly: true
|
|
81
|
-
});
|
|
105
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.started.nativeSessionId);
|
|
82
106
|
const receiptId = `direct:${input.started.nativeTurnId}`;
|
|
83
107
|
const startedObservation = observation({
|
|
84
108
|
kind: "turn.accepted",
|
|
@@ -97,22 +121,16 @@ export async function publishStructuredProviderStarted(input) {
|
|
|
97
121
|
},
|
|
98
122
|
payload: input.started.input === undefined ? {} : { input: input.started.input }
|
|
99
123
|
});
|
|
100
|
-
await persistAndApply(input.home, [startedObservation],
|
|
124
|
+
await persistAndApply(input.home, [startedObservation], input.environment);
|
|
101
125
|
}
|
|
102
126
|
export async function publishStructuredProviderAccepted(input) {
|
|
103
127
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
104
128
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
105
|
-
const
|
|
106
|
-
const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.receipt.nativeSessionId, {
|
|
107
|
-
startupSession,
|
|
108
|
-
nativeTurnId: input.receipt.nativeTurnId,
|
|
109
|
-
attemptId: input.receipt.attemptId
|
|
110
|
-
});
|
|
129
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.receipt.nativeSessionId);
|
|
111
130
|
const observedAt = input.receipt.acceptedAt;
|
|
112
131
|
const commonFence = {
|
|
113
132
|
taskId: fence.taskId,
|
|
114
133
|
roleName: fence.roleName,
|
|
115
|
-
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
116
134
|
agentId: fence.agentId,
|
|
117
135
|
driverId: driver.id,
|
|
118
136
|
conversationId: input.receipt.conversationId,
|
|
@@ -136,18 +154,15 @@ export async function publishStructuredProviderAccepted(input) {
|
|
|
136
154
|
ordinal: 0,
|
|
137
155
|
fence: commonFence
|
|
138
156
|
})];
|
|
139
|
-
await persistAndApply(input.home, observations,
|
|
157
|
+
await persistAndApply(input.home, observations, input.environment);
|
|
140
158
|
}
|
|
141
159
|
/** Steer settles an additional input, never the parent AgentRun's initial delivery. */
|
|
142
160
|
export async function publishStructuredProviderInputSettlement(input) {
|
|
143
161
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
144
162
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
145
|
-
const fence =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
if (fence.runId === undefined)
|
|
150
|
-
throw new Error("Steer settlement has no exact original AgentRun.");
|
|
163
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
|
|
164
|
+
// The Host reports the exact Session/turn/attempt, including input with no
|
|
165
|
+
// AgentRun. Only the Controller resolves a current Run from durable state.
|
|
151
166
|
const entry = observation({
|
|
152
167
|
kind: input.status === "accepted" ? "input.accepted"
|
|
153
168
|
: input.status === "rejected" ? "input.rejected" : "input.delivery-unknown",
|
|
@@ -157,7 +172,6 @@ export async function publishStructuredProviderInputSettlement(input) {
|
|
|
157
172
|
fence: {
|
|
158
173
|
taskId: fence.taskId,
|
|
159
174
|
roleName: fence.roleName,
|
|
160
|
-
runId: fence.runId,
|
|
161
175
|
agentId: fence.agentId,
|
|
162
176
|
driverId: driver.id,
|
|
163
177
|
conversationId: input.nativeSessionId,
|
|
@@ -180,22 +194,20 @@ export async function publishStructuredProviderInputSettlement(input) {
|
|
|
180
194
|
})
|
|
181
195
|
}
|
|
182
196
|
});
|
|
183
|
-
await persistAndApply(input.home, [entry],
|
|
197
|
+
await persistAndApply(input.home, [entry], input.environment);
|
|
184
198
|
}
|
|
185
199
|
export async function publishStructuredProviderOpened(input) {
|
|
186
200
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
187
201
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
188
202
|
const startupSession = driver.capabilities.observation.sessionBootstrap;
|
|
189
|
-
const fence =
|
|
203
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
|
|
190
204
|
const commonFence = {
|
|
191
205
|
taskId: fence.taskId,
|
|
192
206
|
roleName: fence.roleName,
|
|
193
|
-
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
194
207
|
agentId: fence.agentId,
|
|
195
208
|
driverId: driver.id,
|
|
196
209
|
conversationId: input.conversationId,
|
|
197
|
-
nativeSessionId: input.nativeSessionId
|
|
198
|
-
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
210
|
+
nativeSessionId: input.nativeSessionId
|
|
199
211
|
};
|
|
200
212
|
const sequence = nextStructuredSequence();
|
|
201
213
|
const observations = [observation({
|
|
@@ -212,12 +224,12 @@ export async function publishStructuredProviderOpened(input) {
|
|
|
212
224
|
fence: commonFence,
|
|
213
225
|
payload: { recoverability: input.recoverability }
|
|
214
226
|
})];
|
|
215
|
-
await persistAndApply(input.home, observations,
|
|
227
|
+
await persistAndApply(input.home, observations, input.environment, undefined, input.startupRunId);
|
|
216
228
|
}
|
|
217
229
|
export async function publishStructuredProviderGoal(input) {
|
|
218
230
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
219
231
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
220
|
-
const fence =
|
|
232
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.conversationId);
|
|
221
233
|
const goal = input.goal;
|
|
222
234
|
const observedAt = input.observedAt ?? goal?.updatedAt ?? new Date().toISOString();
|
|
223
235
|
const goalObservation = observation({
|
|
@@ -241,22 +253,20 @@ export async function publishStructuredProviderGoal(input) {
|
|
|
241
253
|
...(goal.tokenBudget === undefined ? {} : { goalTokenBudget: goal.tokenBudget })
|
|
242
254
|
}
|
|
243
255
|
});
|
|
244
|
-
await persistAndApply(input.home, [goalObservation],
|
|
256
|
+
await persistAndApply(input.home, [goalObservation], input.environment);
|
|
245
257
|
}
|
|
246
258
|
export async function publishStructuredConversationRecoverability(input) {
|
|
247
259
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
248
260
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
249
|
-
const fence =
|
|
261
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.conversationId);
|
|
250
262
|
const sequence = nextStructuredSequence();
|
|
251
263
|
const observationFence = {
|
|
252
264
|
taskId: fence.taskId,
|
|
253
265
|
roleName: fence.roleName,
|
|
254
|
-
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
255
266
|
agentId: fence.agentId,
|
|
256
267
|
driverId: driver.id,
|
|
257
268
|
conversationId: input.conversationId,
|
|
258
|
-
nativeSessionId: input.conversationId
|
|
259
|
-
...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
|
|
269
|
+
nativeSessionId: input.conversationId
|
|
260
270
|
};
|
|
261
271
|
const observations = [observation({
|
|
262
272
|
kind: "conversation.observed",
|
|
@@ -266,17 +276,12 @@ export async function publishStructuredConversationRecoverability(input) {
|
|
|
266
276
|
fence: observationFence,
|
|
267
277
|
payload: { recoverability: input.recoverability }
|
|
268
278
|
})];
|
|
269
|
-
await persistAndApply(input.home, observations,
|
|
279
|
+
await persistAndApply(input.home, observations, input.environment);
|
|
270
280
|
}
|
|
271
281
|
export async function publishStructuredProviderTerminal(input) {
|
|
272
282
|
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
273
283
|
const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
|
|
274
|
-
const fence =
|
|
275
|
-
terminal: true,
|
|
276
|
-
nativeTurnId: input.terminal.nativeTurnId,
|
|
277
|
-
attemptId: input.terminal.attemptId,
|
|
278
|
-
...(input.terminal.clientOwned ? {} : { sessionOnly: true })
|
|
279
|
-
});
|
|
284
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.terminal.nativeSessionId);
|
|
280
285
|
const kind = input.terminal.status === "completed"
|
|
281
286
|
? "turn.completed"
|
|
282
287
|
: input.terminal.status === "cancelled" ? "turn.cancelled" : "turn.failed";
|
|
@@ -321,7 +326,6 @@ export async function publishStructuredProviderTerminal(input) {
|
|
|
321
326
|
fence: {
|
|
322
327
|
taskId: fence.taskId,
|
|
323
328
|
roleName: fence.roleName,
|
|
324
|
-
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
325
329
|
agentId: fence.agentId,
|
|
326
330
|
driverId: driver.id,
|
|
327
331
|
conversationId: input.terminal.conversationId,
|
|
@@ -329,9 +333,7 @@ export async function publishStructuredProviderTerminal(input) {
|
|
|
329
333
|
...(input.terminal.nativeTurnId === undefined ? {} : {
|
|
330
334
|
nativeTurnId: input.terminal.nativeTurnId
|
|
331
335
|
}),
|
|
332
|
-
...(input.terminal.attemptId === undefined
|
|
333
|
-
? fence.receiptId === undefined ? {} : { receiptId: fence.receiptId }
|
|
334
|
-
: { receiptId: input.terminal.attemptId })
|
|
336
|
+
...(input.terminal.attemptId === undefined ? {} : { receiptId: input.terminal.attemptId })
|
|
335
337
|
},
|
|
336
338
|
payload: {
|
|
337
339
|
...payload,
|
|
@@ -340,7 +342,7 @@ export async function publishStructuredProviderTerminal(input) {
|
|
|
340
342
|
...(input.terminal.input === undefined ? {} : { input: input.terminal.input })
|
|
341
343
|
}
|
|
342
344
|
});
|
|
343
|
-
await persistAndApply(input.home, [terminalObservation],
|
|
345
|
+
await persistAndApply(input.home, [terminalObservation], input.environment);
|
|
344
346
|
}
|
|
345
347
|
function observation(input) {
|
|
346
348
|
const eventId = `agent-host-${randomUUID()}`;
|
|
@@ -367,7 +369,8 @@ function observation(input) {
|
|
|
367
369
|
}
|
|
368
370
|
async function signalController(home, taskId, roleName) {
|
|
369
371
|
await callController(home, "scheduler.signal", {
|
|
370
|
-
key: runtimeLifecycleSignalKey(
|
|
372
|
+
key: runtimeLifecycleSignalKey(taskId === undefined
|
|
373
|
+
? { scope: "global", roleName } : { scope: "task", taskId, roleName })
|
|
371
374
|
}, { timeoutMs: 100 }).catch(() => { });
|
|
372
375
|
}
|
|
373
376
|
function nextStructuredSequence() {
|
|
@@ -380,20 +383,42 @@ function requireIdentity(value, label) {
|
|
|
380
383
|
}
|
|
381
384
|
return value.trim();
|
|
382
385
|
}
|
|
383
|
-
async function persistAndApply(home, observations,
|
|
386
|
+
async function persistAndApply(home, observations, environment, connection, startupRunId) {
|
|
384
387
|
const inbox = new FileRuntimeEventInbox(home);
|
|
385
|
-
|
|
386
|
-
|
|
388
|
+
const host = {
|
|
389
|
+
protocol: AGENT_HOST_EVENT_PROTOCOL,
|
|
390
|
+
adapterId: requireIdentity(environment.YUI_ADAPTER_ID, "Agent adapter id"),
|
|
391
|
+
workspace: requireIdentity(environment.YUI_WORKSPACE, "Yui workspace"),
|
|
392
|
+
...(connection === undefined ? {} : { connection })
|
|
393
|
+
};
|
|
394
|
+
let events;
|
|
395
|
+
try {
|
|
396
|
+
structuredProviderEventDelivery(home, environment); // prune ACKed local diagnostics
|
|
397
|
+
events = observations.map(entry => inbox.enqueueObservation(entry, {
|
|
398
|
+
...host,
|
|
399
|
+
...(startupRunId === undefined ? {} : { startupRunId })
|
|
400
|
+
}).event);
|
|
401
|
+
for (const event of events)
|
|
402
|
+
emittedEvents.set(event.id, {
|
|
403
|
+
home, taskId: event.taskId, roleName: event.observation.fence.roleName,
|
|
404
|
+
nativeSessionId: event.observation.fence.nativeSessionId,
|
|
405
|
+
terminal: ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
deliveryFailure = deliveryFault("persistence", error);
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
387
412
|
// The immutable inbox is authoritative. During a release/update handover the
|
|
388
413
|
// old Controller is draining and the replacement is not ready yet; leave the
|
|
389
414
|
// entries for normal inbox replay instead of turning a healthy Provider Turn
|
|
390
415
|
// into a transport failure.
|
|
391
416
|
if (isForeignHandoverLockHeld(home))
|
|
392
417
|
return;
|
|
393
|
-
for (const entry of
|
|
418
|
+
for (const entry of events) {
|
|
394
419
|
let result;
|
|
395
420
|
try {
|
|
396
|
-
result = await callController(home, "runtime.observation-apply", entry, {
|
|
421
|
+
result = await callController(home, "runtime.host-observation-apply", { eventId: entry.id }, {
|
|
397
422
|
// This is only an eager application hint after durable enqueue, not
|
|
398
423
|
// native admission. Bound it like the wake signal so a frozen
|
|
399
424
|
// Controller cannot serialize ten seconds of delay per tool event.
|
|
@@ -409,7 +434,10 @@ async function persistAndApply(home, observations, taskId, roleName) {
|
|
|
409
434
|
&& ["CONTROLLER_NOT_RUNNING", "CONTROLLER_UNAVAILABLE", "CONTROLLER_TIMEOUT",
|
|
410
435
|
"CONTROLLER_DELIVERY_UNKNOWN"].includes(error.code)))
|
|
411
436
|
return;
|
|
412
|
-
|
|
437
|
+
// The native fact is already durable. An incompatible/refusing
|
|
438
|
+
// Controller is a reporting fault, not Provider execution failure.
|
|
439
|
+
deliveryFailure = deliveryFault("controller", error);
|
|
440
|
+
return;
|
|
413
441
|
}
|
|
414
442
|
// A fast Provider can accept the initial AgentRun before the scheduler call
|
|
415
443
|
// that launched this Host has returned and committed `turn.pushed`. The
|
|
@@ -417,8 +445,43 @@ async function persistAndApply(home, observations, taskId, roleName) {
|
|
|
417
445
|
// `deferred` therefore means "retained for replay", not delivery failure.
|
|
418
446
|
// The signal below schedules the replay after the transport transaction.
|
|
419
447
|
if (result.outcome !== "applied" && result.outcome !== "deferred") {
|
|
420
|
-
|
|
448
|
+
deliveryFailure = deliveryFault("controller", `Structured Provider observation was rejected: ${result.outcome ?? "unknown"}. Event: ${entry.id}`);
|
|
449
|
+
return;
|
|
421
450
|
}
|
|
422
451
|
}
|
|
423
|
-
|
|
452
|
+
if (deliveryFailure?.stage === "controller")
|
|
453
|
+
deliveryFailure = undefined;
|
|
454
|
+
await signalController(home, hostTaskId(environment), requireIdentity(environment.YUI_ROLE, "Role"));
|
|
455
|
+
}
|
|
456
|
+
/** Capture only identities the Host actually received; the current Controller
|
|
457
|
+
* owns all TaskStore reads and resolves the exact attempt's Run at consumption. */
|
|
458
|
+
function describeHostIdentity(environment, adapterId, nativeSessionId) {
|
|
459
|
+
if (environment.YUI_ADAPTER_ID !== adapterId)
|
|
460
|
+
throw new Error("Agent adapter id does not match.");
|
|
461
|
+
return {
|
|
462
|
+
taskId: hostTaskId(environment),
|
|
463
|
+
roleName: requireIdentity(environment.YUI_ROLE, "Role name"),
|
|
464
|
+
agentId: requireIdentity(environment.YUI_AGENT_ID, "Agent id"),
|
|
465
|
+
nativeSessionId: requireIdentity(nativeSessionId, "Native Session id")
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function hostTaskId(environment) {
|
|
469
|
+
if (environment.YUI_SESSION_SCOPE === "global") {
|
|
470
|
+
if (environment.YUI_TASK_ID !== undefined)
|
|
471
|
+
throw new Error("Global Host must not carry a Task id.");
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
if (environment.YUI_SESSION_SCOPE !== "task")
|
|
475
|
+
throw new Error("Agent Host observation requires Task scope.");
|
|
476
|
+
return requireIdentity(environment.YUI_TASK_ID, "Task id");
|
|
477
|
+
}
|
|
478
|
+
export async function publishStructuredProviderConnection(input) {
|
|
479
|
+
const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
|
|
480
|
+
const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
|
|
481
|
+
await persistAndApply(input.home, [observation({
|
|
482
|
+
kind: "host.observed", observedAt: new Date().toISOString(),
|
|
483
|
+
authority: "host", payload: { alive: true },
|
|
484
|
+
sequence: nextStructuredSequence(), ordinal: 0,
|
|
485
|
+
fence: { ...fence, driverId: builtinAgentDriverRegistry().requireByAdapterId(adapterId).id }
|
|
486
|
+
})], input.environment, input.connection, input.startupRunId);
|
|
424
487
|
}
|
|
@@ -9,6 +9,11 @@ const LEGACY_ROLE_RUN_DISPATCH_REASONS = new Set([
|
|
|
9
9
|
/** Atomically useful when called inside the caller's TaskStore transaction. */
|
|
10
10
|
export function enqueueWork(store, target, reason, occurredAt, refs = [], metadata = {}) {
|
|
11
11
|
const mailbox = store.getWorkMailbox(target) ?? createWorkMailbox(target);
|
|
12
|
+
// Archive is an admission boundary, not an acknowledgement of old input.
|
|
13
|
+
// Runtime cleanup mailboxes remain usable for exact-owner recovery.
|
|
14
|
+
if ((target.kind === "task" || target.kind === "role")
|
|
15
|
+
&& store.getTask?.(target.taskId)?.status === "archived")
|
|
16
|
+
return mailbox;
|
|
12
17
|
const queued = enqueueSignal(mailbox, {
|
|
13
18
|
reason,
|
|
14
19
|
refs,
|
|
@@ -278,7 +278,7 @@ function projectNextAction(item, lanes, synthesis, mainRun, candidate) {
|
|
|
278
278
|
return action("inspect-unknown", ["leader"], mainRun.runId === undefined ? [item.id] : [mainRun.runId]);
|
|
279
279
|
}
|
|
280
280
|
if (item.status === "open") {
|
|
281
|
-
return action("dispatch-work", ["leader"], [item.id]);
|
|
281
|
+
return action(item.assignee === undefined ? "execute-directly" : "dispatch-work", ["leader"], [item.id]);
|
|
282
282
|
}
|
|
283
283
|
return action("inspect-unknown", ["leader"], [item.id]);
|
|
284
284
|
}
|