@zq-silk/yui 0.6.0 → 0.6.2
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 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
1
2
|
import { resolve } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { resolveYuiHome } from "../storage/taskStore.js";
|
|
5
|
+
import { handoverCandidateFromEnvironment, runHandoverCandidate } from "./handoverCandidate.js";
|
|
4
6
|
import { startFileTaskControllerRuntime } from "./runtime.js";
|
|
5
7
|
export async function runFileTaskControllerProcess(environment = process.env) {
|
|
6
8
|
const home = resolveYuiHome(environment);
|
|
9
|
+
// Issue 02: a handover candidate runs the read-only prelude and promotes
|
|
10
|
+
// itself once the old owner exits. It never binds the main socket first.
|
|
11
|
+
const candidate = handoverCandidateFromEnvironment(environment);
|
|
12
|
+
if (candidate !== null) {
|
|
13
|
+
const result = await runHandoverCandidate(home, candidate.handoverId, { environment });
|
|
14
|
+
if (result.outcome === "aborted") {
|
|
15
|
+
process.stderr.write(`Controller handover candidate aborted: ${result.reason}\n`);
|
|
16
|
+
process.exitCode = 5;
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
7
20
|
const controller = await startFileTaskControllerRuntime(home, {
|
|
8
21
|
environment,
|
|
9
22
|
onError: (error) => {
|
|
@@ -25,8 +38,18 @@ export async function runFileTaskControllerProcess(environment = process.env) {
|
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
function isEntrypoint() {
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
// Node's ESM loader resolves symlinks for import.meta.url, while
|
|
42
|
+
// process.argv[1] keeps the path as invoked. A release installed under a
|
|
43
|
+
// symlinked Home (e.g. /home -> /data00/home) must still detect itself as
|
|
44
|
+
// the entrypoint, so compare realpaths on both sides.
|
|
45
|
+
if (process.argv[1] === undefined)
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
52
|
+
}
|
|
30
53
|
}
|
|
31
54
|
if (isEntrypoint()) {
|
|
32
55
|
void runFileTaskControllerProcess().catch((error) => {
|
|
@@ -297,18 +297,26 @@ export function ephemeralDomainFingerprint(identity, fileFingerprint) {
|
|
|
297
297
|
fileFingerprint ?? ""
|
|
298
298
|
].join(":");
|
|
299
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Parses the process start identity (field 20, `starttime`) from the content
|
|
302
|
+
* of a Linux `/proc/<pid>/stat` file. Shared by every reader so the parsing
|
|
303
|
+
* (including the `comm` field's embedded spaces/parentheses) is defined once.
|
|
304
|
+
* Returns undefined when the content is not a recognizable stat line.
|
|
305
|
+
*/
|
|
306
|
+
export function parseLinuxProcessStartIdentity(statContent) {
|
|
307
|
+
const closing = statContent.lastIndexOf(")");
|
|
308
|
+
if (closing < 0)
|
|
309
|
+
return undefined;
|
|
310
|
+
const identity = statContent.slice(closing + 1).trim().split(/\s+/u)[19];
|
|
311
|
+
return identity !== undefined && /^[0-9]{1,32}$/u.test(identity)
|
|
312
|
+
? identity
|
|
313
|
+
: undefined;
|
|
314
|
+
}
|
|
300
315
|
export function readLinuxProcessStartIdentity(pid) {
|
|
301
316
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
302
317
|
return undefined;
|
|
303
318
|
try {
|
|
304
|
-
|
|
305
|
-
const closing = stat.lastIndexOf(")");
|
|
306
|
-
if (closing < 0)
|
|
307
|
-
return undefined;
|
|
308
|
-
const identity = stat.slice(closing + 1).trim().split(/\s+/u)[19];
|
|
309
|
-
return identity !== undefined && /^[0-9]{1,32}$/u.test(identity)
|
|
310
|
-
? identity
|
|
311
|
-
: undefined;
|
|
319
|
+
return parseLinuxProcessStartIdentity(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
312
320
|
}
|
|
313
321
|
catch {
|
|
314
322
|
return undefined;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { routeProviderProgress } from "../telemetry/telemetryRouter.js";
|
|
3
|
+
import { NullTelemetrySink } from "../telemetry/telemetryStore.js";
|
|
2
4
|
import { activeLiveRoleAgentSession, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, recordObservedTaskRoleCompletion, recordRoleAgentSession, rememberRoleAgentCompletedTurn, settleTaskRoleCompletion, updateRoleAgentSessionStatus } from "../executor/agentExecutor.js";
|
|
3
5
|
import { createPendingTurnCompletion, hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
4
6
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
5
7
|
import { answerInputRequest } from "../input/inputRequest.js";
|
|
6
8
|
import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
|
|
7
|
-
import { effectiveLaunchSnapshotsCompatible, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
9
|
+
import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
8
10
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
9
|
-
import { failAgentRun, markAgentRunDelivered, markAgentRunPushed } from "../run/agentRun.js";
|
|
11
|
+
import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, withProviderRetry } from "../run/agentRun.js";
|
|
12
|
+
import { classifyProviderError, isRetryableProviderErrorClass } from "../lifecycle/providerErrorClass.js";
|
|
13
|
+
import { providerRetryIsDue, scheduleProviderRetry } from "../run/providerRetry.js";
|
|
14
|
+
import { providerRetryConfig } from "../run/providerRetryConfig.js";
|
|
10
15
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
11
16
|
import { foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
12
17
|
import { mapNativeLifecycleSignal } from "../lifecycle/providerLifecycleMapping.js";
|
|
@@ -14,7 +19,7 @@ import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
|
14
19
|
import { createLeaderRecoveryNotification, createLeaderStallNotification } from "../scheduler/operatorNotification.js";
|
|
15
20
|
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
16
21
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
17
|
-
import { latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_RESOURCE_SUPPRESSED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
22
|
+
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_RESOURCE_SUPPRESSED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
18
23
|
import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
|
|
19
24
|
import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
20
25
|
import { formatAgentRunReceiptId, formatTaskRecordReference } from "../task/taskRecordReference.js";
|
|
@@ -22,11 +27,68 @@ import { bindExecution, claimPending, completeProcessing, releaseProcessing } fr
|
|
|
22
27
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
23
28
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
24
29
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
30
|
+
const NULL_TELEMETRY_SINK = new NullTelemetrySink();
|
|
25
31
|
/** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
|
|
26
32
|
export class FileSchedulerStoreAdapter {
|
|
27
33
|
store;
|
|
28
|
-
|
|
34
|
+
telemetry;
|
|
35
|
+
/**
|
|
36
|
+
* @param telemetry Optional sidecar wiring (Issue 09). Absent or `legacy`
|
|
37
|
+
* keeps master behavior (semantic progress events only); `dual` also
|
|
38
|
+
* upserts progress to the sidecar; `bounded` routes progress to the
|
|
39
|
+
* sidecar only and feeds the scheduler projection synthesized latest
|
|
40
|
+
* progress from the reader.
|
|
41
|
+
*/
|
|
42
|
+
constructor(store, telemetry = null) {
|
|
29
43
|
this.store = store;
|
|
44
|
+
this.telemetry = telemetry;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Revision-scoped read projection. Keyed by the store's durable revision;
|
|
48
|
+
* any committed mutation (ours or an external writer's) advances it and the
|
|
49
|
+
* next read rebuilds. A store without getStateRevision disables caching.
|
|
50
|
+
*/
|
|
51
|
+
#readProjection = null;
|
|
52
|
+
#taskReadProjection(taskId) {
|
|
53
|
+
const revision = typeof this.store.getStateRevision === "function"
|
|
54
|
+
? this.store.getStateRevision()
|
|
55
|
+
: Number.NaN;
|
|
56
|
+
const telemetryRevision = this.telemetry?.mode === "bounded"
|
|
57
|
+
? this.telemetry.reader.revision()
|
|
58
|
+
: 0;
|
|
59
|
+
if (this.#readProjection === null
|
|
60
|
+
|| this.#readProjection.revision !== revision
|
|
61
|
+
|| this.#readProjection.telemetryRevision !== telemetryRevision) {
|
|
62
|
+
this.#readProjection = { revision, telemetryRevision, tasks: new Map() };
|
|
63
|
+
}
|
|
64
|
+
const tasks = this.#readProjection.tasks;
|
|
65
|
+
let task = tasks.get(taskId);
|
|
66
|
+
if (task === undefined) {
|
|
67
|
+
const semanticEvents = this.store.listEvents(taskId);
|
|
68
|
+
// In bounded mode progress lives only in the sidecar; merge the latest
|
|
69
|
+
// observation per Run so liveness/stall folds keep working. In legacy
|
|
70
|
+
// and dual modes semantic history already carries progress.
|
|
71
|
+
const events = this.telemetry?.mode === "bounded"
|
|
72
|
+
? [...semanticEvents, ...this.telemetry.reader.latestProgressEvents(taskId)]
|
|
73
|
+
: semanticEvents;
|
|
74
|
+
task = {
|
|
75
|
+
events,
|
|
76
|
+
runFacts: foldRunProgressFacts(events),
|
|
77
|
+
agentRuns: this.store.listAgentRuns(taskId),
|
|
78
|
+
workItems: this.store.listWorkItems(taskId),
|
|
79
|
+
reviewRounds: this.store.listReviewRounds(taskId),
|
|
80
|
+
changeSets: this.store.listChangeSets(taskId),
|
|
81
|
+
integrationAttempts: this.store.listIntegrationAttempts(taskId),
|
|
82
|
+
inputRequests: this.store.listInputRequests(taskId),
|
|
83
|
+
durableJobs: this.store.listDurableJobs(taskId),
|
|
84
|
+
messages: this.store.listMessages(taskId)
|
|
85
|
+
};
|
|
86
|
+
tasks.set(taskId, task);
|
|
87
|
+
}
|
|
88
|
+
return task;
|
|
89
|
+
}
|
|
90
|
+
withRuntimeEventTransaction(execute) {
|
|
91
|
+
return this.store.withRuntimeEventTransaction(execute);
|
|
30
92
|
}
|
|
31
93
|
getPresentationContext() {
|
|
32
94
|
return { timeZone: this.store.getConfig().timeZone };
|
|
@@ -98,10 +160,51 @@ export class FileSchedulerStoreAdapter {
|
|
|
98
160
|
return session === undefined ? null : mapSession(session);
|
|
99
161
|
}
|
|
100
162
|
listEvents(taskId) {
|
|
101
|
-
return this
|
|
163
|
+
return this.#taskReadProjection(taskId).events;
|
|
164
|
+
}
|
|
165
|
+
listAgentRuns(taskId) {
|
|
166
|
+
return this.#taskReadProjection(taskId).agentRuns;
|
|
167
|
+
}
|
|
168
|
+
listWorkItems(taskId) {
|
|
169
|
+
return this.#taskReadProjection(taskId).workItems;
|
|
170
|
+
}
|
|
171
|
+
listReviewRounds(taskId) {
|
|
172
|
+
return this.#taskReadProjection(taskId).reviewRounds;
|
|
173
|
+
}
|
|
174
|
+
listIntegrationAttempts(taskId) {
|
|
175
|
+
return this.#taskReadProjection(taskId).integrationAttempts;
|
|
176
|
+
}
|
|
177
|
+
listDurableJobs(taskId) {
|
|
178
|
+
return this.#taskReadProjection(taskId).durableJobs;
|
|
179
|
+
}
|
|
180
|
+
listInputRequests(taskId) {
|
|
181
|
+
return this.#taskReadProjection(taskId).inputRequests;
|
|
182
|
+
}
|
|
183
|
+
listMessages(taskId) {
|
|
184
|
+
return this.#taskReadProjection(taskId).messages;
|
|
185
|
+
}
|
|
186
|
+
getRunProgressFacts(taskId, runId) {
|
|
187
|
+
return this.#taskReadProjection(taskId).runFacts.get(runId);
|
|
102
188
|
}
|
|
103
189
|
getRunDurableProgress(taskId, roleName, runId) {
|
|
104
|
-
|
|
190
|
+
const projected = this.#taskReadProjection(taskId);
|
|
191
|
+
// Serve the related-record fold from the same revision's projection: the
|
|
192
|
+
// event history and the WorkItem/Review/ChangeSet/Integration/Input lists
|
|
193
|
+
// are read once per Task per revision, and the per-Run checkpoint/activity
|
|
194
|
+
// facts come from the one-pass fold instead of per-candidate scans.
|
|
195
|
+
const view = {
|
|
196
|
+
getAgentRun: (id, agentRunId) => this.store.getAgentRun(id, agentRunId),
|
|
197
|
+
listEvents: () => projected.events,
|
|
198
|
+
getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
|
|
199
|
+
listReviewRounds: () => projected.reviewRounds,
|
|
200
|
+
listChangeSets: () => projected.changeSets,
|
|
201
|
+
listIntegrationAttempts: () => projected.integrationAttempts,
|
|
202
|
+
listInputRequests: () => projected.inputRequests
|
|
203
|
+
};
|
|
204
|
+
// A missing fold entry is an authoritative empty fold, not a signal to
|
|
205
|
+
// re-scan the whole history. Pass {} so latestRunDurableProgressAt treats
|
|
206
|
+
// the fold as present and skips the per-candidate fallback scans.
|
|
207
|
+
return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
|
|
105
208
|
}
|
|
106
209
|
recordRoleRunStall(input) {
|
|
107
210
|
return this.store.transaction((store) => {
|
|
@@ -241,8 +344,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
241
344
|
// A provider-ready fact for this exact generation is proven by a folded
|
|
242
345
|
// runtime.provider-session-lifecycle event with preInputReady true and a
|
|
243
346
|
// matching agent/launch/nativeSession fence. This reads durable folded truth
|
|
244
|
-
// only — no liveness, screen, or pane/PID inference.
|
|
245
|
-
|
|
347
|
+
// only — no liveness, screen, or pane/PID inference. Served from the
|
|
348
|
+
// revision projection: a stale miss only repeats an idempotent first push.
|
|
349
|
+
return this.listEvents(input.taskId).some((event) => (event.type === "runtime.provider-session-lifecycle"
|
|
246
350
|
&& event.payload.roleName === input.roleName
|
|
247
351
|
&& event.payload.agentId === input.agentId
|
|
248
352
|
&& event.payload.preInputReady === "true"
|
|
@@ -265,6 +369,32 @@ export class FileSchedulerStoreAdapter {
|
|
|
265
369
|
enqueueLeaderWakeup(taskId, reason, now) {
|
|
266
370
|
return this.store.transaction((store) => (queueLeaderWakeup(store, taskId, reason, now)));
|
|
267
371
|
}
|
|
372
|
+
listAllDurableJobs() {
|
|
373
|
+
return this.store.listAllDurableJobs();
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Apply one durable-job transition and, in the SAME transaction, enqueue
|
|
377
|
+
* the Leader wakeup. A terminal job without its wakeup enqueued is a lost
|
|
378
|
+
* wakeup, so the two writes commit together or not at all.
|
|
379
|
+
*
|
|
380
|
+
* f6: The wakeup targets the Leader role mailbox (not the Task mailbox)
|
|
381
|
+
* and also queues a pending wakeup so processLeaderWakeups dispatches
|
|
382
|
+
* the Leader Run with the exact job-finished reason.
|
|
383
|
+
*/
|
|
384
|
+
transitionDurableJob(taskId, jobId, transition, now, wakeup) {
|
|
385
|
+
return this.store.transaction((store) => {
|
|
386
|
+
const current = store.getDurableJob(taskId, jobId);
|
|
387
|
+
if (current === null)
|
|
388
|
+
return null;
|
|
389
|
+
const next = transition(current);
|
|
390
|
+
store.saveDurableJob(taskId, next);
|
|
391
|
+
if (wakeup !== undefined) {
|
|
392
|
+
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, wakeup.reason, now, [...wakeup.refs]);
|
|
393
|
+
queueLeaderWakeup(store, taskId, wakeup.reason, now);
|
|
394
|
+
}
|
|
395
|
+
return next;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
268
398
|
releaseLeaderWakeupAndEnqueue(taskId, batchId, reason, now) {
|
|
269
399
|
const target = { kind: "role", taskId, roleName: "leader" };
|
|
270
400
|
return this.store.transaction((store) => {
|
|
@@ -1383,6 +1513,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1383
1513
|
return { disposition: "obsolete", runId: input.runId };
|
|
1384
1514
|
}
|
|
1385
1515
|
const summary = claudeStopFailureSummary(input);
|
|
1516
|
+
// Issue 04: a classified transient Provider failure keeps the exact Run
|
|
1517
|
+
// and Native Session and is retried in place instead of terminalizing.
|
|
1518
|
+
const retryDecision = this.providerRetryDecision(store, input, summary, now);
|
|
1519
|
+
if (retryDecision !== null)
|
|
1520
|
+
return retryDecision;
|
|
1386
1521
|
const result = terminalizeExactTaskRun(store, {
|
|
1387
1522
|
taskId: input.taskId,
|
|
1388
1523
|
roleName: input.roleName,
|
|
@@ -1442,6 +1577,218 @@ export class FileSchedulerStoreAdapter {
|
|
|
1442
1577
|
return { disposition: "applied", runId: input.runId };
|
|
1443
1578
|
});
|
|
1444
1579
|
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Issue 04 in-place retry decision for one classified Provider failure.
|
|
1582
|
+
* Returns an observation when the failure was handled without terminalizing
|
|
1583
|
+
* the Run, or `null` to fall through to the existing terminalization path.
|
|
1584
|
+
*/
|
|
1585
|
+
providerRetryDecision(store, input, summary, now) {
|
|
1586
|
+
const config = providerRetryConfig();
|
|
1587
|
+
if (config.mode === "off")
|
|
1588
|
+
return null;
|
|
1589
|
+
const classification = classifyProviderError({
|
|
1590
|
+
adapterId: input.adapterId,
|
|
1591
|
+
error: input.error,
|
|
1592
|
+
errorDetails: input.errorDetails,
|
|
1593
|
+
summary: input.lastAssistantMessage
|
|
1594
|
+
});
|
|
1595
|
+
const adapterEnabled = config.adapters.includes(input.adapterId);
|
|
1596
|
+
const retryable = adapterEnabled
|
|
1597
|
+
&& isRetryableProviderErrorClass(classification.errorClass);
|
|
1598
|
+
const shadow = config.mode === "shadow";
|
|
1599
|
+
// Shadow mode records the classification and "would retry" fact without
|
|
1600
|
+
// changing any behavior; the Run still terminalizes below.
|
|
1601
|
+
if (shadow || !retryable) {
|
|
1602
|
+
if (adapterEnabled) {
|
|
1603
|
+
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
1604
|
+
wouldRetry: String(retryable),
|
|
1605
|
+
shadow: String(shadow)
|
|
1606
|
+
}, now);
|
|
1607
|
+
}
|
|
1608
|
+
if (!shadow && classification.errorClass === "policy-denied") {
|
|
1609
|
+
const blocked = this.applyProviderPolicyBlock(store, input, summary, now);
|
|
1610
|
+
if (blocked !== null)
|
|
1611
|
+
return blocked;
|
|
1612
|
+
}
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
// transport-uncertain: the Provider may have accepted the turn. If a
|
|
1616
|
+
// native completion is already durable, the completion path owns the
|
|
1617
|
+
// yield; do not resend.
|
|
1618
|
+
if (classification.errorClass === "transport-uncertain") {
|
|
1619
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1620
|
+
const pending = sessions?.pendingTurnCompletion;
|
|
1621
|
+
if (pending !== null
|
|
1622
|
+
&& pending !== undefined
|
|
1623
|
+
&& pending.runId === input.runId
|
|
1624
|
+
&& pending.agentId === input.agentId) {
|
|
1625
|
+
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
1626
|
+
wouldRetry: "false",
|
|
1627
|
+
shadow: "false",
|
|
1628
|
+
note: "native-turn-completion-durable"
|
|
1629
|
+
}, now);
|
|
1630
|
+
return { disposition: "applied", runId: input.runId };
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const run = store.getAgentRun(input.taskId, input.runId);
|
|
1634
|
+
if (run === null || run.status !== "active")
|
|
1635
|
+
return null;
|
|
1636
|
+
const retry = scheduleProviderRetry(run.providerRetry, {
|
|
1637
|
+
errorClass: classification.errorClass,
|
|
1638
|
+
launchId: input.launchId,
|
|
1639
|
+
nativeSessionId: input.nativeSessionId,
|
|
1640
|
+
lastErrorSummary: summary
|
|
1641
|
+
}, now);
|
|
1642
|
+
store.saveAgentRun(withProviderRetry(run, retry));
|
|
1643
|
+
// Clear the in-flight fence so the original Session can be re-bound and
|
|
1644
|
+
// re-pushed by the delivery path; the Session itself stays alive.
|
|
1645
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1646
|
+
if (sessions !== null) {
|
|
1647
|
+
store.saveTaskRoleSessionSet(clearTaskRoleRun(sessions, {
|
|
1648
|
+
agentId: input.agentId,
|
|
1649
|
+
runId: input.runId,
|
|
1650
|
+
receiptId: `agent-run:${input.taskId}/${input.runId}`
|
|
1651
|
+
}, now));
|
|
1652
|
+
}
|
|
1653
|
+
// Settle the delivery claim so the retry push can re-claim the mailbox.
|
|
1654
|
+
const target = runtimeLifecycleTarget({
|
|
1655
|
+
scope: "task",
|
|
1656
|
+
taskId: input.taskId,
|
|
1657
|
+
roleName: input.roleName
|
|
1658
|
+
});
|
|
1659
|
+
const mailbox = store.getWorkMailbox(target);
|
|
1660
|
+
if (mailbox !== null && mailbox.processing !== null) {
|
|
1661
|
+
const settled = completeProcessing(mailbox, mailbox.processing.batchId);
|
|
1662
|
+
if (settled.processing === null && settled.pending === null) {
|
|
1663
|
+
store.removeWorkMailbox(target);
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
store.saveWorkMailbox(settled);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
1670
|
+
wouldRetry: "true",
|
|
1671
|
+
shadow: "false",
|
|
1672
|
+
attempt: String(retry.attempt),
|
|
1673
|
+
nextAttemptAt: retry.nextAttemptAt
|
|
1674
|
+
}, now);
|
|
1675
|
+
return { disposition: "applied", runId: input.runId };
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Issue 04 policy-denied handling: the Run stays active on its original
|
|
1679
|
+
* Session, no retry is scheduled, and the Leader receives a bounded blocker.
|
|
1680
|
+
* Yui never works around a policy denial by switching Session or widening
|
|
1681
|
+
* permission.
|
|
1682
|
+
*/
|
|
1683
|
+
applyProviderPolicyBlock(store, input, summary, now) {
|
|
1684
|
+
const run = store.getAgentRun(input.taskId, input.runId);
|
|
1685
|
+
if (run === null || run.status !== "active")
|
|
1686
|
+
return null;
|
|
1687
|
+
const retry = scheduleProviderRetry(run.providerRetry, {
|
|
1688
|
+
errorClass: "policy-denied",
|
|
1689
|
+
launchId: input.launchId,
|
|
1690
|
+
nativeSessionId: input.nativeSessionId,
|
|
1691
|
+
lastErrorSummary: summary,
|
|
1692
|
+
scheduleNextAttempt: false
|
|
1693
|
+
}, now);
|
|
1694
|
+
store.saveAgentRun(withProviderRetry(run, retry));
|
|
1695
|
+
this.recordProviderRetryClassified(store, input, "policy-denied", {
|
|
1696
|
+
wouldRetry: "false",
|
|
1697
|
+
shadow: "false",
|
|
1698
|
+
note: "policy-denied-blocker"
|
|
1699
|
+
}, now);
|
|
1700
|
+
if (input.roleName !== "leader") {
|
|
1701
|
+
enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-policy-blocked", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
1702
|
+
}
|
|
1703
|
+
return { disposition: "applied", runId: input.runId };
|
|
1704
|
+
}
|
|
1705
|
+
recordProviderRetryClassified(store, input, errorClass, extra, now) {
|
|
1706
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-retry-classified", {
|
|
1707
|
+
eventId: input.eventId,
|
|
1708
|
+
runId: input.runId,
|
|
1709
|
+
roleName: input.roleName,
|
|
1710
|
+
errorClass,
|
|
1711
|
+
...extra
|
|
1712
|
+
}, now));
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Issue 04 durable retry timer: lists active Runs whose in-place retry is
|
|
1716
|
+
* due. The Controller arms its deadline timer from this projection, so a
|
|
1717
|
+
* Controller restart resumes the same attempt lineage.
|
|
1718
|
+
*/
|
|
1719
|
+
listPendingProviderRetries() {
|
|
1720
|
+
return this.store.listPendingProviderRetries();
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Issue 04: reopens each due retry on its original Native Session. A Run
|
|
1724
|
+
* whose Session is proven dead terminalizes with an exact replacement
|
|
1725
|
+
* blocker; a live Session is reset for the existing delivery path, which
|
|
1726
|
+
* re-pushes the exact same input in the same pass.
|
|
1727
|
+
*/
|
|
1728
|
+
resolveDueProviderRetries(now, taskIds) {
|
|
1729
|
+
const due = this.listPendingProviderRetries().filter((entry) => (Date.parse(entry.nextAttemptAt) <= now.getTime()
|
|
1730
|
+
&& (taskIds === undefined || taskIds.has(entry.taskId))));
|
|
1731
|
+
const reopened = [];
|
|
1732
|
+
for (const entry of due) {
|
|
1733
|
+
this.store.transaction((store) => {
|
|
1734
|
+
const run = store.getAgentRun(entry.taskId, entry.runId);
|
|
1735
|
+
if (run === null
|
|
1736
|
+
|| run.status !== "active"
|
|
1737
|
+
|| run.providerRetry === undefined
|
|
1738
|
+
|| run.providerRetry.nextAttemptAt === undefined
|
|
1739
|
+
|| !providerRetryIsDue(run.providerRetry, now)) {
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
const sessions = store.getTaskRoleSessionSet(entry.taskId, entry.roleName);
|
|
1743
|
+
const session = sessions?.sessions[run.effective.agentId];
|
|
1744
|
+
const identityMatches = session !== undefined
|
|
1745
|
+
&& session.adapterId === run.effective.adapterId
|
|
1746
|
+
&& (run.providerRetry.nativeSessionId === undefined
|
|
1747
|
+
|| session.nativeSessionId === run.providerRetry.nativeSessionId)
|
|
1748
|
+
&& (run.providerRetry.launchId === undefined
|
|
1749
|
+
|| session.launchId === run.providerRetry.launchId);
|
|
1750
|
+
const sessionAlive = identityMatches
|
|
1751
|
+
&& session.status !== "stopped"
|
|
1752
|
+
&& session.status !== "broken";
|
|
1753
|
+
if (!sessionAlive) {
|
|
1754
|
+
// Session-dead: stop in-place retry and raise a precise, actionable
|
|
1755
|
+
// replacement blocker for the Leader.
|
|
1756
|
+
const summary = `Provider retry stopped: the original Session is gone `
|
|
1757
|
+
+ `(run ${run.id}, attempt ${run.providerRetry.attempt}, `
|
|
1758
|
+
+ `last error: ${run.providerRetry.lastErrorSummary}). `
|
|
1759
|
+
+ `A replacement Session requires an explicit Leader recovery decision.`;
|
|
1760
|
+
const result = terminalizeExactTaskRun(store, {
|
|
1761
|
+
taskId: entry.taskId,
|
|
1762
|
+
roleName: entry.roleName,
|
|
1763
|
+
agentId: run.effective.agentId,
|
|
1764
|
+
runId: run.id,
|
|
1765
|
+
receiptId: formatAgentRunReceiptId(entry.taskId, run.id),
|
|
1766
|
+
...(run.providerRetry.nativeSessionId === undefined
|
|
1767
|
+
? {}
|
|
1768
|
+
: { nativeSessionId: run.providerRetry.nativeSessionId }),
|
|
1769
|
+
...(run.providerRetry.launchId === undefined
|
|
1770
|
+
? {}
|
|
1771
|
+
: { launchId: run.providerRetry.launchId }),
|
|
1772
|
+
outcome: { status: "failed", summary }
|
|
1773
|
+
}, now);
|
|
1774
|
+
if (result.disposition !== "applied")
|
|
1775
|
+
return;
|
|
1776
|
+
store.saveEvent(entry.taskId, createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-session-dead", { runId: run.id, attempt: String(run.providerRetry.attempt) }, now));
|
|
1777
|
+
enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: "leader" }, "role-run-failed", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
// Reopen the Run on its original Session: reset transport markers,
|
|
1781
|
+
// switch to resume, and mark the projection in-flight. The delivery
|
|
1782
|
+
// pass re-pushes the exact same input in this same pass.
|
|
1783
|
+
store.saveAgentRun(reopenRunForProviderRetry(run, now));
|
|
1784
|
+
// The retry decision settled the original delivery claim; re-queue the
|
|
1785
|
+
// Role mailbox so the delivery pass can claim and re-push the Run.
|
|
1786
|
+
enqueueWork(store, { kind: "role", taskId: entry.taskId, roleName: entry.roleName }, "provider-retry-repush", now, [{ type: "run", taskId: entry.taskId, id: run.id }]);
|
|
1787
|
+
reopened.push(formatTaskRecordReference(entry.taskId, run.id, "agentRun"));
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
return reopened;
|
|
1791
|
+
}
|
|
1445
1792
|
/**
|
|
1446
1793
|
* Folds a provider SessionStart lifecycle fact
|
|
1447
1794
|
* through the canonical contract. Session-started/ready never advance Run
|
|
@@ -1596,22 +1943,29 @@ export class FileSchedulerStoreAdapter {
|
|
|
1596
1943
|
case "apply":
|
|
1597
1944
|
if (decision.outcome.outcome !== "advance-progress")
|
|
1598
1945
|
return "obsolete";
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1946
|
+
return routeProviderProgress({
|
|
1947
|
+
mode: this.telemetry?.mode ?? "legacy",
|
|
1948
|
+
entry: toTelemetryEntry(input),
|
|
1949
|
+
semanticExists: this.telemetry?.mode === "bounded"
|
|
1950
|
+
? false
|
|
1951
|
+
: store.listEvents(input.taskId).some((event) => (event.type === "runtime.provider-turn-progress"
|
|
1952
|
+
&& event.payload.eventId === input.eventId)),
|
|
1953
|
+
writeSemantic: () => {
|
|
1954
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-turn-progress", {
|
|
1955
|
+
eventId: input.eventId,
|
|
1956
|
+
roleName: input.roleName,
|
|
1957
|
+
agentId: input.agentId,
|
|
1958
|
+
adapterId: input.adapterId,
|
|
1959
|
+
launchId: input.launchId,
|
|
1960
|
+
nativeSessionId: input.nativeSessionId,
|
|
1961
|
+
runId: input.runId,
|
|
1962
|
+
progressId: input.progressId,
|
|
1963
|
+
progressAt: input.receivedAt,
|
|
1964
|
+
...(input.sequence === undefined ? {} : { sequence: String(input.sequence) })
|
|
1965
|
+
}, now));
|
|
1966
|
+
},
|
|
1967
|
+
sink: this.telemetry?.sink ?? NULL_TELEMETRY_SINK
|
|
1968
|
+
});
|
|
1615
1969
|
}
|
|
1616
1970
|
});
|
|
1617
1971
|
}
|
|
@@ -2102,7 +2456,7 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
2102
2456
|
const effective = input.effective === undefined
|
|
2103
2457
|
? resolvedEffective
|
|
2104
2458
|
: validateEffectiveLaunchSnapshot(input.effective);
|
|
2105
|
-
if (!
|
|
2459
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskMain(resolvedEffective, effective, store.getTaskWorkspace(input.taskId))) {
|
|
2106
2460
|
throw new Error("Reserved native Session effective launch changed before persistence.");
|
|
2107
2461
|
}
|
|
2108
2462
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
@@ -2115,7 +2469,7 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
2115
2469
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
2116
2470
|
policy: "fixed",
|
|
2117
2471
|
status: "running",
|
|
2118
|
-
effective
|
|
2472
|
+
effective: effectiveExisting?.effective ?? effective
|
|
2119
2473
|
}, now);
|
|
2120
2474
|
store.saveRoleSessionSet(updated);
|
|
2121
2475
|
return updated.sessions[input.agentId];
|
|
@@ -2174,17 +2528,20 @@ function mapRole(store, role) {
|
|
|
2174
2528
|
?? undefined;
|
|
2175
2529
|
const reopened = role.name === "leader"
|
|
2176
2530
|
&& store.getPendingWakeup(role.taskId)?.reasons.includes("task-reopened") === true;
|
|
2531
|
+
const liveSession = activeLiveRoleAgentSession(store.getTaskRoleSessionSet(role.taskId, role.name));
|
|
2177
2532
|
const effective = reopened
|
|
2178
2533
|
? resolveEffectiveLaunch({
|
|
2179
2534
|
role,
|
|
2180
2535
|
purpose: "execution",
|
|
2181
2536
|
...(workspace === undefined ? {} : { workspace })
|
|
2182
2537
|
})
|
|
2183
|
-
:
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2538
|
+
: liveSession !== null && workspace?.owner.type === "task"
|
|
2539
|
+
? effectiveLaunchWithTaskMainWorkspace(liveSession.effective, workspace)
|
|
2540
|
+
: liveSession?.effective ?? resolveEffectiveLaunch({
|
|
2541
|
+
role,
|
|
2542
|
+
purpose: "execution",
|
|
2543
|
+
...(workspace === undefined ? {} : { workspace })
|
|
2544
|
+
});
|
|
2188
2545
|
return {
|
|
2189
2546
|
taskId: role.taskId,
|
|
2190
2547
|
name: role.name,
|
|
@@ -2204,6 +2561,13 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
|
|
|
2204
2561
|
if (active.effective.agentId !== agentId) {
|
|
2205
2562
|
throw new Error(`Native Session registration does not match the effective Run Agent: ${taskId}/${roleName}.`);
|
|
2206
2563
|
}
|
|
2564
|
+
if (existing !== undefined) {
|
|
2565
|
+
const workspace = active.workspace ?? store.getTaskWorkspace(taskId);
|
|
2566
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskMain(existing.effective, active.effective, workspace)) {
|
|
2567
|
+
throw new Error(`Native Session effective launch does not match the active Run: ${taskId}/${roleName}.`);
|
|
2568
|
+
}
|
|
2569
|
+
return existing.effective;
|
|
2570
|
+
}
|
|
2207
2571
|
return active.effective;
|
|
2208
2572
|
}
|
|
2209
2573
|
if (existing !== undefined)
|
|
@@ -2370,3 +2734,31 @@ function runLaunchEventPayload(run) {
|
|
|
2370
2734
|
writeProjectIds: run.effective.writeProjectIds.join(",") || "none"
|
|
2371
2735
|
};
|
|
2372
2736
|
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Maps a provider progress observation to the telemetry sidecar entry. The
|
|
2739
|
+
* payload mirrors the semantic event payload so sidecar rows and legacy
|
|
2740
|
+
* events carry identical evidence.
|
|
2741
|
+
*/
|
|
2742
|
+
function toTelemetryEntry(input) {
|
|
2743
|
+
return {
|
|
2744
|
+
taskId: input.taskId,
|
|
2745
|
+
roleName: input.roleName,
|
|
2746
|
+
runId: input.runId,
|
|
2747
|
+
generation: input.launchId !== "" ? input.launchId : input.nativeSessionId,
|
|
2748
|
+
progressId: input.progressId,
|
|
2749
|
+
...(input.sequence === undefined ? {} : { sequence: input.sequence }),
|
|
2750
|
+
payload: {
|
|
2751
|
+
eventId: input.eventId,
|
|
2752
|
+
roleName: input.roleName,
|
|
2753
|
+
agentId: input.agentId,
|
|
2754
|
+
adapterId: input.adapterId,
|
|
2755
|
+
launchId: input.launchId,
|
|
2756
|
+
nativeSessionId: input.nativeSessionId,
|
|
2757
|
+
runId: input.runId,
|
|
2758
|
+
progressId: input.progressId,
|
|
2759
|
+
progressAt: input.receivedAt,
|
|
2760
|
+
...(input.sequence === undefined ? {} : { sequence: String(input.sequence) })
|
|
2761
|
+
},
|
|
2762
|
+
receivedAt: input.receivedAt
|
|
2763
|
+
};
|
|
2764
|
+
}
|