@zq-silk/yui 0.15.3 → 0.15.6
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/managedRuntimeEnvironment.js +3 -0
- package/dist/cli.js +35 -127
- package/dist/commands/executionAuditCommands.js +6 -0
- package/dist/commands/taskContextCommand.js +4 -2
- package/dist/context/sessionBootstrapManifest.js +12 -21
- package/dist/controller/clientRuntime.js +1 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +270 -162
- package/dist/controller/runtimeEventInbox.js +8 -0
- package/dist/controller/runtimeEventProcessor.js +31 -4
- package/dist/controller/runtimeHookTurnFence.js +101 -62
- package/dist/controller/runtimeLaunchCoordinator.js +38 -12
- package/dist/controller/runtimeObservationHook.js +17 -1
- package/dist/controller/structuredProviderObservation.js +39 -27
- package/dist/core/controllerClient.js +5 -0
- package/dist/core/controllerServer.js +7 -4
- package/dist/domain/agentResultTransport.js +2 -2
- package/dist/executor/agentExecutor.js +22 -38
- package/dist/executor/executorRegistry.js +16 -5
- package/dist/executor/fileRoleLaunchPlanner.js +22 -28
- package/dist/lifecycle/exactTurnTerminalization.js +3 -3
- package/dist/observability/executionAudit.js +12 -0
- package/dist/repository/executionLaneGitSnapshot.js +4 -3
- package/dist/repository/taskWorkspacePreparer.js +12 -4
- package/dist/review/taskFinalReviewContract.js +13 -32
- package/dist/runtime/agentError.js +299 -12
- package/dist/runtime/agentHost.js +419 -41
- package/dist/runtime/builtinAgentDrivers.js +5 -0
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/ports.js +16 -2
- package/dist/runtime/providerRuntimeIdentity.js +34 -28
- package/dist/runtime/runtimeCoherence.js +91 -0
- package/dist/runtime/runtimeObservation.js +8 -5
- package/dist/runtime/structuredProviderHost.js +53 -44
- package/dist/runtime/tmuxAdapters.js +72 -43
- package/dist/scheduler/activeRoleTurnDelivery.js +59 -11
- package/dist/scheduler/leaderWakeupProcessor.js +61 -7
- package/dist/storage/sqliteSchema.js +9 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/turn/turn.js +7 -1
- package/package.json +1 -1
- package/dist/runtime/exactControlPlane.js +0 -232
|
@@ -2,7 +2,6 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, createRoleSessionSet, recordRoleAgentSession, replaceTaskRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, detachRoleAgentSessionHost, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
4
|
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, managedProviderTurnId, clearProviderGoal, providerGoalContinues, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
5
|
-
import { hasRecentTurnId } from "../runtime/recentTurnIds.js";
|
|
6
5
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
7
6
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
8
7
|
import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
|
|
@@ -87,7 +86,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
87
86
|
if (adapterId === null) {
|
|
88
87
|
return this.recordObsoleteCanonicalObservation(input, "driver-or-turn-mismatch", now);
|
|
89
88
|
}
|
|
90
|
-
|
|
89
|
+
// Receipt dedupe is not business-result dedupe. Terminal folds revalidate
|
|
90
|
+
// their exact binding and commit result + notification + observation
|
|
91
|
+
// together, including a retry after a previously interrupted fold.
|
|
92
|
+
if (!isTurnTerminalObservation(input)
|
|
93
|
+
&& input.kind !== "turn.accepted" && input.kind !== "input.accepted"
|
|
94
|
+
&& hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
|
|
91
95
|
return "applied";
|
|
92
96
|
let outcome;
|
|
93
97
|
switch (input.kind) {
|
|
@@ -146,8 +150,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
146
150
|
default:
|
|
147
151
|
outcome = "obsolete";
|
|
148
152
|
}
|
|
149
|
-
if (outcome === "applied")
|
|
153
|
+
if (outcome === "applied" && !isTurnTerminalObservation(input)) {
|
|
150
154
|
this.persistRuntimeObservation(input, now);
|
|
155
|
+
}
|
|
151
156
|
return outcome;
|
|
152
157
|
}
|
|
153
158
|
adapterForRuntimeObservation(input) {
|
|
@@ -225,7 +230,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
225
230
|
: {
|
|
226
231
|
status: "failed",
|
|
227
232
|
diagnostic: input.payload.resultTransportDiagnostic,
|
|
228
|
-
failureReason: "
|
|
233
|
+
failureReason: "runtime-failed"
|
|
229
234
|
}
|
|
230
235
|
: {
|
|
231
236
|
status: "failed",
|
|
@@ -242,13 +247,16 @@ export class FileSchedulerStoreAdapter {
|
|
|
242
247
|
agentId: input.fence.agentId,
|
|
243
248
|
adapterId,
|
|
244
249
|
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
250
|
+
activationId: input.fence.activationId,
|
|
251
|
+
conversationId: input.fence.conversationId,
|
|
245
252
|
nativeSessionId: input.fence.nativeSessionId,
|
|
246
253
|
nativeTurnId: input.fence.nativeTurnId,
|
|
247
254
|
attemptId: input.fence.receiptId,
|
|
248
255
|
turnId: input.fence.turnId,
|
|
249
256
|
...(input.payload.input === undefined ? {} : { input: input.payload.input }),
|
|
250
257
|
providerStatus,
|
|
251
|
-
outcome
|
|
258
|
+
outcome,
|
|
259
|
+
observation: input
|
|
252
260
|
};
|
|
253
261
|
const classification = this.classifyRuntimeTurnTerminal(completed);
|
|
254
262
|
if (classification !== "apply")
|
|
@@ -882,9 +890,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
882
890
|
agentId: run?.effective.agentId ?? sessions.activeAgentId,
|
|
883
891
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
884
892
|
driverId: driver?.id ?? "unknown",
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
893
|
+
// No native Turn exists at submission resolution, and the Session
|
|
894
|
+
// facts may be absent. An unknown fact is an absent key: an empty
|
|
895
|
+
// string reads back as a real value and cannot be told apart from
|
|
896
|
+
// one the Provider genuinely reported.
|
|
897
|
+
...optionalEventFields({
|
|
898
|
+
runtimeGenerationId: session?.runtimeGenerationId,
|
|
899
|
+
nativeSessionId: session?.nativeSessionId
|
|
900
|
+
}),
|
|
888
901
|
source: error.source,
|
|
889
902
|
phase: error.phase,
|
|
890
903
|
category: error.category,
|
|
@@ -948,12 +961,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
948
961
|
}
|
|
949
962
|
recordAgentError(input, now) {
|
|
950
963
|
return this.store.transaction((store) => {
|
|
951
|
-
const duplicate = [...store.listEvents(input.taskId)].reverse().find((event) => (event.type === "runtime.agent-error"
|
|
952
|
-
&& event.payload.turnId === input.turnId
|
|
953
|
-
&& event.payload.phase === input.phase
|
|
954
|
-
&& event.payload.raw === input.raw));
|
|
955
|
-
if (duplicate !== undefined)
|
|
956
|
-
return duplicate.id;
|
|
957
964
|
const run = store.getTurn(input.taskId, input.turnId);
|
|
958
965
|
const sessionSet = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
959
966
|
const sessionAgentId = run?.effective.agentId ?? sessionSet?.activeAgentId;
|
|
@@ -979,16 +986,35 @@ export class FileSchedulerStoreAdapter {
|
|
|
979
986
|
? {}
|
|
980
987
|
: { sessionDisposition: input.sessionDisposition })
|
|
981
988
|
});
|
|
989
|
+
const duplicate = [...store.listEvents(input.taskId)].reverse().find((event) => (event.type === "runtime.agent-error"
|
|
990
|
+
&& event.payload.turnId === input.turnId
|
|
991
|
+
&& event.payload.roleName === input.roleName
|
|
992
|
+
&& event.payload.phase === input.phase
|
|
993
|
+
&& event.payload.attemptId === input.attemptId
|
|
994
|
+
&& event.payload.source === error.source
|
|
995
|
+
&& event.payload.inputDisposition === error.inputDisposition
|
|
996
|
+
&& event.payload.sessionDisposition === error.sessionDisposition
|
|
997
|
+
&& event.payload.registrationDisposition === input.registrationDisposition
|
|
998
|
+
&& event.payload.hostState === input.hostState
|
|
999
|
+
&& event.payload.expectedRuntimeGenerationId === input.expectedRuntimeGenerationId
|
|
1000
|
+
&& event.payload.observedRuntimeGenerationId === input.observedRuntimeGenerationId
|
|
1001
|
+
&& (input.attemptId === undefined
|
|
1002
|
+
? event.payload.raw === error.raw
|
|
1003
|
+
: event.payload.message === error.message
|
|
1004
|
+
&& event.payload.errorName === input.errorName
|
|
1005
|
+
&& event.payload.causeName === input.causeName)));
|
|
1006
|
+
// Re-reading one failed attempt may add a different caller stack, not a
|
|
1007
|
+
// new execution fact. Preserve its first full cause without multiplying
|
|
1008
|
+
// notifications; changed disposition/identity/diagnostic remains visible.
|
|
1009
|
+
if (duplicate !== undefined)
|
|
1010
|
+
return duplicate.id;
|
|
982
1011
|
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
|
|
983
|
-
sourceEventId: `${input.turnId}:${input.phase}`,
|
|
1012
|
+
sourceEventId: `${input.turnId}:${input.phase}${input.attemptId === undefined ? "" : `:${input.attemptId}`}`,
|
|
984
1013
|
turnId: input.turnId,
|
|
985
1014
|
roleName: input.roleName,
|
|
986
1015
|
agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
|
|
987
1016
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
988
1017
|
driverId: driver?.id ?? "unknown",
|
|
989
|
-
runtimeGenerationId: session?.runtimeGenerationId ?? "",
|
|
990
|
-
nativeSessionId: session?.nativeSessionId ?? "",
|
|
991
|
-
nativeTurnId: "",
|
|
992
1018
|
source: error.source,
|
|
993
1019
|
phase: error.phase,
|
|
994
1020
|
category: error.category,
|
|
@@ -996,7 +1022,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
996
1022
|
message: error.message,
|
|
997
1023
|
raw: error.raw,
|
|
998
1024
|
inputDisposition: error.inputDisposition,
|
|
999
|
-
sessionDisposition: error.sessionDisposition
|
|
1025
|
+
sessionDisposition: error.sessionDisposition,
|
|
1026
|
+
// Structured facts from the failing operation. Absent keys stay
|
|
1027
|
+
// absent rather than becoming an empty string, so a reader can tell
|
|
1028
|
+
// "the Host did not report this" from "the Host reported nothing".
|
|
1029
|
+
// The Session identities follow the same rule: this failure can
|
|
1030
|
+
// happen before any Session record exists, and there is no native
|
|
1031
|
+
// Turn to name at all.
|
|
1032
|
+
...optionalEventFields({
|
|
1033
|
+
runtimeGenerationId: session?.runtimeGenerationId,
|
|
1034
|
+
nativeSessionId: session?.nativeSessionId,
|
|
1035
|
+
errorName: input.errorName,
|
|
1036
|
+
causeName: input.causeName,
|
|
1037
|
+
hostState: input.hostState,
|
|
1038
|
+
expectedRuntimeGenerationId: input.expectedRuntimeGenerationId,
|
|
1039
|
+
observedRuntimeGenerationId: input.observedRuntimeGenerationId,
|
|
1040
|
+
attemptId: input.attemptId,
|
|
1041
|
+
registrationDisposition: input.registrationDisposition
|
|
1042
|
+
})
|
|
1000
1043
|
}, now);
|
|
1001
1044
|
store.saveEvent(input.taskId, event);
|
|
1002
1045
|
const leaderCannotReceive = input.roleName === "leader"
|
|
@@ -1558,129 +1601,98 @@ export class FileSchedulerStoreAdapter {
|
|
|
1558
1601
|
* for later mailbox input. It never performs tmux, workspace, or Controller I/O.
|
|
1559
1602
|
*/
|
|
1560
1603
|
classifyRuntimeTurnTerminal(input) {
|
|
1561
|
-
|
|
1562
|
-
const role = this.store.getRole(input.taskId, input.roleName);
|
|
1563
|
-
if (task === null || task.status === "archived" || role === null)
|
|
1564
|
-
return "obsolete";
|
|
1565
|
-
const sessions = this.store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1566
|
-
const existing = sessions?.sessions[input.agentId];
|
|
1567
|
-
const owner = {
|
|
1568
|
-
scope: "task",
|
|
1569
|
-
taskId: input.taskId,
|
|
1570
|
-
roleName: input.roleName
|
|
1571
|
-
};
|
|
1572
|
-
if (existing === undefined
|
|
1573
|
-
&& !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
|
|
1574
|
-
return "obsolete";
|
|
1575
|
-
try {
|
|
1576
|
-
const effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed Role session.");
|
|
1577
|
-
const effective = taskSessionEffective(this.store, input.taskId, input.roleName, input.agentId, effectiveExisting);
|
|
1578
|
-
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
1579
|
-
return "obsolete";
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
catch {
|
|
1583
|
-
return "obsolete";
|
|
1584
|
-
}
|
|
1585
|
-
const providerTurn = sessions?.providerBinding?.turn;
|
|
1586
|
-
const canonicalTurnId = sessions === null || sessions === undefined
|
|
1587
|
-
? input.nativeTurnId
|
|
1588
|
-
: canonicalStructuredProviderTurnId(sessions, input.nativeTurnId, input.attemptId);
|
|
1589
|
-
const recordedProviderTurn = providerTurn !== null
|
|
1590
|
-
&& providerTurn !== undefined
|
|
1591
|
-
&& providerTurn.nativeTurnId === input.nativeTurnId
|
|
1592
|
-
&& providerTurn.nativeTurnId === canonicalTurnId;
|
|
1593
|
-
if (recordedProviderTurn) {
|
|
1594
|
-
if (input.turnId === undefined)
|
|
1595
|
-
return "apply";
|
|
1596
|
-
const run = this.store.getTurn(input.taskId, input.turnId);
|
|
1597
|
-
return run === null ? "obsolete" : "apply";
|
|
1598
|
-
}
|
|
1599
|
-
const active = this.store.getActiveTurn(input.taskId, input.roleName);
|
|
1600
|
-
if (input.turnId === undefined)
|
|
1601
|
-
return active === null ? "apply" : "obsolete";
|
|
1602
|
-
return active?.id === input.turnId ? "apply" : "obsolete";
|
|
1604
|
+
return resolveTerminalExecution(this.store, input) === null ? "obsolete" : "apply";
|
|
1603
1605
|
}
|
|
1604
1606
|
observeRuntimeTurnTerminal(input, now = new Date()) {
|
|
1607
|
+
// Resource inspection is bounded but external to SQLite. Revalidate the
|
|
1608
|
+
// immutable Turn workspace after acquiring the write transaction.
|
|
1609
|
+
const preparedTurn = resolveTerminalExecution(this.store, input)?.turn;
|
|
1610
|
+
const workspace = preparedTurn?.status === "active"
|
|
1611
|
+
&& preparedTurn.executionGroupId !== undefined
|
|
1612
|
+
&& preparedTurn.executionLaneId !== undefined
|
|
1613
|
+
&& preparedTurn.workspace !== undefined
|
|
1614
|
+
&& input.outcome.status === "completed"
|
|
1615
|
+
? this.snapshotExecutionLaneWorkspace(this.store, preparedTurn.workspace)
|
|
1616
|
+
: undefined;
|
|
1605
1617
|
return this.store.transaction((store) => {
|
|
1606
1618
|
const task = store.getTask(input.taskId);
|
|
1607
1619
|
if (task === null)
|
|
1608
1620
|
throw new Error(`Task not found: ${input.taskId}.`);
|
|
1609
|
-
if (task.status
|
|
1621
|
+
if (task.status === "archived") {
|
|
1610
1622
|
throw new Error(`Cannot complete a runtime turn for unavailable Task: ${input.taskId}.`);
|
|
1611
1623
|
}
|
|
1612
|
-
const
|
|
1624
|
+
const resolved = resolveTerminalExecution(store, input);
|
|
1625
|
+
if (resolved === null)
|
|
1626
|
+
throw new Error("Runtime terminal has no exact execution binding.");
|
|
1627
|
+
const { turn: observedTurn, current: recordedProviderTurn, attemptId } = resolved;
|
|
1628
|
+
if (!isDeepStrictEqual(preparedTurn?.workspace, observedTurn?.workspace)) {
|
|
1629
|
+
throw new Error("Runtime terminal workspace changed during result preparation.");
|
|
1630
|
+
}
|
|
1631
|
+
if (workspace !== undefined && observedTurn?.workspace !== undefined
|
|
1632
|
+
&& !isDeepStrictEqual(store.getManagedWorkspace(observedTurn.workspace.owner), observedTurn.workspace)) {
|
|
1633
|
+
throw new Error("Runtime terminal workspace ownership changed during result preparation.");
|
|
1634
|
+
}
|
|
1613
1635
|
let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
|
|
1614
1636
|
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
1615
|
-
const canonicalTurnId =
|
|
1616
|
-
const providerTurn = sessions.providerBinding?.turn;
|
|
1617
|
-
const recordedProviderTurn = providerTurn !== null
|
|
1618
|
-
&& providerTurn !== undefined
|
|
1619
|
-
&& providerTurn.nativeTurnId === input.nativeTurnId
|
|
1620
|
-
&& providerTurn.nativeTurnId === canonicalTurnId;
|
|
1621
|
-
const observedTurn = input.turnId === undefined
|
|
1622
|
-
? store.listTurns(input.taskId).find((turn) => (turn.result?.provider?.nativeTurnId === canonicalTurnId)) ?? null
|
|
1623
|
-
: store.getTurn(input.taskId, input.turnId);
|
|
1637
|
+
const canonicalTurnId = input.nativeTurnId ?? resolved.nativeTurnId;
|
|
1624
1638
|
const existing = sessions.sessions[input.agentId];
|
|
1625
1639
|
const owner = {
|
|
1626
1640
|
scope: "task",
|
|
1627
1641
|
taskId: input.taskId,
|
|
1628
1642
|
roleName: input.roleName
|
|
1629
1643
|
};
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1644
|
+
assertConsistentTerminal(store, input, observedTurn);
|
|
1645
|
+
// A historical operation failure stays failed. The immutable terminal
|
|
1646
|
+
// observation retains the late report for explicit Leader adoption.
|
|
1647
|
+
// Never touch a successor Session, active pointer, mailbox or Review.
|
|
1648
|
+
if (!recordedProviderTurn || (observedTurn !== null && observedTurn.status !== "active")) {
|
|
1649
|
+
// A failed operation and a subsequently confirmed Provider outcome are
|
|
1650
|
+
// distinct facts. Settle only the still-owned original attempt, never
|
|
1651
|
+
// rewriting the historical Task result or a successor's binding.
|
|
1652
|
+
if (recordedProviderTurn && sessions.providerBinding?.turn !== null
|
|
1653
|
+
&& sessions.providerBinding?.turn !== undefined
|
|
1654
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(sessions.providerBinding.turn.status)) {
|
|
1655
|
+
if (sessions.providerBinding.turn.status !== "accepted") {
|
|
1656
|
+
sessions = updateTaskRoleProviderRuntime(sessions, acceptProviderTurn(sessions.providerBinding, {
|
|
1657
|
+
attemptId,
|
|
1658
|
+
...(canonicalTurnId === undefined ? {} : { nativeTurnId: canonicalTurnId }),
|
|
1659
|
+
acceptedAt: now.toISOString()
|
|
1660
|
+
}), now);
|
|
1661
|
+
}
|
|
1662
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus, now, attemptId);
|
|
1663
|
+
store.saveTaskRoleSessionSet(sessions);
|
|
1664
|
+
}
|
|
1665
|
+
if (input.observation !== undefined)
|
|
1666
|
+
this.persistRuntimeObservation(input.observation, now);
|
|
1641
1667
|
return {
|
|
1642
|
-
|
|
1668
|
+
...(sessions.sessions[input.agentId] === undefined
|
|
1669
|
+
? {} : { session: sessions.sessions[input.agentId] }),
|
|
1643
1670
|
duplicate: true,
|
|
1644
1671
|
...(observedTurn === null ? {} : { turn: observedTurn })
|
|
1645
1672
|
};
|
|
1646
1673
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
&& observedTurn?.id !== input.turnId) {
|
|
1655
|
-
return { session: effectiveExisting, duplicate: false, disposition: "obsolete" };
|
|
1674
|
+
const pending = sessions.providerBinding;
|
|
1675
|
+
if (pending.turn?.status === "submitting" || pending.turn?.status === "delivery-unknown") {
|
|
1676
|
+
sessions = updateTaskRoleProviderRuntime(sessions, acceptProviderTurn(pending, {
|
|
1677
|
+
attemptId,
|
|
1678
|
+
...(canonicalTurnId === undefined ? {} : { nativeTurnId: canonicalTurnId }),
|
|
1679
|
+
acceptedAt: now.toISOString()
|
|
1680
|
+
}), now);
|
|
1656
1681
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
status: sessionStatus,
|
|
1665
|
-
...(effectiveExisting?.endReason === undefined
|
|
1666
|
-
? {}
|
|
1667
|
-
: { endReason: effectiveExisting.endReason }),
|
|
1668
|
-
effective
|
|
1669
|
-
}, now);
|
|
1670
|
-
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus, now);
|
|
1671
|
-
sessions = recordTaskRoleTurnBoundary(sessions, {
|
|
1672
|
-
agentId: input.agentId,
|
|
1673
|
-
nativeSessionId: input.nativeSessionId,
|
|
1674
|
-
turnId: canonicalTurnId
|
|
1675
|
-
}, now);
|
|
1682
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus, now, attemptId);
|
|
1683
|
+
if (canonicalTurnId !== undefined)
|
|
1684
|
+
sessions = recordTaskRoleTurnBoundary(sessions, {
|
|
1685
|
+
agentId: input.agentId,
|
|
1686
|
+
nativeSessionId: input.nativeSessionId,
|
|
1687
|
+
turnId: canonicalTurnId
|
|
1688
|
+
}, now);
|
|
1676
1689
|
store.saveTaskRoleSessionSet(sessions);
|
|
1677
1690
|
completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
|
|
1678
1691
|
let terminalTurn;
|
|
1679
1692
|
if (recordedProviderTurn && observedTurn?.status === "active") {
|
|
1680
1693
|
const binding = sessions.providerBinding;
|
|
1681
|
-
const conversation =
|
|
1682
|
-
const activation = binding.activations.find((entry) => (entry.activationId ===
|
|
1683
|
-
|| (entry.conversationId === conversation.conversationId && entry.status === "active")));
|
|
1694
|
+
const conversation = binding.conversations.find((entry) => entry.conversationId === input.nativeSessionId);
|
|
1695
|
+
const activation = binding.activations.find((entry) => (entry.activationId === resolved.activationId));
|
|
1684
1696
|
if (activation === undefined) {
|
|
1685
1697
|
throw new Error("Provider Turn result has no matching Activation.");
|
|
1686
1698
|
}
|
|
@@ -1692,7 +1704,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1692
1704
|
&& observedTurn.executionLaneId !== undefined
|
|
1693
1705
|
&& observedTurn.workspace !== undefined
|
|
1694
1706
|
&& input.outcome.status === "completed") {
|
|
1695
|
-
const gitSnapshot =
|
|
1707
|
+
const gitSnapshot = workspace;
|
|
1696
1708
|
if (gitSnapshot.status === "captured") {
|
|
1697
1709
|
systemEvidence = { workspaceSnapshot: gitSnapshot.snapshot };
|
|
1698
1710
|
}
|
|
@@ -1721,7 +1733,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1721
1733
|
accountScope: binding.accountScope,
|
|
1722
1734
|
conversationId: conversation.conversationId,
|
|
1723
1735
|
activationId: activation.activationId,
|
|
1724
|
-
nativeTurnId: canonicalTurnId,
|
|
1736
|
+
...(canonicalTurnId === undefined ? {} : { nativeTurnId: canonicalTurnId }),
|
|
1737
|
+
attemptId,
|
|
1725
1738
|
status: providerStatus
|
|
1726
1739
|
}
|
|
1727
1740
|
},
|
|
@@ -1735,7 +1748,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1735
1748
|
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, terminalTurn.status === "completed" ? "turn.completed" : "turn.failed", {
|
|
1736
1749
|
turnId: terminalTurn.id,
|
|
1737
1750
|
roleName: terminalTurn.roleName,
|
|
1738
|
-
providerTurnId: canonicalTurnId,
|
|
1751
|
+
...(canonicalTurnId === undefined ? {} : { providerTurnId: canonicalTurnId }),
|
|
1739
1752
|
providerStatus
|
|
1740
1753
|
}, now);
|
|
1741
1754
|
store.saveEvent(input.taskId, event);
|
|
@@ -1744,6 +1757,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1744
1757
|
routeRoleEvent(store, event, terminalTurn.roleName, terminalTurn.purpose === "review" ? "review-result" : "role-turn-result", now);
|
|
1745
1758
|
}
|
|
1746
1759
|
}
|
|
1760
|
+
if (input.observation !== undefined)
|
|
1761
|
+
this.persistRuntimeObservation(input.observation, now);
|
|
1747
1762
|
return {
|
|
1748
1763
|
session: sessions.sessions[input.agentId],
|
|
1749
1764
|
duplicate: false,
|
|
@@ -1976,6 +1991,27 @@ export class FileSchedulerStoreAdapter {
|
|
|
1976
1991
|
return this.store.transaction((store) => {
|
|
1977
1992
|
const continuation = input.fence.receiptId?.startsWith("turn-input:") === true;
|
|
1978
1993
|
const ordinaryAttemptId = input.fence.receiptId;
|
|
1994
|
+
if (!continuation && input.fence.turnId !== undefined) {
|
|
1995
|
+
const exact = resolveTerminalExecution(store, {
|
|
1996
|
+
taskId: input.fence.taskId,
|
|
1997
|
+
roleName: input.fence.roleName,
|
|
1998
|
+
agentId: input.fence.agentId,
|
|
1999
|
+
adapterId,
|
|
2000
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2001
|
+
activationId: input.fence.activationId,
|
|
2002
|
+
conversationId: input.fence.conversationId,
|
|
2003
|
+
nativeSessionId: input.fence.nativeSessionId,
|
|
2004
|
+
nativeTurnId: input.fence.nativeTurnId,
|
|
2005
|
+
attemptId: ordinaryAttemptId,
|
|
2006
|
+
turnId: input.fence.turnId
|
|
2007
|
+
});
|
|
2008
|
+
if (exact === null)
|
|
2009
|
+
return "obsolete";
|
|
2010
|
+
// A terminal can prove acceptance before the delayed receipt. Retain
|
|
2011
|
+
// the receipt without recreating lifecycle state or touching a successor.
|
|
2012
|
+
if (!exact.current || exact.turn?.status !== "active")
|
|
2013
|
+
return "applied";
|
|
2014
|
+
}
|
|
1979
2015
|
if (input.fence.turnId === undefined
|
|
1980
2016
|
&& ordinaryAttemptId?.startsWith("direct:") === true) {
|
|
1981
2017
|
const taskId = input.fence.taskId;
|
|
@@ -2377,34 +2413,6 @@ function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
|
2377
2413
|
reason
|
|
2378
2414
|
}, now));
|
|
2379
2415
|
}
|
|
2380
|
-
function isLeaderDisposedWorkItemTurn(store, input) {
|
|
2381
|
-
if (input.turnId === undefined)
|
|
2382
|
-
return false;
|
|
2383
|
-
const run = store.getTurn(input.taskId, input.turnId);
|
|
2384
|
-
if (run === null
|
|
2385
|
-
|| run.status !== "failed"
|
|
2386
|
-
|| run.roleName !== input.roleName
|
|
2387
|
-
|| run.effective.agentId !== input.agentId
|
|
2388
|
-
|| run.workItemId === undefined) {
|
|
2389
|
-
return false;
|
|
2390
|
-
}
|
|
2391
|
-
return store.getWorkItem(input.taskId, run.workItemId)?.disposition !== undefined;
|
|
2392
|
-
}
|
|
2393
|
-
function isObsoleteTerminalRuntimeTurn(store, input) {
|
|
2394
|
-
if (isLeaderDisposedWorkItemTurn(store, input))
|
|
2395
|
-
return true;
|
|
2396
|
-
if (input.turnId === undefined)
|
|
2397
|
-
return false;
|
|
2398
|
-
const run = store.getTurn(input.taskId, input.turnId);
|
|
2399
|
-
if (run === null
|
|
2400
|
-
|| run.status !== "failed"
|
|
2401
|
-
|| run.roleName !== input.roleName
|
|
2402
|
-
|| run.effective.agentId !== input.agentId) {
|
|
2403
|
-
return false;
|
|
2404
|
-
}
|
|
2405
|
-
return store.listEvents(input.taskId).some((event) => (event.type === "runtime.role-delivery-failed"
|
|
2406
|
-
&& event.payload.turnId === input.turnId));
|
|
2407
|
-
}
|
|
2408
2416
|
function sessionPreview(value) {
|
|
2409
2417
|
const normalized = value.trim().replaceAll(/\s+/g, " ");
|
|
2410
2418
|
const truncated = normalized.slice(0, 1_024);
|
|
@@ -2811,6 +2819,14 @@ function turnLaunchEventPayload(turn) {
|
|
|
2811
2819
|
writeProjectIds: turn.effective.writeProjectIds.join(",") || "none"
|
|
2812
2820
|
};
|
|
2813
2821
|
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Keeps only the fields the caller actually knew. Event payloads are a
|
|
2824
|
+
* string map, so an unknown fact has to be an absent key: writing `""` would
|
|
2825
|
+
* claim the Host reported an empty value.
|
|
2826
|
+
*/
|
|
2827
|
+
function optionalEventFields(fields) {
|
|
2828
|
+
return Object.fromEntries(Object.entries(fields).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0));
|
|
2829
|
+
}
|
|
2814
2830
|
function runtimeObservationTelemetryEntry(input) {
|
|
2815
2831
|
return {
|
|
2816
2832
|
taskId: input.fence.taskId,
|
|
@@ -2907,7 +2923,7 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
|
2907
2923
|
const current = sessions.providerBinding;
|
|
2908
2924
|
const attemptId = input.fence.receiptId;
|
|
2909
2925
|
const turnId = input.fence.nativeTurnId;
|
|
2910
|
-
if (current === null || attemptId === undefined
|
|
2926
|
+
if (current === null || attemptId === undefined)
|
|
2911
2927
|
return sessions;
|
|
2912
2928
|
if (current.turn === null
|
|
2913
2929
|
|| current.turn.turnId !== input.fence.turnId)
|
|
@@ -2922,19 +2938,109 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
|
2922
2938
|
});
|
|
2923
2939
|
return updateTaskRoleProviderRuntime(sessions, binding, now);
|
|
2924
2940
|
}
|
|
2925
|
-
/**
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2941
|
+
/** One correlation boundary shared by classification and the committing fold. */
|
|
2942
|
+
function resolveTerminalExecution(store, input) {
|
|
2943
|
+
const task = store.getTask(input.taskId);
|
|
2944
|
+
if (task === null || task.status === "archived")
|
|
2945
|
+
return null;
|
|
2946
|
+
if (input.conversationId !== undefined && input.conversationId !== input.nativeSessionId)
|
|
2947
|
+
return null;
|
|
2948
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
2949
|
+
const session = sessions?.sessions[input.agentId];
|
|
2950
|
+
const binding = sessions?.providerBinding;
|
|
2951
|
+
const pending = binding?.turn;
|
|
2952
|
+
const evidence = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
|
|
2953
|
+
.filter((event) => event !== null
|
|
2954
|
+
&& (event.kind === "turn.accepted" || isTurnTerminalObservation(event)));
|
|
2955
|
+
if (input.attemptId !== undefined && evidence.some((event) => (event.fence.roleName === input.roleName
|
|
2956
|
+
&& event.fence.agentId === input.agentId
|
|
2957
|
+
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
2958
|
+
&& event.fence.receiptId === input.attemptId
|
|
2959
|
+
&& (event.fence.runtimeGenerationId !== input.runtimeGenerationId
|
|
2960
|
+
&& input.runtimeGenerationId !== undefined
|
|
2961
|
+
|| input.nativeTurnId !== undefined && event.fence.nativeTurnId !== undefined
|
|
2962
|
+
&& event.fence.nativeTurnId !== input.nativeTurnId))))
|
|
2963
|
+
return null;
|
|
2964
|
+
const identityMatches = (attemptId, nativeTurnId) => ((input.attemptId === undefined
|
|
2965
|
+
? input.nativeTurnId !== undefined && input.nativeTurnId === nativeTurnId
|
|
2966
|
+
: input.attemptId === attemptId)
|
|
2967
|
+
&& !(input.nativeTurnId !== undefined && nativeTurnId !== undefined
|
|
2968
|
+
&& input.nativeTurnId !== nativeTurnId));
|
|
2969
|
+
if (binding !== null && binding !== undefined && pending !== null && pending !== undefined
|
|
2970
|
+
&& session?.adapterId === input.adapterId
|
|
2971
|
+
&& session.nativeSessionId === input.nativeSessionId
|
|
2972
|
+
&& (input.runtimeGenerationId === undefined
|
|
2973
|
+
|| (pending.activationId ?? session.runtimeGenerationId) === input.runtimeGenerationId)
|
|
2974
|
+
&& currentProviderConversation(binding).conversationId === input.nativeSessionId
|
|
2975
|
+
&& identityMatches(pending.attemptId, pending.nativeTurnId)
|
|
2976
|
+
&& (input.turnId === undefined || input.turnId === pending.turnId)
|
|
2977
|
+
&& pending.status !== "rejected") {
|
|
2978
|
+
const activation = binding.activations.find((entry) => (entry.activationId === (input.runtimeGenerationId ?? pending.activationId ?? session.runtimeGenerationId)
|
|
2979
|
+
&& (input.activationId === undefined || entry.activationId === input.activationId)
|
|
2980
|
+
&& entry.conversationId === input.nativeSessionId));
|
|
2981
|
+
const turn = pending.turnId === undefined ? null : store.getTurn(input.taskId, pending.turnId);
|
|
2982
|
+
if (activation !== undefined && (pending.turnId === undefined || (turn !== null && turn.roleName === input.roleName
|
|
2983
|
+
&& turn.effective.agentId === input.agentId && turn.effective.adapterId === input.adapterId)))
|
|
2984
|
+
return {
|
|
2985
|
+
turn, current: true, attemptId: pending.attemptId,
|
|
2986
|
+
nativeTurnId: pending.nativeTurnId, activationId: activation.activationId
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
// Old results use immutable acceptance/terminal evidence, not today's Role
|
|
2990
|
+
// config, Session status, current generation or active Turn pointer.
|
|
2991
|
+
const matches = evidence.filter((event) => event.fence.roleName === input.roleName
|
|
2992
|
+
&& event.fence.agentId === input.agentId
|
|
2993
|
+
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
2994
|
+
&& input.runtimeGenerationId !== undefined
|
|
2995
|
+
&& event.fence.runtimeGenerationId === input.runtimeGenerationId
|
|
2996
|
+
&& (input.activationId === undefined
|
|
2997
|
+
|| (event.fence.activationId ?? event.fence.runtimeGenerationId) === input.activationId)
|
|
2998
|
+
&& identityMatches(event.fence.receiptId, event.fence.nativeTurnId)
|
|
2999
|
+
&& event.fence.turnId !== undefined
|
|
3000
|
+
&& (input.turnId === undefined || event.fence.turnId === input.turnId));
|
|
3001
|
+
const accepted = matches[0];
|
|
3002
|
+
if (accepted === undefined || accepted.fence.receiptId === undefined)
|
|
3003
|
+
return null;
|
|
3004
|
+
if (matches.some((event) => event.fence.turnId !== accepted.fence.turnId
|
|
3005
|
+
|| event.fence.receiptId !== accepted.fence.receiptId)) {
|
|
3006
|
+
throw new Error("Runtime terminal has conflicting durable execution bindings.");
|
|
3007
|
+
}
|
|
3008
|
+
const turn = store.getTurn(input.taskId, accepted.fence.turnId);
|
|
3009
|
+
if (turn === null || turn.effective.adapterId !== input.adapterId
|
|
3010
|
+
|| turn.effective.agentId !== input.agentId || turn.roleName !== input.roleName)
|
|
3011
|
+
return null;
|
|
3012
|
+
return {
|
|
3013
|
+
turn, current: false, attemptId: accepted.fence.receiptId,
|
|
3014
|
+
nativeTurnId: accepted.fence.nativeTurnId,
|
|
3015
|
+
activationId: accepted.fence.activationId ?? accepted.fence.runtimeGenerationId
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
function isTurnTerminalObservation(input) {
|
|
3019
|
+
return ["turn.completed", "turn.failed", "turn.cancelled"].includes(input.kind);
|
|
3020
|
+
}
|
|
3021
|
+
function assertConsistentTerminal(store, input, turn) {
|
|
3022
|
+
const previous = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
|
|
3023
|
+
.filter((event) => event !== null
|
|
3024
|
+
&& isTurnTerminalObservation(event)
|
|
3025
|
+
&& event.fence.roleName === input.roleName
|
|
3026
|
+
&& event.fence.agentId === input.agentId
|
|
3027
|
+
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
3028
|
+
&& event.fence.runtimeGenerationId === input.runtimeGenerationId
|
|
3029
|
+
&& (input.attemptId === undefined
|
|
3030
|
+
? input.nativeTurnId !== undefined && event.fence.nativeTurnId === input.nativeTurnId
|
|
3031
|
+
: event.fence.receiptId === input.attemptId));
|
|
3032
|
+
if (input.observation !== undefined && previous.some((event) => (event.kind !== input.observation.kind
|
|
3033
|
+
|| !isDeepStrictEqual(event.payload, input.observation.payload)
|
|
3034
|
+
|| event.fence.turnId !== input.observation.fence.turnId)))
|
|
3035
|
+
throw new Error("Conflicting terminal content for the same execution.");
|
|
3036
|
+
if (turn?.result?.provider !== undefined) {
|
|
3037
|
+
if (turn.result.provider.status !== input.providerStatus
|
|
3038
|
+
|| (input.outcome.status === "completed" && turn.result.output !== input.outcome.output)
|
|
3039
|
+
|| (previous.length === 0 && input.outcome.status === "failed"
|
|
3040
|
+
&& turn.result.diagnostic !== input.outcome.diagnostic)) {
|
|
3041
|
+
throw new Error("Conflicting result for the same execution.");
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
2938
3044
|
}
|
|
2939
3045
|
function terminalSessionReplacementBasis(sessions, input, run) {
|
|
2940
3046
|
const binding = sessions.providerBinding;
|
|
@@ -3012,15 +3118,17 @@ function terminalProviderReplacementBasis(sessions, input, mode) {
|
|
|
3012
3118
|
&& session.nativeSessionId !== current.nativeSessionId));
|
|
3013
3119
|
return replaced === undefined ? undefined : "terminal-session";
|
|
3014
3120
|
}
|
|
3015
|
-
function settleStructuredProviderTurn(sessions, turnId, status, now) {
|
|
3121
|
+
function settleStructuredProviderTurn(sessions, turnId, status, now, attemptId) {
|
|
3016
3122
|
const binding = sessions.providerBinding;
|
|
3017
|
-
if (binding === null || binding.turn === null
|
|
3123
|
+
if (binding === null || binding.turn === null
|
|
3124
|
+
|| (attemptId === undefined ? binding.turn.nativeTurnId !== turnId : binding.turn.attemptId !== attemptId)) {
|
|
3018
3125
|
return sessions;
|
|
3019
3126
|
}
|
|
3020
3127
|
if (["completed", "failed", "cancelled"].includes(binding.turn.status))
|
|
3021
3128
|
return sessions;
|
|
3022
3129
|
return updateTaskRoleProviderRuntime(sessions, settleProviderTurn(binding, {
|
|
3023
3130
|
nativeTurnId: turnId,
|
|
3131
|
+
...(attemptId === undefined ? {} : { attemptId }),
|
|
3024
3132
|
status,
|
|
3025
3133
|
settledAt: now.toISOString()
|
|
3026
3134
|
}), now);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
3
|
import { closeSync, constants, existsSync, fchmodSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { createRuntimeObservation, isRuntimeTokenEvidence } from "../runtime/runtimeObservation.js";
|
|
@@ -488,6 +489,13 @@ function assertEventId(id) {
|
|
|
488
489
|
throw invalidEvent("Runtime event id is invalid.");
|
|
489
490
|
}
|
|
490
491
|
function hasSameIdentity(left, right) {
|
|
492
|
+
if (left.type === "runtime-observation" && right.type === "runtime-observation"
|
|
493
|
+
&& ["turn.completed", "turn.failed", "turn.cancelled"].includes(left.observation.kind)) {
|
|
494
|
+
return left.id === right.id
|
|
495
|
+
&& left.observation.kind === right.observation.kind
|
|
496
|
+
&& isDeepStrictEqual(left.observation.fence, right.observation.fence)
|
|
497
|
+
&& isDeepStrictEqual(left.observation.payload, right.observation.payload);
|
|
498
|
+
}
|
|
491
499
|
return left.id === right.id
|
|
492
500
|
&& left.type === right.type
|
|
493
501
|
&& left.scope === right.scope
|