@zq-silk/yui 2.20.0 → 2.22.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 -0
- package/dist/cli/commandCatalog.js +88 -28
- package/dist/cli/dynamicCompletion.js +4 -1
- package/dist/cli/helpRenderer.js +12 -1
- package/dist/cli/interactionPolicy.js +2 -0
- package/dist/cli/invocationAuthority.js +2 -6
- package/dist/cli/invocationRouter.js +5 -5
- package/dist/cli/managedDiagnostics.js +24 -16
- package/dist/cli/roleWizard.js +5 -1
- package/dist/commands/durableJobCommands.js +2 -3
- package/dist/commands/globalRoleCommands.js +33 -8
- package/dist/commands/inboxCommands.js +39 -12
- package/dist/commands/operatorCommands.js +2 -5
- package/dist/commands/releaseCommands.js +16 -2
- package/dist/commands/roleConfiguration.js +25 -2
- package/dist/commands/sessionCommands.js +5 -2
- package/dist/commands/taskCommands.js +225 -99
- package/dist/commands/taskContextCommand.js +26 -4
- package/dist/commands/taskFactCommands.js +20 -7
- package/dist/commands/taskIntegrationCommands.js +3 -3
- package/dist/commands/taskRoleRuntimeStatus.js +15 -5
- package/dist/commands/taskUsageCommand.js +2 -6
- package/dist/commands/textInput.js +2 -2
- package/dist/context/contextFamilies.js +10 -0
- package/dist/context/currentSkills.js +6 -4
- package/dist/context/runContextPack.js +44 -5
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +7 -5
- package/dist/context/sessionSkillNotice.js +53 -0
- package/dist/context/taskCatalog.js +4 -13
- package/dist/context/taskContext.js +135 -103
- package/dist/context/taskSearch.js +3 -7
- package/dist/controlPlaneCli.js +23 -25
- package/dist/controller/agentRuntimeObserver.js +57 -12
- package/dist/controller/clientRuntime.js +37 -8
- package/dist/controller/controller.js +3 -1
- package/dist/controller/diagnosticJournal.js +10 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +72 -24
- package/dist/controller/jobClient.js +2 -2
- package/dist/controller/jobControl.js +2 -2
- package/dist/controller/jobDefinitionControl.js +5 -1
- package/dist/controller/messageHealth.js +4 -3
- package/dist/controller/providerRetryAdmission.js +15 -1
- package/dist/controller/providerRetryDelivery.js +18 -2
- package/dist/controller/runtime.js +15 -5
- package/dist/controller/runtimeEventInbox.js +107 -13
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/controller/taskRunRecovery.js +153 -0
- package/dist/core/controllerClient.js +16 -2
- package/dist/core/controllerServer.js +14 -5
- package/dist/core/controllerTelemetry.js +51 -5
- package/dist/errors/cliFailure.js +14 -5
- package/dist/executor/agentAdapter.js +32 -4
- package/dist/executor/agentConfigurationFields.js +2 -0
- package/dist/executor/taskAgentCapabilities.js +2 -2
- package/dist/integration/gitIntegrationService.js +3 -2
- package/dist/integration/integrationSourceApplication.js +12 -1
- package/dist/interaction/notificationPresentation.js +11 -16
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/messageContinuation.js +3 -1
- package/dist/output/boundedRead.js +36 -7
- package/dist/output/rolePresentation.js +3 -1
- package/dist/runtime/agentHost.js +12 -3
- package/dist/runtime/codexAppServerRuntime.js +110 -24
- package/dist/runtime/codexInteractiveHost.js +46 -0
- package/dist/runtime/providerRetry.js +5 -2
- package/dist/runtime/providerRuntimeIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +32 -34
- package/dist/scheduler/wakeupQueue.js +10 -4
- package/dist/storage/sqliteSchema.js +9 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/task/taskAuthority.js +7 -3
- package/dist/tmux/tmuxManager.js +4 -2
- package/dist/web/webSettings.js +2 -0
- package/docs/authorization.md +18 -3
- package/docs/cli-information-contract.md +245 -6
- package/docs/cli-information-contract.zh-CN.md +107 -5
- package/docs/managed-turn-and-session-runtime.md +82 -21
- package/docs/managed-turn-and-session-runtime.zh-CN.md +56 -13
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +62 -0
- package/docs/release-workflow.zh-CN.md +46 -0
- package/docs/roles-and-configuration.md +28 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/storage-baseline.md +4 -1
- package/docs/storage-baseline.zh-CN.md +1 -1
- package/docs/task-delivery.md +7 -0
- package/docs/task-delivery.zh-CN.md +5 -0
- package/docs/task-discovery.md +5 -5
- package/docs/task-discovery.zh-CN.md +4 -4
- package/docs/testing/review-evaluation.md +7 -7
- package/docs/testing/review-evaluation.zh-CN.md +7 -7
- package/docs/testing/verification-levels.md +45 -2
- package/docs/testing/verification-levels.zh-CN.md +21 -2
- package/i18n/README.zh-CN.md +6 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +98 -189
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/artifacts.md +41 -0
- package/skills/yui-leader/references/assignment-execution.md +140 -0
- package/skills/yui-leader/references/completion.md +79 -0
- package/skills/yui-leader/references/delivery-scope.md +26 -0
- package/skills/yui-leader/references/execution.md +16 -487
- package/skills/yui-leader/references/implementation.md +81 -0
- package/skills/yui-leader/references/planning.md +7 -45
- package/skills/yui-leader/references/review.md +90 -0
- package/skills/yui-leader/references/task-progress.md +23 -0
- package/skills/yui-leader/references/work-allocation.md +118 -0
- package/skills/yui-operator/SKILL.md +77 -331
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-operator/references/body-transport.md +62 -0
- package/skills/yui-operator/references/configuration.md +47 -0
- package/skills/yui-operator/references/lifecycle.md +41 -0
- package/skills/yui-operator/references/progress.md +39 -0
- package/skills/yui-operator/references/routing.md +57 -0
- package/skills/yui-operator/references/submission.md +56 -0
- package/skills/yui-operator/references/task-delivery.md +5 -2
- package/skills/yui-operator/references/web-lifecycle.md +19 -0
- package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
- package/skills/yui-reviewer/SKILL.md +4 -2
- package/skills/yui-reviewer/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +177 -505
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/archive.md +51 -0
- package/skills/yui-runtime/references/command-discovery.md +41 -0
- package/skills/yui-runtime/references/completed-followup.md +65 -0
- package/skills/yui-runtime/references/context-ordering.md +3 -34
- package/skills/yui-runtime/references/context-reads.md +61 -0
- package/skills/yui-runtime/references/dispatched-run.md +61 -0
- package/skills/yui-runtime/references/execution-failure.md +105 -0
- package/skills/yui-runtime/references/global-context.md +18 -0
- package/skills/yui-runtime/references/global-input-controls.md +44 -0
- package/skills/yui-runtime/references/host-custody.md +30 -0
- package/skills/yui-runtime/references/inbox-recovery.md +77 -0
- package/skills/yui-runtime/references/incremental-context.md +40 -0
- package/skills/yui-runtime/references/publication.md +4 -104
- package/skills/yui-runtime/references/recovery.md +32 -192
- package/skills/yui-runtime/references/session-identity.md +18 -0
- package/skills/yui-runtime/references/session-replacement.md +45 -0
- package/skills/yui-runtime/references/uncertain-execution.md +29 -0
- package/skills/yui-worker/SKILL.md +7 -4
- package/skills/yui-worker/VERSION +1 -1
|
@@ -15,14 +15,45 @@ export class AgentRuntimeObserver {
|
|
|
15
15
|
#states = new Map();
|
|
16
16
|
#maxConcurrentSamples;
|
|
17
17
|
#sequence = 0;
|
|
18
|
+
#samples = 0;
|
|
19
|
+
#sources = 0;
|
|
20
|
+
#published = 0;
|
|
21
|
+
#usagePublications = 0;
|
|
22
|
+
#failed = 0;
|
|
18
23
|
constructor(store, inbox, drivers = builtinAgentDriverRegistry(), options = {}) {
|
|
19
24
|
this.store = store;
|
|
20
25
|
this.inbox = inbox;
|
|
21
26
|
this.drivers = drivers;
|
|
22
27
|
this.#maxConcurrentSamples = sampleConcurrency(options.maxConcurrentSamples);
|
|
23
28
|
}
|
|
24
|
-
async sample(now = new Date()) {
|
|
25
|
-
|
|
29
|
+
async sample(now = new Date(), phases) {
|
|
30
|
+
this.#samples++;
|
|
31
|
+
const finishDiscovery = phases?.start("runtime-sample-discovery", "home");
|
|
32
|
+
let active;
|
|
33
|
+
try {
|
|
34
|
+
active = this.activeSources();
|
|
35
|
+
finishDiscovery?.();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
finishDiscovery?.(true);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
this.#sources += active.length;
|
|
42
|
+
const publish = async (observation) => {
|
|
43
|
+
try {
|
|
44
|
+
const result = await (phases === undefined
|
|
45
|
+
? this.inbox.enqueueObservationAsync(observation)
|
|
46
|
+
: phases.observe(observation.payload.usage === undefined ? "runtime-sample-publish" : "runtime-usage-publish", undefined, () => this.inbox.enqueueObservationAsync(observation)));
|
|
47
|
+
this.#published++;
|
|
48
|
+
if (observation.payload.usage !== undefined)
|
|
49
|
+
this.#usagePublications++;
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.#failed++;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
26
57
|
const activeKeys = new Set(active.map(({ key }) => key));
|
|
27
58
|
for (const key of this.#states.keys()) {
|
|
28
59
|
if (!activeKeys.has(key))
|
|
@@ -37,7 +68,10 @@ export class AgentRuntimeObserver {
|
|
|
37
68
|
// usage/activity baseline is durable. Rehydrate it after Controller
|
|
38
69
|
// restart so rereading the bounded transcript tail cannot manufacture a
|
|
39
70
|
// fresh activity edge from tokens that were already observed.
|
|
40
|
-
|
|
71
|
+
// Commit process-local state only when every fact from this sample is
|
|
72
|
+
// durable. A failed later publication must leave the old cursor usable;
|
|
73
|
+
// retry deduplicates any already-persisted prefix.
|
|
74
|
+
const state = { ...(existingState ?? persistedState) };
|
|
41
75
|
const restoredUsageEventId = existingState === undefined
|
|
42
76
|
? persistedState.usageEventId
|
|
43
77
|
: undefined;
|
|
@@ -63,7 +97,6 @@ export class AgentRuntimeObserver {
|
|
|
63
97
|
detail: error instanceof Error ? error.message : String(error)
|
|
64
98
|
});
|
|
65
99
|
}
|
|
66
|
-
state.cursor = sample.cursor;
|
|
67
100
|
const at = now.toISOString();
|
|
68
101
|
// Reserve sequence numbers in source-key order before sampling. Provider
|
|
69
102
|
// latency can change completion order without changing observation
|
|
@@ -101,7 +134,7 @@ export class AgentRuntimeObserver {
|
|
|
101
134
|
const identity = completeHistory
|
|
102
135
|
? tokenObservationIdentity("baseline", fence, source.sourceId, baselineKey)
|
|
103
136
|
: usageObservationIdentity(fence, source.sourceId, latestOccurrence);
|
|
104
|
-
|
|
137
|
+
await publish(createRuntimeObservation({
|
|
105
138
|
schemaVersion: 1,
|
|
106
139
|
eventId: identity.eventId,
|
|
107
140
|
semanticKey: identity.semanticKey,
|
|
@@ -136,7 +169,7 @@ export class AgentRuntimeObserver {
|
|
|
136
169
|
}
|
|
137
170
|
const health = JSON.stringify([sample.status, sample.detail ?? null]);
|
|
138
171
|
if (state.health !== health) {
|
|
139
|
-
|
|
172
|
+
await publish(createRuntimeObservation({
|
|
140
173
|
schemaVersion: 1,
|
|
141
174
|
eventId: observationId("health", fence, source.sourceId, health),
|
|
142
175
|
semanticKey: observationId("health", fence, source.sourceId, health),
|
|
@@ -180,9 +213,9 @@ export class AgentRuntimeObserver {
|
|
|
180
213
|
: Object.freeze({ ...occurrence, observationQuality: "partial" })));
|
|
181
214
|
}
|
|
182
215
|
}
|
|
183
|
-
usages.
|
|
216
|
+
for (const [usageIndex, occurrence] of usages.entries()) {
|
|
184
217
|
const identity = usageObservationIdentity(fence, source.sourceId, occurrence);
|
|
185
|
-
|
|
218
|
+
await publish(createRuntimeObservation({
|
|
186
219
|
schemaVersion: 1,
|
|
187
220
|
eventId: identity.eventId,
|
|
188
221
|
semanticKey: identity.semanticKey,
|
|
@@ -210,9 +243,9 @@ export class AgentRuntimeObserver {
|
|
|
210
243
|
state.usageOccurrenceId = occurrence.occurrenceId;
|
|
211
244
|
state.usageOccurrenceCheckpoint = occurrence.resumeCheckpoint;
|
|
212
245
|
}
|
|
213
|
-
}
|
|
214
|
-
if (activityChanged &&
|
|
215
|
-
|
|
246
|
+
}
|
|
247
|
+
if (activityChanged && sample.cursor !== undefined) {
|
|
248
|
+
await publish(createRuntimeObservation({
|
|
216
249
|
schemaVersion: 1,
|
|
217
250
|
eventId: observationId("activity", fence, source.sourceId, sample.activityId),
|
|
218
251
|
semanticKey: observationId("activity", fence, source.sourceId, sample.activityId),
|
|
@@ -232,10 +265,17 @@ export class AgentRuntimeObserver {
|
|
|
232
265
|
}
|
|
233
266
|
if (sample.activityId !== undefined)
|
|
234
267
|
state.activityId = sample.activityId;
|
|
268
|
+
state.cursor = sample.cursor;
|
|
235
269
|
this.#states.set(key, state);
|
|
236
270
|
});
|
|
237
271
|
return Object.freeze([...dirty].sort(numericCompare));
|
|
238
272
|
}
|
|
273
|
+
/** Counts successful durable admissions (including duplicates), not tokens or
|
|
274
|
+
* provider content. Phase observations carry discovery/publication duration. */
|
|
275
|
+
metrics() {
|
|
276
|
+
return { samples: this.#samples, sources: this.#sources, published: this.#published,
|
|
277
|
+
usagePublications: this.#usagePublications, failed: this.#failed };
|
|
278
|
+
}
|
|
239
279
|
activeSources() {
|
|
240
280
|
const result = [];
|
|
241
281
|
const activeTasks = [...new Set(this.store.listActiveTaskIds())]
|
|
@@ -354,13 +394,18 @@ export class AgentRuntimeObserver {
|
|
|
354
394
|
async function forEachConcurrent(values, limit, visit) {
|
|
355
395
|
let nextIndex = 0;
|
|
356
396
|
const workers = Math.min(limit, values.length);
|
|
357
|
-
await Promise.
|
|
397
|
+
const results = await Promise.allSettled(Array.from({ length: workers }, async () => {
|
|
358
398
|
while (nextIndex < values.length) {
|
|
359
399
|
const index = nextIndex;
|
|
360
400
|
nextIndex += 1;
|
|
361
401
|
await visit(values[index], index);
|
|
362
402
|
}
|
|
363
403
|
}));
|
|
404
|
+
// An admission failure must not release the Controller's in-flight sample
|
|
405
|
+
// guard while another source can still commit state from that sample.
|
|
406
|
+
const failure = results.find(result => result.status === "rejected");
|
|
407
|
+
if (failure?.status === "rejected")
|
|
408
|
+
throw failure.reason;
|
|
364
409
|
}
|
|
365
410
|
function sampleConcurrency(value) {
|
|
366
411
|
const resolved = value ?? DEFAULT_MAX_CONCURRENT_SAMPLES;
|
|
@@ -2,7 +2,8 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { jobCleanupBlocker } from "../job/jobCleanupInspection.js";
|
|
3
3
|
import { describeCliFailure } from "../errors/cliFailure.js";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { findLiveControllerProcessForHome
|
|
5
|
+
import { findLiveControllerProcessForHome } from "../core/controllerProcessIdentity.js";
|
|
6
|
+
import { processGenerationIsLive } from "../core/fileLockOwner.js";
|
|
6
7
|
import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
|
|
7
8
|
import { parseControllerIdentity } from "../core/controllerIdentity.js";
|
|
8
9
|
import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
|
|
@@ -12,6 +13,7 @@ import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
|
|
|
12
13
|
import { WorkspaceCleanupBlockedError } from "../repository/taskWorkspacePreparer.js";
|
|
13
14
|
import { hasRuntimeLifecycleWork } from "../runtime/lifecycleReservation.js";
|
|
14
15
|
import { assertControllerStatusIdentity } from "../runtime/runtimeCoherence.js";
|
|
16
|
+
import { readLinuxProcessIdentity } from "../runtime/sessionOwnerIdentity.js";
|
|
15
17
|
import { CONTROLLER_SHUTDOWN_TIMEOUT_MS, LIFECYCLE_REQUEST_TIMEOUT_MS } from "../runtime/runtimeDeadlines.js";
|
|
16
18
|
import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
17
19
|
import { yuiVersionIdentity } from "../version.js";
|
|
@@ -169,13 +171,27 @@ export async function stopFileTaskController(home, options = {}) {
|
|
|
169
171
|
await waitForForeignHandover(home, options);
|
|
170
172
|
const physical = options.call === undefined
|
|
171
173
|
? findLiveControllerProcessForHome(readHomeFilesystemId(home)) : undefined;
|
|
172
|
-
|
|
174
|
+
// A protected, Home-validated discovery can retain the generation fence
|
|
175
|
+
// even if the optional /proc ownership inventory is currently unreadable.
|
|
176
|
+
// It is not signal authority; physical recovery still revalidates ownership.
|
|
177
|
+
let captured = physical;
|
|
178
|
+
if (captured === undefined && options.call === undefined) {
|
|
179
|
+
try {
|
|
180
|
+
const discovery = await readControllerDiscovery(home);
|
|
181
|
+
captured = { pid: discovery.pid, processStartIdentity: discovery.processStartIdentity };
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (!isDefinitelyNotRunning(error))
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const expectedPid = options.expectedPid ?? captured?.pid;
|
|
173
189
|
const bounded = { ...options, requestTimeoutMs: options.requestTimeoutMs ?? 2000,
|
|
174
190
|
...(expectedPid === undefined ? {} : { expectedPid }) };
|
|
175
191
|
try {
|
|
176
192
|
const result = await stopControllerGracefully(home, bounded);
|
|
177
|
-
if (result.stopped &&
|
|
178
|
-
await waitForControllerProcessExit(
|
|
193
|
+
if (result.stopped && captured !== undefined) {
|
|
194
|
+
await waitForControllerProcessExit(captured, options);
|
|
179
195
|
}
|
|
180
196
|
if (result.stopped || options.call !== undefined)
|
|
181
197
|
return result;
|
|
@@ -185,15 +201,28 @@ export async function stopFileTaskController(home, options = {}) {
|
|
|
185
201
|
throw error;
|
|
186
202
|
}
|
|
187
203
|
const stopped = await stopOrphanedFileTaskController(home, options.shutdownTimeoutMs ?? CONTROLLER_SHUTDOWN_TIMEOUT_MS, { force: true, ...(expectedPid === undefined ? {} : { expectedPid }),
|
|
188
|
-
...(
|
|
204
|
+
...(captured === undefined ? {} : { expectedProcessStartIdentity: captured.processStartIdentity }) });
|
|
205
|
+
// No match is not exit proof: /proc ownership inspection may have become
|
|
206
|
+
// unreadable after we captured this exact generation. Physical recovery
|
|
207
|
+
// cannot authorize a successor until that original generation is gone.
|
|
208
|
+
if (captured !== undefined)
|
|
209
|
+
await waitForControllerProcessExit(captured, options);
|
|
189
210
|
return stopped === undefined ? { stopped: false, alreadyStopped: true } : { stopped: true, pid: stopped.pid };
|
|
190
211
|
}
|
|
191
|
-
async function waitForControllerProcessExit(
|
|
212
|
+
async function waitForControllerProcessExit(controller, options) {
|
|
192
213
|
const timeoutMs = positive(options.shutdownTimeoutMs, CONTROLLER_SHUTDOWN_TIMEOUT_MS, "shutdownTimeoutMs");
|
|
193
214
|
const pollMs = positive(options.pollIntervalMs, POLL_INTERVAL_MS, "pollIntervalMs");
|
|
194
|
-
const homeFilesystemId = readHomeFilesystemId(home);
|
|
195
215
|
const deadline = Date.now() + timeoutMs;
|
|
196
|
-
|
|
216
|
+
// This check is tri-state: unreadable OS identity throws rather than
|
|
217
|
+
// collapsing an unknown/live process into the absence of a Controller match.
|
|
218
|
+
while (processGenerationIsLive(controller.pid, controller.processStartIdentity)) {
|
|
219
|
+
if (process.platform === "linux") {
|
|
220
|
+
const observed = readLinuxProcessIdentity(controller.pid);
|
|
221
|
+
// Preserve the existing physical-stop boundary: an exact zombie has
|
|
222
|
+
// exited, even if its parent has not reaped the process table entry.
|
|
223
|
+
if (observed?.startIdentity === controller.processStartIdentity && observed.state === "Z")
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
197
226
|
if (Date.now() >= deadline) {
|
|
198
227
|
throw new ControllerClientError("CONTROLLER_TIMEOUT", `Controller process ${controller.pid} did not exit within ${timeoutMs} ms.`);
|
|
199
228
|
}
|
|
@@ -792,6 +792,7 @@ export class FileTaskController {
|
|
|
792
792
|
runtimeMetrics() {
|
|
793
793
|
const metrics = this.#lastRuntimeDrain?.metrics ?? ZERO_DRAIN_METRICS;
|
|
794
794
|
return {
|
|
795
|
+
sampler: this.#runtimeObserver?.metrics?.() ?? null,
|
|
795
796
|
messageHealth: this.#lastMessageHealth,
|
|
796
797
|
diagnostics: this.#diagnosticMetrics?.() ?? null,
|
|
797
798
|
phases: this.#phases.snapshot(),
|
|
@@ -920,7 +921,7 @@ export class FileTaskController {
|
|
|
920
921
|
if (this.#stopped || this.#runtimeObserver === undefined
|
|
921
922
|
|| this.#runtimeObserverCurrent !== undefined)
|
|
922
923
|
return;
|
|
923
|
-
const running = this.#runtimeObserver.sample(this.#now()).then((keys) => {
|
|
924
|
+
const running = this.#phases.observe("runtime-sample", "home", () => this.#runtimeObserver.sample(this.#now(), this.#phases)).then((keys) => {
|
|
924
925
|
if (this.#stopped)
|
|
925
926
|
return;
|
|
926
927
|
for (const key of keys)
|
|
@@ -1779,6 +1780,7 @@ export async function startFileTaskController(home, store, delivery, dispatcher,
|
|
|
1779
1780
|
await runtime.shutdownAndDrain();
|
|
1780
1781
|
}, {
|
|
1781
1782
|
domainIdentity: options.domainIdentity,
|
|
1783
|
+
onRequestDiagnostic: options.onRequestDiagnostic,
|
|
1782
1784
|
status: ({ commandObserver, eventLoopDelay }) => ({
|
|
1783
1785
|
...runtime.runtimeMetrics(),
|
|
1784
1786
|
commands: {
|
|
@@ -13,13 +13,15 @@ const NUMERIC_KEYS = new Set([
|
|
|
13
13
|
"completed", "failed", "received", "inFlight", "maximumMs", "maximumLagMs",
|
|
14
14
|
"maximumObservedAtMs", "oldestInFlightAgeMs", "startedAtMs", "elapsedMs",
|
|
15
15
|
"ageMs", "depth", "semanticDepth", "progressDepth", "pendingKeys", "queueDepth",
|
|
16
|
-
"hits", "rebuilds", "invalidations", "passes", "listedEvents", "selectedEvents"
|
|
16
|
+
"hits", "rebuilds", "invalidations", "passes", "listedEvents", "selectedEvents",
|
|
17
|
+
"acceptedAtMs", "receivedAtMs", "routedAtMs", "handlerCompletedAtMs", "flushCompletedAtMs",
|
|
18
|
+
"handlerFailed", "flushFailed", "samples", "sources", "published", "usagePublications"
|
|
17
19
|
]);
|
|
18
20
|
const CONTAINER_KEYS = new Set([
|
|
19
21
|
"commands", "observations", "routes", "eventLoopDelay", "phases", "reads",
|
|
20
22
|
"persistence", "main", "worker", "database", "roundTrips", "work", "body",
|
|
21
23
|
"lock", "commit", "payloadReads", "inbox", "scheduler", "projectionCache",
|
|
22
|
-
"active", "recent", "slowest", "latest"
|
|
24
|
+
"active", "recent", "slowest", "latest", "transport", "sampler"
|
|
23
25
|
]);
|
|
24
26
|
const ERROR_CODES = new Set([
|
|
25
27
|
"SQLITE_BUSY", "SQLITE_LOCKED", "SQLITE_IOERR", "SQLITE_FULL", "SQLITE_READONLY",
|
|
@@ -101,6 +103,12 @@ export class ControllerDiagnosticJournal {
|
|
|
101
103
|
error(error) {
|
|
102
104
|
this.#enqueue("error", { error: errorEvidence(error) });
|
|
103
105
|
}
|
|
106
|
+
/** Completion-driven evidence bypasses the periodic recent-array/sample
|
|
107
|
+
* window, but uses the same allowlist, queue/record/file bounds and async I/O.
|
|
108
|
+
* Disk failure, queue pressure and force-kill remain explicit evidence gaps. */
|
|
109
|
+
request(record) {
|
|
110
|
+
this.#enqueue("request", { request: safeSnapshot(record) });
|
|
111
|
+
}
|
|
104
112
|
sample(snapshot, lagMs) {
|
|
105
113
|
const now = monotonicMilliseconds();
|
|
106
114
|
if (now - this.#lastSampleAt < 1000) {
|
|
@@ -43,7 +43,7 @@ import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
|
43
43
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
44
44
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
45
45
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
46
|
-
import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
46
|
+
import { acceptProviderTurn, beginProviderTurn, cancelQuiescentProviderInput, clearProviderGoal, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, providerTurnIsActive, settleProviderTurn, settleProviderTurnSubmission, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
47
47
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
48
48
|
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeUsageEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
49
49
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
@@ -106,11 +106,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
106
106
|
return errors;
|
|
107
107
|
}
|
|
108
108
|
observeAgentHostObservation(event, now = new Date()) {
|
|
109
|
+
let preparation;
|
|
110
|
+
if (isRunTerminalObservation(event.observation)) {
|
|
111
|
+
preparation = {};
|
|
112
|
+
try {
|
|
113
|
+
preparation = this.prepareRuntimeTerminalObservation(resolveAgentHostObservation(this.store, event), now);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (!(error instanceof AgentHostObservationDeferred || error instanceof RuntimeHookRunFenceError))
|
|
117
|
+
throw error;
|
|
118
|
+
// Re-resolve under the committing fence below; preparation grants no
|
|
119
|
+
// authority and must not settle a deferred/obsolete Host envelope.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
109
122
|
// Match canonical ingress: archive and Host fact settlement share the
|
|
110
123
|
// same write boundary, including the original transport envelope.
|
|
111
|
-
return this.store.transaction(() => this.foldAgentHostObservation(event, now));
|
|
124
|
+
return this.store.transaction(() => this.foldAgentHostObservation(event, now, preparation));
|
|
112
125
|
}
|
|
113
|
-
foldAgentHostObservation(event, now) {
|
|
126
|
+
foldAgentHostObservation(event, now, preparation) {
|
|
114
127
|
const raw = createRuntimeObservation(event.observation);
|
|
115
128
|
const task = raw.fence.taskId === undefined ? null : this.store.getTask(raw.fence.taskId);
|
|
116
129
|
if (raw.fence.taskId !== undefined && task === null)
|
|
@@ -154,7 +167,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
154
167
|
// Rejection is durable diagnostic evidence, never business acceptance.
|
|
155
168
|
return this.recordObsoleteCanonicalObservation(event.observation, error.message, now);
|
|
156
169
|
}
|
|
157
|
-
return this.
|
|
170
|
+
return this.foldRuntimeObservation(input, now, preparation);
|
|
158
171
|
}
|
|
159
172
|
/**
|
|
160
173
|
* Sole provider-independent ingress for structured runtime state. Driver
|
|
@@ -163,12 +176,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
163
176
|
* transitions, and retains the canonical observation for status projection.
|
|
164
177
|
*/
|
|
165
178
|
observeRuntimeObservation(raw, now = new Date()) {
|
|
179
|
+
const preparation = this.prepareRuntimeTerminalObservation(raw, now);
|
|
166
180
|
// Archive and observation settlement share the same write boundary.
|
|
167
181
|
// An ingress read outside this transaction must never authorize clearing
|
|
168
182
|
// a claim after another CLI has committed archive.
|
|
169
|
-
return this.store.transaction(() => this.foldRuntimeObservation(raw, now));
|
|
183
|
+
return this.store.transaction(() => this.foldRuntimeObservation(raw, now, preparation));
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
prepareRuntimeTerminalObservation(raw, now) {
|
|
186
|
+
const input = createRuntimeObservation(raw);
|
|
187
|
+
if (!isRunTerminalObservation(input))
|
|
188
|
+
return undefined;
|
|
189
|
+
if (input.fence.taskId === undefined || Date.parse(input.receivedAt) > now.getTime())
|
|
190
|
+
return {};
|
|
191
|
+
const adapterId = this.adapterForRuntimeObservation(input);
|
|
192
|
+
if (adapterId === null)
|
|
193
|
+
return {};
|
|
194
|
+
return this.prepareRuntimeTerminal({
|
|
195
|
+
taskId: input.fence.taskId, roleName: input.fence.roleName,
|
|
196
|
+
agentId: input.fence.agentId, adapterId, conversationId: input.fence.conversationId,
|
|
197
|
+
nativeSessionId: input.fence.nativeSessionId, nativeTurnId: input.fence.nativeTurnId,
|
|
198
|
+
attemptId: input.fence.receiptId, runId: input.fence.runId
|
|
199
|
+
}, input.kind === "turn.completed" && input.payload.resultTransportDiagnostic === undefined
|
|
200
|
+
&& transportAgentResult(input.payload.output).status === "completed");
|
|
201
|
+
}
|
|
202
|
+
foldRuntimeObservation(raw, now, preparation) {
|
|
172
203
|
const input = createRuntimeObservation(raw);
|
|
173
204
|
const taskId = input.fence.taskId;
|
|
174
205
|
if (taskId === undefined)
|
|
@@ -263,13 +294,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
263
294
|
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
264
295
|
break;
|
|
265
296
|
case "turn.completed":
|
|
266
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now);
|
|
297
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now, preparation);
|
|
267
298
|
break;
|
|
268
299
|
case "turn.failed": {
|
|
269
300
|
// A Provider Turn is an activation boundary, not a Task outcome.
|
|
270
301
|
// Keep the AgentRun and Session identity available for Agent-directed
|
|
271
302
|
// restore + submit, even when a provider marks its AgentRun terminal.
|
|
272
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now);
|
|
303
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now, preparation);
|
|
273
304
|
break;
|
|
274
305
|
}
|
|
275
306
|
case "operation.started":
|
|
@@ -286,7 +317,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
286
317
|
outcome = this.validateCanonicalRunObservation(input, now);
|
|
287
318
|
break;
|
|
288
319
|
case "turn.cancelled": {
|
|
289
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now);
|
|
320
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now, preparation);
|
|
290
321
|
break;
|
|
291
322
|
}
|
|
292
323
|
case "conversation.observed":
|
|
@@ -617,7 +648,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
617
648
|
return "applied";
|
|
618
649
|
});
|
|
619
650
|
}
|
|
620
|
-
foldProviderTurnBoundary(input, adapterId, providerStatus, now) {
|
|
651
|
+
foldProviderTurnBoundary(input, adapterId, providerStatus, now, preparation) {
|
|
621
652
|
const outcome = providerStatus === "completed"
|
|
622
653
|
? input.payload.resultTransportDiagnostic === undefined
|
|
623
654
|
? transportAgentResult(input.payload.output)
|
|
@@ -656,7 +687,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
656
687
|
const classification = this.classifyRuntimeRunTerminal(completed);
|
|
657
688
|
if (classification !== "apply")
|
|
658
689
|
return classification;
|
|
659
|
-
const result = this.
|
|
690
|
+
const result = this.foldRuntimeRunTerminal(completed, now, preparation ?? {});
|
|
660
691
|
return result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
661
692
|
}
|
|
662
693
|
validateCanonicalSessionObservation(input, now) {
|
|
@@ -1774,8 +1805,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1774
1805
|
listDormantRuntimeOwners() {
|
|
1775
1806
|
return this.listRuntimeSessionCandidates().flatMap((candidate) => {
|
|
1776
1807
|
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(candidate.owner)))
|
|
1777
|
-
|| (candidate.owner
|
|
1778
|
-
&& this.store.getActiveRun(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
|
|
1808
|
+
|| runtimeOwnerHasActiveWork(this.store, candidate.owner)) {
|
|
1779
1809
|
return [];
|
|
1780
1810
|
}
|
|
1781
1811
|
return [{
|
|
@@ -2282,17 +2312,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
2282
2312
|
classifyRuntimeRunTerminal(input) {
|
|
2283
2313
|
return resolveTerminalExecution(this.store, input) === null ? "obsolete" : "apply";
|
|
2284
2314
|
}
|
|
2315
|
+
prepareRuntimeTerminal(input, completed) {
|
|
2316
|
+
const run = resolveTerminalExecution(this.store, input)?.run;
|
|
2317
|
+
const workspace = run?.status === "active"
|
|
2318
|
+
&& run.executionGroupId !== undefined
|
|
2319
|
+
&& run.executionLaneId !== undefined
|
|
2320
|
+
&& run.workspace !== undefined
|
|
2321
|
+
&& completed
|
|
2322
|
+
? this.#observeRead("git-workspace-snapshot", run.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, run.workspace))
|
|
2323
|
+
: undefined;
|
|
2324
|
+
return { run, workspace };
|
|
2325
|
+
}
|
|
2285
2326
|
observeRuntimeRunTerminal(input, now = new Date()) {
|
|
2327
|
+
return this.foldRuntimeRunTerminal(input, now, this.prepareRuntimeTerminal(input, input.outcome.status === "completed"));
|
|
2328
|
+
}
|
|
2329
|
+
foldRuntimeRunTerminal(input, now, preparation) {
|
|
2286
2330
|
// Resource inspection is bounded but external to SQLite. Revalidate the
|
|
2287
2331
|
// immutable AgentRun workspace after acquiring the write transaction.
|
|
2288
|
-
const preparedRun =
|
|
2289
|
-
const workspace = preparedRun?.status === "active"
|
|
2290
|
-
&& preparedRun.executionGroupId !== undefined
|
|
2291
|
-
&& preparedRun.executionLaneId !== undefined
|
|
2292
|
-
&& preparedRun.workspace !== undefined
|
|
2293
|
-
&& input.outcome.status === "completed"
|
|
2294
|
-
? this.#observeRead("git-workspace-snapshot", preparedRun.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace))
|
|
2295
|
-
: undefined;
|
|
2332
|
+
const { run: preparedRun, workspace } = preparation;
|
|
2296
2333
|
return this.store.transaction((store) => {
|
|
2297
2334
|
const task = store.getTask(input.taskId);
|
|
2298
2335
|
if (task === null)
|
|
@@ -2304,7 +2341,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
2304
2341
|
if (resolved === null)
|
|
2305
2342
|
throw new Error("Runtime terminal has no exact execution binding.");
|
|
2306
2343
|
const { run: observedRun, current: recordedProviderTurn, attemptId } = resolved;
|
|
2307
|
-
if (
|
|
2344
|
+
if (preparedRun?.id !== observedRun?.id
|
|
2345
|
+
|| !isDeepStrictEqual(preparedRun?.workspace, observedRun?.workspace)) {
|
|
2308
2346
|
throw new Error("Runtime terminal workspace changed during result preparation.");
|
|
2309
2347
|
}
|
|
2310
2348
|
if (workspace !== undefined && observedRun?.workspace !== undefined
|
|
@@ -3132,8 +3170,7 @@ function sameRuntimeOwner(left, right) {
|
|
|
3132
3170
|
}
|
|
3133
3171
|
function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = false) {
|
|
3134
3172
|
const { owner } = candidate;
|
|
3135
|
-
if (!allowActiveRun && owner
|
|
3136
|
-
&& store.getActiveRun(owner.taskId, owner.roleName) !== null) {
|
|
3173
|
+
if (!allowActiveRun && runtimeOwnerHasActiveWork(store, owner)) {
|
|
3137
3174
|
return false;
|
|
3138
3175
|
}
|
|
3139
3176
|
const sessions = runtimeOwnerSessionSet(store, owner);
|
|
@@ -3143,6 +3180,17 @@ function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = fal
|
|
|
3143
3180
|
return active !== undefined && active.agentId === candidate.agentId && active.adapterId === candidate.adapterId
|
|
3144
3181
|
&& active.nativeSessionId === candidate.nativeSessionId && (allowActiveRun || active.updatedAt === candidate.sessionUpdatedAt);
|
|
3145
3182
|
}
|
|
3183
|
+
function runtimeOwnerHasActiveWork(store, owner) {
|
|
3184
|
+
if (owner.scope === "task")
|
|
3185
|
+
return store.getActiveRun(owner.taskId, owner.roleName) !== null;
|
|
3186
|
+
// Global Turns do not have Task AgentRun records. Use their existing native
|
|
3187
|
+
// delivery binding, including submitting and uncertain acceptance.
|
|
3188
|
+
const sessions = store.getGlobalRoleSessionSet(owner.roleName);
|
|
3189
|
+
const binding = sessions?.providerBinding;
|
|
3190
|
+
return binding != null && (providerTurnIsActive(binding.run)
|
|
3191
|
+
|| currentProviderConversation(binding).conversationId
|
|
3192
|
+
!== sessions?.sessions[sessions.activeAgentId]?.nativeSessionId);
|
|
3193
|
+
}
|
|
3146
3194
|
function endRuntimeOwnerSession(store, owner, now) {
|
|
3147
3195
|
if (owner.scope === "task") {
|
|
3148
3196
|
const sessions = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveJobCaller } from "../task/taskAuthority.js";
|
|
1
|
+
import { resolveJobCaller, resolveJobReadCaller } from "../task/taskAuthority.js";
|
|
2
2
|
import { callFileTaskController } from "./clientRuntime.js";
|
|
3
3
|
export async function startDurableJob(home, params, clientOptions = {}) {
|
|
4
4
|
// rr6/f2: Route through callFileTaskController so an Integration-issued
|
|
@@ -89,7 +89,7 @@ export function createControllerIntegrationJobPort(home, clientOptions = {}) {
|
|
|
89
89
|
return job;
|
|
90
90
|
},
|
|
91
91
|
async getJob(taskId, jobId) {
|
|
92
|
-
return getDurableJob(home, taskId, jobId,
|
|
92
|
+
return getDurableJob(home, taskId, jobId, resolveJobReadCaller(clientOptions.environment, taskId), clientOptions);
|
|
93
93
|
},
|
|
94
94
|
async cancelJob(taskId, jobId) {
|
|
95
95
|
// rr8/rr12: Bind the cancel request to the caller's managed identity.
|
|
@@ -18,7 +18,7 @@ import { redactLaunchText } from "../runtime/launchDiagnostics.js";
|
|
|
18
18
|
import { requireManagedTaskCaller, ManagedRuntimeDriftError } from "../runtime/managedCaller.js";
|
|
19
19
|
import { CliError } from "../errors/cliError.js";
|
|
20
20
|
import { assertJobAssignmentScope, JobAssignmentScopeError } from "../job/jobAssignmentScope.js";
|
|
21
|
-
import {
|
|
21
|
+
import { assertTaskReadable } from "../context/taskContext.js";
|
|
22
22
|
import { assertTaskProjectWriteAuthority } from "../task/taskAuthority.js";
|
|
23
23
|
import { validateHttpPreview } from "../job/httpPreview.js";
|
|
24
24
|
import { definitionFromTaskRun } from "../job/jobDefinition.js";
|
|
@@ -134,7 +134,7 @@ export function createDurableJobControl(store) {
|
|
|
134
134
|
if (caller === undefined)
|
|
135
135
|
throw jobControlError("UNAUTHORIZED", "Job reads require an explicit caller.");
|
|
136
136
|
try {
|
|
137
|
-
|
|
137
|
+
assertTaskReadable(store, taskId, caller.scope === "user" ? {} : {
|
|
138
138
|
YUI_SESSION_SCOPE: caller.scope, YUI_TASK_ID: caller.taskId, YUI_ROLE: caller.role,
|
|
139
139
|
YUI_AGENT_ID: caller.agentId, YUI_ADAPTER_ID: caller.adapterId, YUI_NATIVE_SESSION_ID: caller.nativeSessionId
|
|
140
140
|
});
|
|
@@ -137,7 +137,7 @@ export function authorizeRunRead(store, run, caller) {
|
|
|
137
137
|
throw new Error("Task delegation is not an Assignment grant.");
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
//
|
|
140
|
+
// Ordinary process-Run evidence does not transfer its execution Assignment.
|
|
141
141
|
if ("taskId" in run)
|
|
142
142
|
createDurableJobControl(store).getJob(run.taskId, run.id, caller);
|
|
143
143
|
}
|
|
@@ -168,6 +168,8 @@ export function admitJobRun(store, home, scope, definitionId, caller, trigger, n
|
|
|
168
168
|
const same = runs.find(run => run.trigger.key === trigger.key && run.trigger.source === trigger.source);
|
|
169
169
|
if (same !== undefined) {
|
|
170
170
|
authorizeRunRead(tx, same, caller);
|
|
171
|
+
if ("taskId" in same && !same.delegation)
|
|
172
|
+
requireJobAssignment(tx, same, caller);
|
|
171
173
|
if (same.definitionRevision !== job.revision || same.retryOf !== retryOf) {
|
|
172
174
|
throw new Error(`JobRun trigger conflicts with its original snapshot: ${same.id}.`);
|
|
173
175
|
}
|
|
@@ -178,6 +180,8 @@ export function admitJobRun(store, home, scope, definitionId, caller, trigger, n
|
|
|
178
180
|
|| run.status === "unknown-needs-attention");
|
|
179
181
|
if (occupied !== undefined) {
|
|
180
182
|
authorizeRunRead(tx, occupied, caller);
|
|
183
|
+
if ("taskId" in occupied && !occupied.delegation)
|
|
184
|
+
requireJobAssignment(tx, occupied, caller);
|
|
181
185
|
return { run: occupied, created: false, reason: "overlap" };
|
|
182
186
|
}
|
|
183
187
|
if (retryOf !== undefined && !runs.some(run => run.id === retryOf
|
|
@@ -91,13 +91,14 @@ function recoveryGuidance(o) {
|
|
|
91
91
|
if (o.wakeId !== null && roleName === "leader") {
|
|
92
92
|
const wakeTarget = `${arg(taskId)} ${arg(o.wakeId)}`;
|
|
93
93
|
if (o.deliveryEvidence?.outcome === "rejected") {
|
|
94
|
-
lines.push(`After correcting the rejection cause, and only if this exact wake is still unconsumed, its original claim matches and native acceptance/activity are proven absent: yui task wake retry ${wakeTarget} --reason '<verified correction and quiescence>'. Queued is not accepted. Do not retry accepted/unknown input
|
|
94
|
+
lines.push(`Choose alternative actions, not a sequence. After correcting the rejection cause, and only if this exact wake is still unconsumed, its original claim matches and native acceptance/activity are proven absent: yui task wake retry ${wakeTarget} --reason '<verified correction and quiescence>'. Queued is not accepted. Do not retry accepted/unknown input.`);
|
|
95
|
+
lines.push(`Alternatively, after exact shared-native/background/writer quiescence and persisted notification-attempt evidence: yui task wake resolve ${wakeTarget} --reason '<exact quiescence evidence>'. A rejection is not quiescence, including rejection after an unknown outcome. Resolve releases only scheduling without replay, ACK or acceptance; it preserves original claims and delivery evidence. If the operational mailbox is absent, the exact durable wake/attempt must still be proven; another current batch is never released. The rightful handler must inspect original effects and settle still-valid obligations separately.`);
|
|
95
96
|
}
|
|
96
97
|
else if (o.deliveryEvidence?.outcome === "unknown") {
|
|
97
|
-
lines.push(`Do not replay unknown input. After exact shared-native/background/writer quiescence and persisted evidence
|
|
98
|
+
lines.push(`Do not replay unknown input. After exact shared-native/background/writer quiescence and persisted evidence identifying the original wake/notification attempt: yui task wake resolve ${wakeTarget} --reason '<exact quiescence evidence>'. This releases only scheduling without replay, ACK or acceptance; an absent operational mailbox requires exact durable evidence, never release of another current batch. The rightful handler must inspect original effects and settle still-valid obligations separately.`);
|
|
98
99
|
}
|
|
99
100
|
else {
|
|
100
|
-
lines.push("Deferred, accepted or unreported delivery does not meet this candidate's rejected-retry/unknown-resolve preconditions. Keep the original wake and inspect the runtime barrier instead of mechanically invoking either action.");
|
|
101
|
+
lines.push("Deferred, accepted or unreported delivery does not meet this candidate's rejected-retry/unknown-or-rejected-resolve preconditions. Keep the original wake and inspect the runtime barrier instead of mechanically invoking either action.");
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -4,6 +4,7 @@ import { blockingProviderContinuations } from "../runtime/runtimeContinuationPro
|
|
|
4
4
|
import { validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
5
5
|
import { isDeepStrictEqual } from "node:util";
|
|
6
6
|
import { isHandlingReminder } from "../message/inboxTurn.js";
|
|
7
|
+
import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
|
|
7
8
|
export function ownedProviderInput(input) {
|
|
8
9
|
if (input.runId !== undefined && input.taskId !== undefined) {
|
|
9
10
|
return { kind: "run", taskId: input.taskId, runId: input.runId };
|
|
@@ -90,8 +91,21 @@ export function admitOwnedProviderInput(store, binding, input) {
|
|
|
90
91
|
if (input.taskId !== undefined && input.runId !== undefined) {
|
|
91
92
|
const run = store.getRun(input.taskId, input.runId);
|
|
92
93
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
94
|
+
const session = sessions?.sessions[input.agentId];
|
|
95
|
+
// Session provenance can describe an older ReviewRound. The exact failed
|
|
96
|
+
// Run, not that first launch, owns this retry's frozen launch configuration.
|
|
97
|
+
// Continuity still fences the native Session's physical workspace/authority;
|
|
98
|
+
// exact equality with the failed Run retains model, permission and candidate.
|
|
99
|
+
const previous = run?.purpose === "review" && retry.previousRunId !== undefined
|
|
100
|
+
? store.getRun(input.taskId, retry.previousRunId) : null;
|
|
101
|
+
const matchesLaunch = run !== null && session !== undefined && (run.purpose === "review"
|
|
102
|
+
? previous?.status === "failed" && previous.purpose === "review"
|
|
103
|
+
&& previous.roleName === run.roleName && previous.reviewRoundId === run.reviewRoundId
|
|
104
|
+
&& isDeepStrictEqual(run.effective, previous.effective)
|
|
105
|
+
&& roleSessionMayContinue(session.effective, run.effective)
|
|
106
|
+
: isDeepStrictEqual(run.effective, session.effective));
|
|
93
107
|
if (run === null || run.status !== "active"
|
|
94
|
-
|| !
|
|
108
|
+
|| !matchesLaunch) {
|
|
95
109
|
throw new Error("Retry execution no longer matches its fixed native Session.");
|
|
96
110
|
}
|
|
97
111
|
if (run.workspace !== undefined && !isDeepStrictEqual(store.getManagedWorkspace(run.workspace.owner), run.workspace)) {
|
|
@@ -171,9 +171,25 @@ export function createProviderRetryHooks(home, store, ports = {}) {
|
|
|
171
171
|
}
|
|
172
172
|
try {
|
|
173
173
|
if (owner.scope === "task" && retry.previousRunId !== undefined) {
|
|
174
|
-
if (retry.successorRunId !== undefined || retry.successorReviewRoundId !== undefined)
|
|
175
|
-
continue;
|
|
176
174
|
const previous = store.getRun(owner.taskId, retry.previousRunId);
|
|
175
|
+
if (retry.successorRunId !== undefined || retry.successorReviewRoundId !== undefined) {
|
|
176
|
+
const round = retry.successorReviewRoundId === undefined
|
|
177
|
+
? null : store.getReviewRound(owner.taskId, retry.successorReviewRoundId);
|
|
178
|
+
const successorId = retry.successorRunId ?? (previous?.executionLaneId === undefined
|
|
179
|
+
? round?.reviewerRunId
|
|
180
|
+
: round?.executionGroup?.lanes.find(lane => lane.id === previous.executionLaneId)?.currentRunId);
|
|
181
|
+
const successor = successorId === undefined ? null : store.getRun(owner.taskId, successorId);
|
|
182
|
+
// A scheduled Run can fail before Host admission, leaving the
|
|
183
|
+
// Provider binding on the original failed Turn. Stop waiting;
|
|
184
|
+
// this is not proof of native quiescence or permission to replay.
|
|
185
|
+
if (successor?.status === "failed" && successor.id !== previous?.id
|
|
186
|
+
&& successor.roleName === owner.roleName
|
|
187
|
+
&& (retry.successorReviewRoundId === undefined
|
|
188
|
+
|| successor.reviewRoundId === retry.successorReviewRoundId)) {
|
|
189
|
+
stop(set, "successor-failed-before-admission", at);
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
177
193
|
if (previous?.status === "active")
|
|
178
194
|
continue; // ordinary rejected-delivery terminal fold
|
|
179
195
|
if (previous?.status !== "failed") {
|