@pasko70/pibo 2.5.2 → 3.0.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/context/pibo-native-tooling.md +1 -0
- package/dist/agent-runtime/contract.js +2 -1
- package/dist/agent-runtime/history.js +53 -1
- package/dist/agent-runtime/routed-session.js +100 -26
- package/dist/agent-runtimes/codex-native/adapter.js +304 -30
- package/dist/agent-runtimes/codex-native/client.js +22 -3
- package/dist/agent-runtimes/codex-native/first-use.js +294 -0
- package/dist/agent-runtimes/codex-native/history.js +26 -7
- package/dist/agent-runtimes/codex-native/process.js +14 -0
- package/dist/agent-runtimes/codex-native/thread.js +59 -25
- package/dist/agent-runtimes/codex-native/turn.js +27 -0
- package/dist/agent-runtimes/history-proof.js +8 -0
- package/dist/agent-runtimes/omp/adapter.js +61 -14
- package/dist/agent-runtimes/omp/history.js +189 -34
- package/dist/agent-runtimes/omp/thread.js +82 -36
- package/dist/agent-runtimes/pi/adapter.js +30 -3
- package/dist/agent-runtimes/pi/history.js +66 -5
- package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
- package/dist/agent-runtimes/pi/routed-session.js +344 -37
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- package/dist/apps/chat/chat-settings-routes.js +27 -2
- package/dist/apps/chat/chat-speech.js +100 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -5
- package/dist/apps/chat/data/timeline-query-service.js +89 -1
- package/dist/apps/chat/trace-v2.js +210 -8
- package/dist/apps/chat/trace.js +3 -0
- package/dist/apps/chat/web-app.js +108 -3
- package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
- package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
- package/dist/cli.js +49 -5
- package/dist/config/config.js +59 -8
- package/dist/core/preview-server-settings.js +20 -0
- package/dist/core/session-router.js +457 -37
- package/dist/core/user-settings.js +10 -0
- package/dist/data/payload-store.js +57 -1
- package/dist/data/schema.js +18 -1
- package/dist/debug/trace.js +5 -0
- package/dist/gateway/client.js +368 -65
- package/dist/gateway/protocol.js +2 -0
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/web.js +2 -0
- package/dist/index.js +1 -1
- package/dist/loops/store.js +6 -4
- package/dist/mcp/config.js +89 -17
- package/dist/mcp/daemon-client.js +473 -153
- package/dist/mcp/daemon.js +401 -37
- package/dist/plugins/builtin.js +2 -2
- package/dist/plugins/codex-native.js +2 -0
- package/dist/plugins/registry.js +259 -1
- package/dist/previews/cli.js +345 -0
- package/dist/previews/config.js +61 -0
- package/dist/previews/manager.js +644 -0
- package/dist/previews/network.js +198 -0
- package/dist/previews/plugin.js +11 -0
- package/dist/previews/process-supervisor.js +38 -0
- package/dist/previews/proxy.js +387 -0
- package/dist/previews/store.js +750 -0
- package/dist/previews/types.js +1 -0
- package/dist/previews/web-app.js +446 -0
- package/dist/runs/registry.js +22 -0
- package/dist/runs/tools.js +46 -7
- package/dist/session-ui/terminalRows.js +187 -13
- package/dist/sessions/pibo-data-store.js +37 -0
- package/dist/sessions/runtime-binding-persistence.js +34 -0
- package/dist/sessions/runtime-binding.js +6 -0
- package/dist/sessions/sqlite-store.js +37 -0
- package/dist/shared/trace-engine.js +12 -64
- package/dist/shared/trace-event-projection.js +16 -0
- package/dist/shared/trace-history.js +684 -184
- package/dist/shared/trace-limits.js +7 -0
- package/dist/shared/trace-live-patch.js +61 -0
- package/dist/shared/trace-tool-identity.js +43 -0
- package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
- package/dist/speech/openai-codex.js +407 -0
- package/dist/speech/types.js +8 -0
- package/dist/subagents/context.js +5 -3
- package/dist/subagents/observations.js +94 -6
- package/dist/subagents/tool.js +103 -20
- package/dist/tools/codex-image-artifacts.js +24 -0
- package/dist/tools/codex-image-generation.js +4 -22
- package/dist/web/channel.js +88 -4
- package/dist/web/http.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
- package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { historyReconciliationDigest, historyReconciliationEntrySignature, } from "../agent-runtime/history.js";
|
|
1
2
|
import { historyTraceOrder } from "./trace-order.js";
|
|
2
3
|
import { attachAsyncAgentRunNode, reconcileAsyncAgentRunStatuses } from "./trace-async-agent-runs.js";
|
|
3
4
|
import { sortTraceNodes } from "./trace-nodes.js";
|
|
4
5
|
import { createRunNotificationNode, parseRunNotificationText } from "./trace-run-notifications.js";
|
|
5
6
|
import { isSubagentToolName } from "./trace-subagent-links.js";
|
|
6
7
|
import { assistantMessageNodeId, messageTurnNodeId, thinkingNodeId } from "./trace-event-projection.js";
|
|
7
|
-
|
|
8
|
+
import { TRACE_RECONCILIATION_ENTRY_CAP, TRACE_RECONCILIATION_TIMING_CAP } from "./trace-limits.js";
|
|
9
|
+
import { qualifiedHistoryToolNodeId } from "./trace-tool-identity.js";
|
|
10
|
+
const MAX_CONFIDENT_TIMESTAMP_DISTANCE_MS = 5 * 60 * 1_000;
|
|
11
|
+
const PERSISTED_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|([+-])(\d{2}):(\d{2}))$/;
|
|
12
|
+
export function projectHistoryEntries(entries, sessionStatus, openHistoryEventIds, turnTimings = [], reconciliationProof, reconciliationAuthoritative = false) {
|
|
8
13
|
if (sessionStatus !== "running" || openHistoryEventIds.size === 0)
|
|
9
14
|
return [...entries];
|
|
10
|
-
const
|
|
15
|
+
const { userAssignments, userFallbackEventIds } = assignHistoryTimings(entries, turnTimings, reconciliationProof, reconciliationAuthoritative);
|
|
11
16
|
let lastUserMessageIndex = -1;
|
|
12
17
|
let lastUserEventId;
|
|
13
18
|
for (let index = 0; index < entries.length; index += 1) {
|
|
@@ -15,32 +20,42 @@ export function projectHistoryEntries(entries, sessionStatus, openHistoryEventId
|
|
|
15
20
|
if (entry.type !== "message" || entry.role !== "user")
|
|
16
21
|
continue;
|
|
17
22
|
lastUserMessageIndex = index;
|
|
18
|
-
lastUserEventId = entry.
|
|
19
|
-
? entry.turnId
|
|
20
|
-
: userTimingAssignments.get(index)?.eventId;
|
|
23
|
+
lastUserEventId = canonicalProductTurnId(entry, userAssignments.get(index), userFallbackEventIds.get(index));
|
|
21
24
|
}
|
|
22
25
|
return lastUserMessageIndex !== -1 && lastUserEventId && openHistoryEventIds.has(lastUserEventId)
|
|
23
26
|
? entries.slice(0, lastUserMessageIndex)
|
|
24
27
|
: [...entries];
|
|
25
28
|
}
|
|
26
|
-
export function traceNodesFromHistoryEntries(piboSessionId, entries, turnTimings = []) {
|
|
29
|
+
export function traceNodesFromHistoryEntries(piboSessionId, entries, turnTimings = [], reconciliationProof, reconciliationAuthoritative = false) {
|
|
27
30
|
const nodes = [];
|
|
28
|
-
const
|
|
29
|
-
const
|
|
31
|
+
const { userAssignments, turnAssignments, userFallbackEventIds, turnFallbackEventIds, turnProjectionStates } = assignHistoryTimings(entries, turnTimings, reconciliationProof, reconciliationAuthoritative);
|
|
32
|
+
const qualifiedNativeToolCallIds = nativeToolCallIdsRequiringQualification(entries, reconciliationProof);
|
|
33
|
+
const projectionStateByEventId = new Map();
|
|
30
34
|
let assistantTurnIndex = 0;
|
|
31
35
|
for (let index = 0; index < entries.length; index += 1) {
|
|
32
36
|
const entry = entries[index];
|
|
33
37
|
if (entry.type === "message") {
|
|
34
38
|
if (entry.role === "user") {
|
|
35
|
-
nodes.push(createUserMessageNode(piboSessionId, entry, index,
|
|
39
|
+
nodes.push(createUserMessageNode(piboSessionId, entry, index, userAssignments.get(index), userFallbackEventIds.get(index)));
|
|
36
40
|
}
|
|
37
41
|
else if (entry.role === "assistant" || entry.role === "tool") {
|
|
38
42
|
const turn = collectAssistantTurn(entries, index);
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const timing = turnAssignments[assistantTurnIndex];
|
|
44
|
+
const fallbackEventId = turnFallbackEventIds.get(assistantTurnIndex);
|
|
45
|
+
const identityEntry = turn.entries.find(({ entry: turnEntry }) => turnEntry.role === "assistant")
|
|
46
|
+
?? turn.entries[0];
|
|
47
|
+
const eventId = identityEntry
|
|
48
|
+
? canonicalProductTurnId(identityEntry.entry, timing, fallbackEventId)
|
|
49
|
+
: undefined;
|
|
50
|
+
const projectionState = eventId
|
|
51
|
+
? turnProjectionStates.get(assistantTurnIndex)
|
|
52
|
+
?? projectionStateByEventId.get(eventId)
|
|
53
|
+
?? emptyAssistantTurnProjectionState()
|
|
54
|
+
: emptyAssistantTurnProjectionState();
|
|
55
|
+
nodes.push(...createAssistantTurnNodes(piboSessionId, turn.entries, timing, projectionState, fallbackEventId, qualifiedNativeToolCallIds));
|
|
56
|
+
if (eventId)
|
|
57
|
+
projectionStateByEventId.set(eventId, projectionState);
|
|
58
|
+
assistantTurnIndex += 1;
|
|
44
59
|
index = turn.nextIndex - 1;
|
|
45
60
|
}
|
|
46
61
|
}
|
|
@@ -71,7 +86,8 @@ function collectHistoryTurnTimingTargets(entries) {
|
|
|
71
86
|
const targets = [];
|
|
72
87
|
let latestUserText;
|
|
73
88
|
let latestUserEntryIndex;
|
|
74
|
-
let
|
|
89
|
+
let latestUserAt;
|
|
90
|
+
let latestProviderNativeTurnId;
|
|
75
91
|
for (let index = 0; index < entries.length; index += 1) {
|
|
76
92
|
const entry = entries[index];
|
|
77
93
|
if (entry.type !== "message")
|
|
@@ -79,180 +95,538 @@ function collectHistoryTurnTimingTargets(entries) {
|
|
|
79
95
|
if (entry.role === "user") {
|
|
80
96
|
latestUserText = normalizedPrompt(historyMessageText(entry));
|
|
81
97
|
latestUserEntryIndex = index;
|
|
82
|
-
|
|
98
|
+
latestUserAt = parsedTimestamp(entry.createdAt);
|
|
99
|
+
latestProviderNativeTurnId = entry.nativeTurnId;
|
|
83
100
|
continue;
|
|
84
101
|
}
|
|
85
102
|
if (entry.role !== "assistant" && entry.role !== "tool")
|
|
86
103
|
continue;
|
|
87
104
|
const turn = collectAssistantTurn(entries, index);
|
|
88
105
|
const lastAssistant = [...turn.entries].reverse().find(({ entry: turnEntry }) => turnEntry.role === "assistant");
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
})
|
|
97
|
-
}
|
|
106
|
+
const identityEntry = lastAssistant ?? turn.entries[0];
|
|
107
|
+
targets.push({
|
|
108
|
+
prompt: latestUserText,
|
|
109
|
+
userEntryIndex: latestUserEntryIndex,
|
|
110
|
+
userAt: latestUserAt,
|
|
111
|
+
assistantAt: lastAssistant ? parsedTimestamp(lastAssistant.entry.createdAt) : undefined,
|
|
112
|
+
providerNativeTurnId: identityEntry?.entry.nativeTurnId ?? latestProviderNativeTurnId,
|
|
113
|
+
entryIndexes: turn.entries.map(({ index: entryIndex }) => entryIndex),
|
|
114
|
+
});
|
|
98
115
|
index = turn.nextIndex - 1;
|
|
99
116
|
}
|
|
100
117
|
return targets;
|
|
101
118
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (normalizedPrompt(timing?.userText) !== historyTurn.prompt)
|
|
123
|
-
continue;
|
|
124
|
-
const completedAt = parsedTimestamp(timing?.completedAt);
|
|
125
|
-
const distance = completedAt === undefined || historyTurn.assistantAt === undefined
|
|
126
|
-
? Number.POSITIVE_INFINITY
|
|
127
|
-
: Math.abs(completedAt - historyTurn.assistantAt);
|
|
128
|
-
if (matchedIndex === undefined || distance < matchedDistance) {
|
|
129
|
-
matchedIndex = timingIndex;
|
|
130
|
-
matchedDistance = distance;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (matchedIndex === undefined)
|
|
134
|
-
continue;
|
|
135
|
-
assignments[turnIndex] = messageTurnTimings[matchedIndex];
|
|
136
|
-
timingCursor = matchedIndex - 1;
|
|
137
|
-
}
|
|
138
|
-
return assignments;
|
|
119
|
+
function assignHistoryTimings(entries, turnTimings, reconciliationProof, reconciliationAuthoritative = false) {
|
|
120
|
+
// Direct callers hand us a complete in-memory history. Paged production
|
|
121
|
+
// callers must provide an explicit adapter proof: complete proof decisions
|
|
122
|
+
// are computed once over the whole bounded scope and then projected back by
|
|
123
|
+
// stable position; incomplete proof can never authorize a product identity.
|
|
124
|
+
if (entries.length > TRACE_RECONCILIATION_ENTRY_CAP
|
|
125
|
+
|| turnTimings.length > TRACE_RECONCILIATION_TIMING_CAP
|
|
126
|
+
|| (reconciliationProof?.entries.length ?? 0) > TRACE_RECONCILIATION_ENTRY_CAP)
|
|
127
|
+
return assignHistoryTimingsWithinScope(entries, [], true);
|
|
128
|
+
if (!reconciliationProof)
|
|
129
|
+
return assignHistoryTimingsWithinScope(entries, turnTimings);
|
|
130
|
+
if (!reconciliationProof.complete
|
|
131
|
+
|| !structuralClaimMatchesPage(entries, reconciliationProof))
|
|
132
|
+
return assignHistoryTimingsWithinScope(entries, [], true);
|
|
133
|
+
// A compatibility claim may carry bounded structural context, but it cannot
|
|
134
|
+
// assign product ownership to native output. User-only history has no output
|
|
135
|
+
// ownership to transfer, so its independently persisted timing evidence may
|
|
136
|
+
// still reconcile the user messages exactly as the direct-history path does.
|
|
137
|
+
const userOnlyClaim = reconciliationProof.entries.every((entry) => entry.type !== "message" || entry.role === "user");
|
|
138
|
+
return assignmentsFromCompleteProof(entries, reconciliationAuthoritative || userOnlyClaim ? turnTimings : [], reconciliationProof.entries, !reconciliationAuthoritative && !userOnlyClaim);
|
|
139
139
|
}
|
|
140
|
-
function
|
|
140
|
+
function assignHistoryTimingsWithinScope(entries, turnTimings, failClosed = false) {
|
|
141
141
|
const users = [];
|
|
142
|
-
const userPositionByEntryIndex = new Map();
|
|
143
142
|
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
|
|
144
143
|
const entry = entries[entryIndex];
|
|
145
144
|
if (entry.type !== "message" || entry.role !== "user")
|
|
146
145
|
continue;
|
|
147
|
-
userPositionByEntryIndex.set(entryIndex, users.length);
|
|
148
146
|
users.push({
|
|
149
147
|
entryIndex,
|
|
150
148
|
entryId: entry.nativeEntryId,
|
|
151
149
|
turnId: entry.turnId,
|
|
152
150
|
prompt: normalizedPrompt(historyMessageText(entry)),
|
|
151
|
+
userAt: parsedTimestamp(entry.createdAt),
|
|
153
152
|
});
|
|
154
153
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
const historyTurns = collectHistoryTurnTimingTargets(entries);
|
|
155
|
+
const groups = collectHistoryTurnGroups(entries, users, historyTurns);
|
|
156
|
+
if (failClosed) {
|
|
157
|
+
const userFallbackEventIds = new Map();
|
|
158
|
+
const turnFallbackEventIds = new Map();
|
|
159
|
+
for (const group of groups) {
|
|
160
|
+
if (group.userEntryIndex !== undefined)
|
|
161
|
+
userFallbackEventIds.set(group.userEntryIndex, group.fallbackEventId);
|
|
162
|
+
for (const turnIndex of group.assistantTurnIndexes)
|
|
163
|
+
turnFallbackEventIds.set(turnIndex, group.fallbackEventId);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
userAssignments: new Map(),
|
|
167
|
+
turnAssignments: historyTurns.map(() => undefined),
|
|
168
|
+
userFallbackEventIds,
|
|
169
|
+
turnFallbackEventIds,
|
|
170
|
+
turnProjectionStates: new Map(),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
for (const historyTurn of historyTurns) {
|
|
174
|
+
if (historyTurn.userEntryIndex === undefined || historyTurn.assistantAt === undefined)
|
|
164
175
|
continue;
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
const user = users.find((candidate) => candidate.entryIndex === historyTurn.userEntryIndex);
|
|
177
|
+
if (user)
|
|
178
|
+
user.assistantAt ??= historyTurn.assistantAt;
|
|
167
179
|
}
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
const matches = users.map((user) => findConfidentTimingMatch(user, turnTimings));
|
|
181
|
+
const invalidUserPositions = invalidHistoryUserMatchPositions(matches);
|
|
182
|
+
const messageTurnTimings = turnTimings.filter((timing) => timing.userMessageType !== "message_steered");
|
|
183
|
+
const timingByEventId = new Map(messageTurnTimings.map((timing) => [timing.eventId, timing]));
|
|
184
|
+
const conflictingGroupIndexes = conflictingHistoryGroupIndexes(groups, matches, timingByEventId, invalidUserPositions);
|
|
185
|
+
const repeatedProviderTurnIds = repeatedProviderTurnIdsAcrossGroups(entries, groups);
|
|
186
|
+
const userAssignments = new Map();
|
|
187
|
+
const turnAssignments = historyTurns.map(() => undefined);
|
|
188
|
+
const userFallbackEventIds = new Map();
|
|
189
|
+
const turnFallbackEventIds = new Map();
|
|
190
|
+
const turnProjectionStates = new Map();
|
|
191
|
+
for (const group of groups) {
|
|
192
|
+
const userPosition = group.userPosition;
|
|
193
|
+
const match = userPosition === undefined ? undefined : matches[userPosition];
|
|
194
|
+
if (conflictingGroupIndexes.has(group.index)) {
|
|
195
|
+
if (group.userEntryIndex !== undefined)
|
|
196
|
+
userFallbackEventIds.set(group.userEntryIndex, group.fallbackEventId);
|
|
197
|
+
for (const turnIndex of group.assistantTurnIndexes) {
|
|
198
|
+
turnFallbackEventIds.set(turnIndex, group.fallbackEventId);
|
|
199
|
+
}
|
|
176
200
|
continue;
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
201
|
+
}
|
|
202
|
+
if (!match || userPosition === undefined || invalidUserPositions.has(userPosition)) {
|
|
203
|
+
if (groupNeedsStructuralFallback(entries, group, repeatedProviderTurnIds)) {
|
|
204
|
+
if (group.userEntryIndex !== undefined)
|
|
205
|
+
userFallbackEventIds.set(group.userEntryIndex, group.fallbackEventId);
|
|
206
|
+
for (const turnIndex of group.assistantTurnIndexes)
|
|
207
|
+
turnFallbackEventIds.set(turnIndex, group.fallbackEventId);
|
|
208
|
+
}
|
|
180
209
|
continue;
|
|
181
210
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
if (group.userEntryIndex !== undefined)
|
|
212
|
+
userAssignments.set(group.userEntryIndex, match.timing);
|
|
213
|
+
const outputTiming = outputTimingForUserTiming(match.timing, timingByEventId);
|
|
214
|
+
if (!outputTiming)
|
|
215
|
+
continue;
|
|
216
|
+
for (const turnIndex of group.assistantTurnIndexes)
|
|
217
|
+
turnAssignments[turnIndex] = outputTiming;
|
|
218
|
+
}
|
|
219
|
+
return { userAssignments, turnAssignments, userFallbackEventIds, turnFallbackEventIds, turnProjectionStates };
|
|
220
|
+
}
|
|
221
|
+
function assignmentsFromCompleteProof(entries, turnTimings, proofEntries, proofFailClosed = false) {
|
|
222
|
+
if (!hasUniqueStableHistoryPositions(entries)) {
|
|
223
|
+
return assignHistoryTimingsWithinScope(entries, turnTimings, true);
|
|
224
|
+
}
|
|
225
|
+
const proofAssignments = assignHistoryTimingsWithinScope(proofEntries, turnTimings, proofFailClosed);
|
|
226
|
+
const localFallbackEventIds = historyGroupFallbackEventIds(entries);
|
|
227
|
+
const decisionsByPosition = new Map();
|
|
228
|
+
for (let entryIndex = 0; entryIndex < proofEntries.length; entryIndex += 1) {
|
|
229
|
+
const entry = proofEntries[entryIndex];
|
|
230
|
+
if (entry.type !== "message" || entry.role !== "user")
|
|
231
|
+
continue;
|
|
232
|
+
decisionsByPosition.set(entry.historyPosition, {
|
|
233
|
+
timing: proofAssignments.userAssignments.get(entryIndex),
|
|
234
|
+
fallbackEventId: proofAssignments.userFallbackEventIds.get(entryIndex),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const proofTurns = collectHistoryTurnTimingTargets(proofEntries);
|
|
238
|
+
for (let turnIndex = 0; turnIndex < proofTurns.length; turnIndex += 1) {
|
|
239
|
+
const decision = {
|
|
240
|
+
timing: proofAssignments.turnAssignments[turnIndex],
|
|
241
|
+
fallbackEventId: proofAssignments.turnFallbackEventIds.get(turnIndex),
|
|
242
|
+
};
|
|
243
|
+
for (const entryIndex of proofTurns[turnIndex].entryIndexes) {
|
|
244
|
+
decisionsByPosition.set(proofEntries[entryIndex].historyPosition, decision);
|
|
191
245
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
246
|
+
}
|
|
247
|
+
const userAssignments = new Map();
|
|
248
|
+
const userFallbackEventIds = new Map();
|
|
249
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
|
|
250
|
+
const entry = entries[entryIndex];
|
|
251
|
+
if (entry.type !== "message" || entry.role !== "user")
|
|
196
252
|
continue;
|
|
253
|
+
const decision = decisionsByPosition.get(entry.historyPosition);
|
|
254
|
+
if (decision?.timing)
|
|
255
|
+
userAssignments.set(entryIndex, decision.timing);
|
|
256
|
+
if (decision?.fallbackEventId)
|
|
257
|
+
userFallbackEventIds.set(entryIndex, decision.fallbackEventId);
|
|
258
|
+
if (!decision)
|
|
259
|
+
userFallbackEventIds.set(entryIndex, localFallbackEventIds[entryIndex]);
|
|
260
|
+
}
|
|
261
|
+
const localTurns = collectHistoryTurnTimingTargets(entries);
|
|
262
|
+
const turnAssignments = localTurns.map(() => undefined);
|
|
263
|
+
const turnFallbackEventIds = new Map();
|
|
264
|
+
const turnProjectionStates = projectionStatesFromCompleteProof(entries, localTurns, proofEntries, proofTurns, proofAssignments);
|
|
265
|
+
for (let turnIndex = 0; turnIndex < localTurns.length; turnIndex += 1) {
|
|
266
|
+
const turn = localTurns[turnIndex];
|
|
267
|
+
const decisions = turn.entryIndexes.map((entryIndex) => decisionsByPosition.get(entries[entryIndex].historyPosition));
|
|
268
|
+
const decision = oneConsistentProofDecision(decisions);
|
|
269
|
+
if (decision?.timing)
|
|
270
|
+
turnAssignments[turnIndex] = decision.timing;
|
|
271
|
+
if (decision?.fallbackEventId) {
|
|
272
|
+
turnFallbackEventIds.set(turnIndex, decision.fallbackEventId);
|
|
273
|
+
}
|
|
274
|
+
else if (!decision) {
|
|
275
|
+
const firstEntryIndex = turn.entryIndexes[0];
|
|
276
|
+
turnFallbackEventIds.set(turnIndex, localFallbackEventIds[firstEntryIndex]);
|
|
197
277
|
}
|
|
198
|
-
|
|
278
|
+
}
|
|
279
|
+
return { userAssignments, turnAssignments, userFallbackEventIds, turnFallbackEventIds, turnProjectionStates };
|
|
280
|
+
}
|
|
281
|
+
function structuralClaimMatchesPage(entries, proof) {
|
|
282
|
+
if (!proof.fullScope
|
|
283
|
+
|| !Number.isSafeInteger(proof.fullScope.entryCount)
|
|
284
|
+
|| proof.fullScope.entryCount < 0
|
|
285
|
+
|| proof.fullScope.entryCount > TRACE_RECONCILIATION_ENTRY_CAP
|
|
286
|
+
|| proof.fullScope.entryCount !== proof.entries.length
|
|
287
|
+
|| !/^[a-f0-9]{64}$/.test(proof.fullScope.digest)
|
|
288
|
+
|| proof.fullScope.digest !== historyReconciliationDigest(proof.entries))
|
|
289
|
+
return false;
|
|
290
|
+
if (!hasUniqueStableHistoryPositions(entries) || !hasUniqueStableHistoryPositions(proof.entries))
|
|
291
|
+
return false;
|
|
292
|
+
if (proof.scopeId) {
|
|
293
|
+
if (entries.some((entry) => entry.historyScopeId !== proof.scopeId))
|
|
294
|
+
return false;
|
|
295
|
+
if (proof.entries.some((entry) => entry.historyScopeId !== proof.scopeId))
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const proofByPosition = new Map(proof.entries.map((entry) => [entry.historyPosition, entry]));
|
|
299
|
+
return entries.every((entry) => {
|
|
300
|
+
const proofEntry = proofByPosition.get(entry.historyPosition);
|
|
301
|
+
return proofEntry !== undefined
|
|
302
|
+
&& historyReconciliationEntrySignature(entry) === historyReconciliationEntrySignature(proofEntry);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function projectionStatesFromCompleteProof(entries, localTurns, proofEntries, proofTurns, proofAssignments) {
|
|
306
|
+
const stateByEventId = new Map();
|
|
307
|
+
const stateByPosition = new Map();
|
|
308
|
+
for (let turnIndex = 0; turnIndex < proofTurns.length; turnIndex += 1) {
|
|
309
|
+
const turn = proofTurns[turnIndex];
|
|
310
|
+
const firstAssistantIndex = turn.entryIndexes.find((entryIndex) => {
|
|
311
|
+
const entry = proofEntries[entryIndex];
|
|
312
|
+
return entry?.type === "message" && entry.role === "assistant";
|
|
313
|
+
});
|
|
314
|
+
const identityEntryIndex = firstAssistantIndex ?? turn.entryIndexes[0];
|
|
315
|
+
if (identityEntryIndex === undefined)
|
|
316
|
+
continue;
|
|
317
|
+
const identityEntry = proofEntries[identityEntryIndex];
|
|
318
|
+
if (!identityEntry || identityEntry.type !== "message")
|
|
199
319
|
continue;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
320
|
+
const eventId = canonicalProductTurnId(identityEntry, proofAssignments.turnAssignments[turnIndex], proofAssignments.turnFallbackEventIds.get(turnIndex));
|
|
321
|
+
const state = eventId
|
|
322
|
+
? stateByEventId.get(eventId) ?? emptyAssistantTurnProjectionState()
|
|
323
|
+
: emptyAssistantTurnProjectionState();
|
|
324
|
+
for (const entryIndex of turn.entryIndexes) {
|
|
325
|
+
const entry = proofEntries[entryIndex];
|
|
326
|
+
if (!entry || entry.type !== "message" || !entry.historyPosition)
|
|
205
327
|
continue;
|
|
206
|
-
|
|
328
|
+
stateByPosition.set(entry.historyPosition, cloneAssistantTurnProjectionState(state));
|
|
329
|
+
advanceAssistantTurnProjectionState(state, entry);
|
|
330
|
+
}
|
|
331
|
+
if (eventId)
|
|
332
|
+
stateByEventId.set(eventId, state);
|
|
333
|
+
}
|
|
334
|
+
const localStates = new Map();
|
|
335
|
+
for (let turnIndex = 0; turnIndex < localTurns.length; turnIndex += 1) {
|
|
336
|
+
const firstEntryIndex = localTurns[turnIndex].entryIndexes[0];
|
|
337
|
+
if (firstEntryIndex === undefined)
|
|
338
|
+
continue;
|
|
339
|
+
const position = entries[firstEntryIndex]?.historyPosition;
|
|
340
|
+
const state = position ? stateByPosition.get(position) : undefined;
|
|
341
|
+
if (state)
|
|
342
|
+
localStates.set(turnIndex, cloneAssistantTurnProjectionState(state));
|
|
343
|
+
}
|
|
344
|
+
return localStates;
|
|
345
|
+
}
|
|
346
|
+
function emptyAssistantTurnProjectionState() {
|
|
347
|
+
return {
|
|
348
|
+
assistantPartOrdinal: 0,
|
|
349
|
+
reasoningPartOrdinal: 0,
|
|
350
|
+
toolCallCounts: new Map(),
|
|
351
|
+
pendingToolCallOrdinals: new Map(),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function cloneAssistantTurnProjectionState(state) {
|
|
355
|
+
return {
|
|
356
|
+
assistantPartOrdinal: state.assistantPartOrdinal,
|
|
357
|
+
reasoningPartOrdinal: state.reasoningPartOrdinal,
|
|
358
|
+
toolCallCounts: new Map(state.toolCallCounts),
|
|
359
|
+
pendingToolCallOrdinals: new Map([...state.pendingToolCallOrdinals].map(([toolCallId, ordinals]) => [toolCallId, [...ordinals]])),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function advanceAssistantTurnProjectionState(state, entry) {
|
|
363
|
+
if (entry.role === "tool" && entry.toolCallId) {
|
|
364
|
+
consumeToolCallOrdinal(state, entry.toolCallId);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (entry.role !== "assistant")
|
|
368
|
+
return;
|
|
369
|
+
const parts = historyMessageParts(entry);
|
|
370
|
+
for (const part of parts) {
|
|
371
|
+
if (part.type === "reasoning" && hasVisibleText(part.text))
|
|
372
|
+
state.reasoningPartOrdinal += 1;
|
|
373
|
+
if (part.type === "tool_call")
|
|
374
|
+
reserveToolCallOrdinal(state, part.toolCallId);
|
|
375
|
+
}
|
|
376
|
+
if (parts.some((part) => part.type === "text" && part.text !== ""))
|
|
377
|
+
state.assistantPartOrdinal += 1;
|
|
378
|
+
}
|
|
379
|
+
function reserveToolCallOrdinal(state, toolCallId) {
|
|
380
|
+
const ordinal = state.toolCallCounts.get(toolCallId) ?? 0;
|
|
381
|
+
state.toolCallCounts.set(toolCallId, ordinal + 1);
|
|
382
|
+
const pending = state.pendingToolCallOrdinals.get(toolCallId) ?? [];
|
|
383
|
+
pending.push(ordinal);
|
|
384
|
+
state.pendingToolCallOrdinals.set(toolCallId, pending);
|
|
385
|
+
return ordinal;
|
|
386
|
+
}
|
|
387
|
+
function consumeToolCallOrdinal(state, toolCallId) {
|
|
388
|
+
const pending = state.pendingToolCallOrdinals.get(toolCallId);
|
|
389
|
+
const ordinal = pending?.shift();
|
|
390
|
+
if (pending?.length === 0)
|
|
391
|
+
state.pendingToolCallOrdinals.delete(toolCallId);
|
|
392
|
+
if (ordinal !== undefined)
|
|
393
|
+
return ordinal;
|
|
394
|
+
const nextOrdinal = state.toolCallCounts.get(toolCallId) ?? 0;
|
|
395
|
+
state.toolCallCounts.set(toolCallId, nextOrdinal + 1);
|
|
396
|
+
return nextOrdinal;
|
|
397
|
+
}
|
|
398
|
+
function hasUniqueStableHistoryPositions(entries) {
|
|
399
|
+
const positions = new Set();
|
|
400
|
+
for (const entry of entries) {
|
|
401
|
+
if (!entry.historyPosition || positions.has(entry.historyPosition))
|
|
402
|
+
return false;
|
|
403
|
+
positions.add(entry.historyPosition);
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
function oneConsistentProofDecision(decisions) {
|
|
408
|
+
const first = decisions[0];
|
|
409
|
+
if (!first || decisions.some((decision) => !decision))
|
|
410
|
+
return undefined;
|
|
411
|
+
const firstTimingId = first.timing?.eventId;
|
|
412
|
+
return decisions.every((decision) => decision?.timing?.eventId === firstTimingId
|
|
413
|
+
&& decision?.fallbackEventId === first.fallbackEventId) ? first : undefined;
|
|
414
|
+
}
|
|
415
|
+
function invalidHistoryUserMatchPositions(matches) {
|
|
416
|
+
const invalidUserPositions = new Set();
|
|
417
|
+
for (let leftPosition = 0; leftPosition < matches.length; leftPosition += 1) {
|
|
418
|
+
const left = matches[leftPosition];
|
|
419
|
+
if (!left)
|
|
420
|
+
continue;
|
|
421
|
+
for (let rightPosition = leftPosition + 1; rightPosition < matches.length; rightPosition += 1) {
|
|
422
|
+
const right = matches[rightPosition];
|
|
423
|
+
if (!right || left.timingIndex < right.timingIndex)
|
|
207
424
|
continue;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (matchedTimingIndex === undefined || distance < matchedDistance) {
|
|
213
|
-
matchedTimingIndex = timingIndex;
|
|
214
|
-
matchedDistance = distance;
|
|
215
|
-
}
|
|
425
|
+
if (left.confidence !== "identity")
|
|
426
|
+
invalidUserPositions.add(leftPosition);
|
|
427
|
+
if (right.confidence !== "identity")
|
|
428
|
+
invalidUserPositions.add(rightPosition);
|
|
216
429
|
}
|
|
217
|
-
|
|
430
|
+
}
|
|
431
|
+
return invalidUserPositions;
|
|
432
|
+
}
|
|
433
|
+
function collectHistoryTurnGroups(entries, users, historyTurns) {
|
|
434
|
+
const fallbackEventIds = historyGroupFallbackEventIds(entries);
|
|
435
|
+
const groups = users.map((user, userPosition) => {
|
|
436
|
+
const entry = entries[user.entryIndex];
|
|
437
|
+
return {
|
|
438
|
+
index: userPosition,
|
|
439
|
+
userPosition,
|
|
440
|
+
userEntryIndex: user.entryIndex,
|
|
441
|
+
assistantTurnIndexes: [],
|
|
442
|
+
entryIndexes: [user.entryIndex],
|
|
443
|
+
providerNativeTurnIds: new Set(entry.type === "message" && entry.nativeTurnId ? [entry.nativeTurnId] : []),
|
|
444
|
+
fallbackEventId: fallbackEventIds[user.entryIndex],
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
const groupByUserEntryIndex = new Map(groups.map((group) => [group.userEntryIndex, group]));
|
|
448
|
+
for (let turnIndex = 0; turnIndex < historyTurns.length; turnIndex += 1) {
|
|
449
|
+
const userEntryIndex = historyTurns[turnIndex].userEntryIndex;
|
|
450
|
+
const group = userEntryIndex === undefined ? undefined : groupByUserEntryIndex.get(userEntryIndex);
|
|
451
|
+
if (!group) {
|
|
452
|
+
const firstEntryIndex = historyTurns[turnIndex].entryIndexes[0];
|
|
453
|
+
const firstEntry = firstEntryIndex === undefined ? undefined : entries[firstEntryIndex];
|
|
454
|
+
if (!firstEntry)
|
|
455
|
+
continue;
|
|
456
|
+
groups.push({
|
|
457
|
+
index: groups.length,
|
|
458
|
+
assistantTurnIndexes: [turnIndex],
|
|
459
|
+
entryIndexes: [...historyTurns[turnIndex].entryIndexes],
|
|
460
|
+
providerNativeTurnIds: new Set(historyTurns[turnIndex].providerNativeTurnId
|
|
461
|
+
? [historyTurns[turnIndex].providerNativeTurnId]
|
|
462
|
+
: []),
|
|
463
|
+
fallbackEventId: fallbackEventIds[firstEntryIndex],
|
|
464
|
+
});
|
|
218
465
|
continue;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
466
|
+
}
|
|
467
|
+
group.assistantTurnIndexes.push(turnIndex);
|
|
468
|
+
group.entryIndexes.push(...historyTurns[turnIndex].entryIndexes);
|
|
469
|
+
const providerNativeTurnId = historyTurns[turnIndex].providerNativeTurnId;
|
|
470
|
+
if (providerNativeTurnId)
|
|
471
|
+
group.providerNativeTurnIds.add(providerNativeTurnId);
|
|
472
|
+
}
|
|
473
|
+
return groups;
|
|
474
|
+
}
|
|
475
|
+
function historyGroupFallbackEventIds(entries) {
|
|
476
|
+
const positionCounts = new Map();
|
|
477
|
+
const sequenceCounts = new Map();
|
|
478
|
+
for (const entry of entries) {
|
|
479
|
+
if (entry.historyPosition)
|
|
480
|
+
positionCounts.set(entry.historyPosition, (positionCounts.get(entry.historyPosition) ?? 0) + 1);
|
|
481
|
+
if (entry.sequence !== undefined)
|
|
482
|
+
sequenceCounts.set(entry.sequence, (sequenceCounts.get(entry.sequence) ?? 0) + 1);
|
|
483
|
+
}
|
|
484
|
+
return entries.map((entry, entryIndex) => {
|
|
485
|
+
if (entry.historyPosition && positionCounts.get(entry.historyPosition) === 1) {
|
|
486
|
+
return `native-history-group:${entry.historyPosition}`;
|
|
487
|
+
}
|
|
488
|
+
const scopePrefix = entry.historyScopeId ? `${entry.historyScopeId}:` : "";
|
|
489
|
+
if (entry.sequence !== undefined && sequenceCounts.get(entry.sequence) === 1) {
|
|
490
|
+
return `native-history-group:${scopePrefix}position:${entry.sequence}`;
|
|
491
|
+
}
|
|
492
|
+
return `native-history-group:${scopePrefix}position:${entryIndex}`;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function groupNeedsStructuralFallback(entries, group, repeatedProviderTurnIds) {
|
|
496
|
+
return group.userPosition === undefined
|
|
497
|
+
|| group.entryIndexes.some((entryIndex) => {
|
|
498
|
+
const entry = entries[entryIndex];
|
|
499
|
+
return entry?.type === "message"
|
|
500
|
+
&& ((entry.turnId !== undefined && repeatedProviderTurnIds.has(entry.turnId))
|
|
501
|
+
|| (entry.nativeTurnId !== undefined && repeatedProviderTurnIds.has(entry.nativeTurnId)));
|
|
502
|
+
})
|
|
503
|
+
|| (group.assistantTurnIndexes.length > 0 && group.entryIndexes.every((entryIndex) => {
|
|
504
|
+
const entry = entries[entryIndex];
|
|
505
|
+
return entry?.type !== "message" || !entry.turnId;
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
function repeatedProviderTurnIdsAcrossGroups(entries, groups) {
|
|
509
|
+
const counts = new Map();
|
|
510
|
+
for (const group of groups) {
|
|
511
|
+
const identities = new Set();
|
|
512
|
+
for (const entryIndex of group.entryIndexes) {
|
|
513
|
+
const entry = entries[entryIndex];
|
|
514
|
+
if (entry?.type !== "message")
|
|
241
515
|
continue;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
516
|
+
if (entry.turnId)
|
|
517
|
+
identities.add(entry.turnId);
|
|
518
|
+
if (entry.nativeTurnId)
|
|
519
|
+
identities.add(entry.nativeTurnId);
|
|
520
|
+
}
|
|
521
|
+
for (const identity of identities)
|
|
522
|
+
counts.set(identity, (counts.get(identity) ?? 0) + 1);
|
|
523
|
+
}
|
|
524
|
+
return new Set([...counts].filter(([, count]) => count > 1).map(([identity]) => identity));
|
|
525
|
+
}
|
|
526
|
+
function conflictingHistoryGroupIndexes(groups, matches, timingByEventId, invalidUserPositions) {
|
|
527
|
+
const conflictingGroupIndexes = new Set();
|
|
528
|
+
const directClaimants = new Map();
|
|
529
|
+
const outputClaimants = new Map();
|
|
530
|
+
for (const group of groups) {
|
|
531
|
+
if (group.userPosition === undefined
|
|
532
|
+
|| (invalidUserPositions.has(group.userPosition) && group.assistantTurnIndexes.length === 0))
|
|
533
|
+
continue;
|
|
534
|
+
const match = matches[group.userPosition];
|
|
535
|
+
if (!match)
|
|
536
|
+
continue;
|
|
537
|
+
const timingClaimants = directClaimants.get(match.timingIndex) ?? [];
|
|
538
|
+
timingClaimants.push(group);
|
|
539
|
+
directClaimants.set(match.timingIndex, timingClaimants);
|
|
540
|
+
if (group.assistantTurnIndexes.length === 0)
|
|
541
|
+
continue;
|
|
542
|
+
const outputTiming = outputTimingForUserTiming(match.timing, timingByEventId);
|
|
543
|
+
if (!outputTiming)
|
|
544
|
+
continue;
|
|
545
|
+
const eventClaimants = outputClaimants.get(outputTiming.eventId) ?? [];
|
|
546
|
+
eventClaimants.push(group);
|
|
547
|
+
outputClaimants.set(outputTiming.eventId, eventClaimants);
|
|
548
|
+
}
|
|
549
|
+
for (const claimants of directClaimants.values()) {
|
|
550
|
+
if (claimants.length <= 1)
|
|
551
|
+
continue;
|
|
552
|
+
for (const claimant of claimants)
|
|
553
|
+
conflictingGroupIndexes.add(claimant.index);
|
|
554
|
+
}
|
|
555
|
+
for (const [eventId, claimants] of outputClaimants) {
|
|
556
|
+
if (claimants.length <= 1 || isLegitimateSteeringCohort(eventId, claimants, matches))
|
|
557
|
+
continue;
|
|
558
|
+
for (const claimant of claimants)
|
|
559
|
+
conflictingGroupIndexes.add(claimant.index);
|
|
560
|
+
}
|
|
561
|
+
return conflictingGroupIndexes;
|
|
562
|
+
}
|
|
563
|
+
function isLegitimateSteeringCohort(outputEventId, groups, matches) {
|
|
564
|
+
const [providerNativeTurnId] = groups[0]?.providerNativeTurnIds ?? [];
|
|
565
|
+
if (!providerNativeTurnId || groups.some((group) => group.providerNativeTurnIds.size !== 1 || !group.providerNativeTurnIds.has(providerNativeTurnId)))
|
|
566
|
+
return false;
|
|
567
|
+
let baseClaimants = 0;
|
|
568
|
+
let steeringClaimants = 0;
|
|
569
|
+
for (const group of groups) {
|
|
570
|
+
const timing = group.userPosition === undefined ? undefined : matches[group.userPosition]?.timing;
|
|
571
|
+
if (!timing)
|
|
572
|
+
return false;
|
|
573
|
+
if (timing.userMessageType === "message_steered" && timing.activeEventId === outputEventId) {
|
|
574
|
+
steeringClaimants += 1;
|
|
575
|
+
}
|
|
576
|
+
else if (timing.eventId === outputEventId) {
|
|
577
|
+
baseClaimants += 1;
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
return false;
|
|
253
581
|
}
|
|
254
582
|
}
|
|
255
|
-
return
|
|
583
|
+
return steeringClaimants > 0 && baseClaimants <= 1;
|
|
584
|
+
}
|
|
585
|
+
function outputTimingForUserTiming(userTiming, timingByEventId) {
|
|
586
|
+
if (userTiming.userMessageType !== "message_steered")
|
|
587
|
+
return userTiming;
|
|
588
|
+
return userTiming.activeEventId ? timingByEventId.get(userTiming.activeEventId) : undefined;
|
|
589
|
+
}
|
|
590
|
+
function findConfidentTimingMatch(target, turnTimings) {
|
|
591
|
+
for (const identity of [target.turnId, target.entryId]) {
|
|
592
|
+
if (!identity)
|
|
593
|
+
continue;
|
|
594
|
+
const timingIndex = turnTimings.findIndex((timing) => timing.eventId === identity);
|
|
595
|
+
if (timingIndex >= 0)
|
|
596
|
+
return { timing: turnTimings[timingIndex], timingIndex, confidence: "identity" };
|
|
597
|
+
}
|
|
598
|
+
if (!target.prompt)
|
|
599
|
+
return undefined;
|
|
600
|
+
const candidates = turnTimings.flatMap((timing, timingIndex) => normalizedPrompt(timing.userText) === target.prompt ? [{ timing, timingIndex }] : []);
|
|
601
|
+
if (!candidates.length)
|
|
602
|
+
return undefined;
|
|
603
|
+
const timestampEvidence = candidates.flatMap((candidate) => {
|
|
604
|
+
const startedAt = parsedTimestamp(candidate.timing.startedAt);
|
|
605
|
+
const completedAt = parsedTimestamp(candidate.timing.completedAt);
|
|
606
|
+
const startDistance = startedAt === undefined || target.userAt === undefined
|
|
607
|
+
? undefined
|
|
608
|
+
: Math.abs(startedAt - target.userAt);
|
|
609
|
+
const completionDistance = completedAt === undefined || target.assistantAt === undefined
|
|
610
|
+
? undefined
|
|
611
|
+
: Math.abs(completedAt - target.assistantAt);
|
|
612
|
+
return [
|
|
613
|
+
...(startDistance !== undefined && startDistance <= MAX_CONFIDENT_TIMESTAMP_DISTANCE_MS
|
|
614
|
+
? [{ ...candidate, endpoint: "start", distance: startDistance }]
|
|
615
|
+
: []),
|
|
616
|
+
...(completionDistance !== undefined && completionDistance <= MAX_CONFIDENT_TIMESTAMP_DISTANCE_MS
|
|
617
|
+
? [{ ...candidate, endpoint: "completion", distance: completionDistance }]
|
|
618
|
+
: []),
|
|
619
|
+
];
|
|
620
|
+
}).sort((left, right) => left.distance - right.distance || left.timingIndex - right.timingIndex);
|
|
621
|
+
const best = timestampEvidence[0];
|
|
622
|
+
if (!best)
|
|
623
|
+
return undefined;
|
|
624
|
+
const bestTimingIndices = new Set(timestampEvidence
|
|
625
|
+
.filter((evidence) => evidence.distance === best.distance)
|
|
626
|
+
.map((evidence) => evidence.timingIndex));
|
|
627
|
+
return bestTimingIndices.size === 1
|
|
628
|
+
? { timing: best.timing, timingIndex: best.timingIndex, confidence: "timestamp" }
|
|
629
|
+
: undefined;
|
|
256
630
|
}
|
|
257
631
|
function collectAssistantTurn(entries, startIndex) {
|
|
258
632
|
const turnEntries = [];
|
|
@@ -268,7 +642,7 @@ function collectAssistantTurn(entries, startIndex) {
|
|
|
268
642
|
}
|
|
269
643
|
return { entries: turnEntries, nextIndex: index };
|
|
270
644
|
}
|
|
271
|
-
function createUserMessageNode(piboSessionId, entry, entryIndex, timing) {
|
|
645
|
+
function createUserMessageNode(piboSessionId, entry, entryIndex, timing, fallbackEventId) {
|
|
272
646
|
const text = historyMessageText(entry);
|
|
273
647
|
const notification = parseRunNotificationText(text);
|
|
274
648
|
const source = traceSource(entry.source);
|
|
@@ -286,13 +660,17 @@ function createUserMessageNode(piboSessionId, entry, entryIndex, timing) {
|
|
|
286
660
|
});
|
|
287
661
|
}
|
|
288
662
|
const userMessageType = timing?.userMessageType ?? "message_queued";
|
|
289
|
-
const eventId = entry
|
|
663
|
+
const eventId = canonicalProductTurnId(entry, timing, fallbackEventId);
|
|
290
664
|
const eventIdentity = eventId ? `event:${userMessageType}:${eventId}` : undefined;
|
|
291
665
|
return {
|
|
292
666
|
id: eventIdentity ?? historyEntryNodeId(entry),
|
|
293
667
|
entryId: entry.nativeEntryId,
|
|
668
|
+
nativeTurnId: nativeHistoryTurnId(entry),
|
|
294
669
|
piboSessionId,
|
|
295
670
|
eventId,
|
|
671
|
+
parentId: timing?.userMessageType === "message_steered" && timing.activeEventId
|
|
672
|
+
? messageTurnNodeId(timing.activeEventId)
|
|
673
|
+
: undefined,
|
|
296
674
|
type: "user.message",
|
|
297
675
|
title: "User Message",
|
|
298
676
|
status: entry.status === "running" ? "running" : entry.status === "error" ? "error" : "done",
|
|
@@ -306,17 +684,20 @@ function createUserMessageNode(piboSessionId, entry, entryIndex, timing) {
|
|
|
306
684
|
children: [],
|
|
307
685
|
};
|
|
308
686
|
}
|
|
309
|
-
function createAssistantTurnNodes(piboSessionId, entries, timing) {
|
|
687
|
+
function createAssistantTurnNodes(piboSessionId, entries, timing, projectionState = emptyAssistantTurnProjectionState(), fallbackEventId, qualifiedNativeToolCallIds = new Set()) {
|
|
310
688
|
const firstAssistant = entries.find(({ entry }) => entry.role === "assistant");
|
|
311
|
-
|
|
689
|
+
const identityEntry = firstAssistant ?? entries[0];
|
|
690
|
+
if (!identityEntry)
|
|
312
691
|
return [];
|
|
692
|
+
const eventId = canonicalProductTurnId(identityEntry.entry, timing, fallbackEventId);
|
|
313
693
|
const orderedNodes = [];
|
|
314
694
|
const toolsByCallId = new Map();
|
|
315
|
-
let assistantPartOrdinal = 0;
|
|
316
|
-
let reasoningPartOrdinal = 0;
|
|
317
695
|
for (const { entry, index: entryIndex } of entries) {
|
|
318
696
|
if (entry.role === "tool") {
|
|
319
|
-
|
|
697
|
+
const toolInvocationOrdinal = entry.toolCallId
|
|
698
|
+
? consumeToolCallOrdinal(projectionState, entry.toolCallId)
|
|
699
|
+
: 0;
|
|
700
|
+
mergePersistedToolResult(toolsByCallId, orderedNodes, entry, piboSessionId, entryIndex, eventId, toolInvocationOrdinal, qualifiedNativeToolCallIds.has(entry.toolCallId ?? ""));
|
|
320
701
|
continue;
|
|
321
702
|
}
|
|
322
703
|
const responseStatus = historyMessageStatus(entry);
|
|
@@ -324,28 +705,31 @@ function createAssistantTurnNodes(piboSessionId, entries, timing) {
|
|
|
324
705
|
const parts = historyMessageParts(entry);
|
|
325
706
|
for (const [contentPartIndex, part] of parts.entries()) {
|
|
326
707
|
if (part.type === "reasoning" && hasVisibleText(part.text)) {
|
|
327
|
-
const thinkingIndex = timing?.reasoningIndices?.[reasoningPartOrdinal]
|
|
708
|
+
const thinkingIndex = timing?.reasoningIndices?.[projectionState.reasoningPartOrdinal]
|
|
709
|
+
?? projectionState.reasoningPartOrdinal;
|
|
328
710
|
orderedNodes.push(createReasoningNode({
|
|
329
711
|
piboSessionId,
|
|
330
712
|
entry,
|
|
331
713
|
entryIndex,
|
|
332
714
|
contentPartIndex,
|
|
333
715
|
thinkingIndex,
|
|
334
|
-
eventId
|
|
716
|
+
eventId,
|
|
335
717
|
thinking: part.text,
|
|
336
718
|
}));
|
|
337
|
-
reasoningPartOrdinal += 1;
|
|
719
|
+
projectionState.reasoningPartOrdinal += 1;
|
|
338
720
|
}
|
|
339
721
|
else if (part.type === "text" && part.text !== "") {
|
|
340
722
|
if (!responseNode) {
|
|
341
|
-
const assistantIndex =
|
|
723
|
+
const assistantIndex = timing?.assistantIndices?.[projectionState.assistantPartOrdinal]
|
|
724
|
+
?? entry.assistantIndex
|
|
725
|
+
?? projectionState.assistantPartOrdinal;
|
|
342
726
|
responseNode = createAssistantMessageNode({
|
|
343
727
|
piboSessionId,
|
|
344
728
|
entry,
|
|
345
729
|
entryIndex,
|
|
346
730
|
contentPartIndex,
|
|
347
731
|
assistantIndex,
|
|
348
|
-
eventId
|
|
732
|
+
eventId,
|
|
349
733
|
status: responseStatus,
|
|
350
734
|
text: part.text,
|
|
351
735
|
error: entry.error,
|
|
@@ -360,15 +744,18 @@ function createAssistantTurnNodes(piboSessionId, entries, timing) {
|
|
|
360
744
|
}
|
|
361
745
|
}
|
|
362
746
|
else if (part.type === "tool_call") {
|
|
363
|
-
const
|
|
747
|
+
const toolInvocationOrdinal = reserveToolCallOrdinal(projectionState, part.toolCallId);
|
|
748
|
+
const toolNode = createToolCallNode(piboSessionId, entry, entryIndex, contentPartIndex, part, eventId, toolInvocationOrdinal, qualifiedNativeToolCallIds.has(part.toolCallId));
|
|
364
749
|
orderedNodes.push(toolNode);
|
|
365
|
-
toolsByCallId.
|
|
750
|
+
const pendingTools = toolsByCallId.get(part.toolCallId) ?? [];
|
|
751
|
+
pendingTools.push(toolNode);
|
|
752
|
+
toolsByCallId.set(part.toolCallId, pendingTools);
|
|
366
753
|
}
|
|
367
754
|
}
|
|
368
755
|
if (responseNode) {
|
|
369
756
|
responseNode.status = responseStatus;
|
|
370
757
|
responseNode.error = entry.error;
|
|
371
|
-
assistantPartOrdinal += 1;
|
|
758
|
+
projectionState.assistantPartOrdinal += 1;
|
|
372
759
|
}
|
|
373
760
|
}
|
|
374
761
|
const finalNode = orderedNodes.at(-1);
|
|
@@ -384,9 +771,10 @@ function createReasoningNode(input) {
|
|
|
384
771
|
return {
|
|
385
772
|
id: eventIdentity ? thinkingNodeId(eventIdentity) : `${historyEntryNodeId(input.entry)}:thinking:${input.contentPartIndex}`,
|
|
386
773
|
entryId: input.entry.nativeEntryId,
|
|
774
|
+
nativeTurnId: nativeHistoryTurnId(input.entry),
|
|
387
775
|
piboSessionId: input.piboSessionId,
|
|
388
776
|
eventId: input.eventId,
|
|
389
|
-
parentId: input.entry
|
|
777
|
+
parentId: historyTurnParentId(input.entry, input.eventId),
|
|
390
778
|
type: "model.reasoning",
|
|
391
779
|
title: "Thinking",
|
|
392
780
|
status: "done",
|
|
@@ -399,14 +787,16 @@ function createReasoningNode(input) {
|
|
|
399
787
|
children: [],
|
|
400
788
|
};
|
|
401
789
|
}
|
|
402
|
-
function createToolCallNode(piboSessionId, entry, entryIndex, contentPartIndex, part) {
|
|
790
|
+
function createToolCallNode(piboSessionId, entry, entryIndex, contentPartIndex, part, eventId, toolInvocationOrdinal = 0, qualifyProjectionIdentity = false) {
|
|
403
791
|
const source = traceSource(entry.source);
|
|
792
|
+
const toolNodeId = historyToolNodeId(entry, part.toolCallId, eventId, toolInvocationOrdinal, qualifyProjectionIdentity);
|
|
404
793
|
return {
|
|
405
|
-
id:
|
|
794
|
+
id: toolNodeId,
|
|
406
795
|
entryId: entry.nativeEntryId,
|
|
796
|
+
nativeTurnId: nativeHistoryTurnId(entry),
|
|
407
797
|
piboSessionId,
|
|
408
|
-
eventId
|
|
409
|
-
parentId: entry
|
|
798
|
+
eventId,
|
|
799
|
+
parentId: historyTurnParentId(entry, eventId),
|
|
410
800
|
toolCallId: part.toolCallId,
|
|
411
801
|
type: isSubagentToolName(part.toolName) ? "agent.delegation" : "tool.call",
|
|
412
802
|
title: part.toolName,
|
|
@@ -414,41 +804,48 @@ function createToolCallNode(piboSessionId, entry, entryIndex, contentPartIndex,
|
|
|
414
804
|
startedAt: entry.createdAt,
|
|
415
805
|
input: part.input ?? {},
|
|
416
806
|
source,
|
|
417
|
-
stableKey:
|
|
807
|
+
stableKey: toolNodeId,
|
|
418
808
|
orderKey: historyTraceOrder(historyIndex(entry, entryIndex), contentPartIndex, isSubagentToolName(part.toolName) ? "agent.delegation" : "tool.call", source),
|
|
419
809
|
children: [],
|
|
420
810
|
};
|
|
421
811
|
}
|
|
422
|
-
function mergePersistedToolResult(toolsByCallId, childNodes, entry, piboSessionId, entryIndex) {
|
|
812
|
+
function mergePersistedToolResult(toolsByCallId, childNodes, entry, piboSessionId, entryIndex, eventId, toolInvocationOrdinal = 0, qualifyProjectionIdentity = false) {
|
|
423
813
|
const toolCallId = entry.toolCallId;
|
|
424
814
|
if (!toolCallId)
|
|
425
815
|
return;
|
|
426
|
-
|
|
816
|
+
const pendingTools = toolsByCallId.get(toolCallId);
|
|
817
|
+
let toolNode = pendingTools?.shift();
|
|
818
|
+
if (pendingTools?.length === 0)
|
|
819
|
+
toolsByCallId.delete(toolCallId);
|
|
427
820
|
if (!toolNode) {
|
|
428
|
-
toolNode = createMissingToolResultNode(piboSessionId, entry, entryIndex, toolCallId);
|
|
821
|
+
toolNode = createMissingToolResultNode(piboSessionId, entry, entryIndex, toolCallId, eventId, toolInvocationOrdinal, qualifyProjectionIdentity);
|
|
429
822
|
childNodes.push(toolNode);
|
|
430
|
-
toolsByCallId.set(toolCallId, toolNode);
|
|
431
823
|
}
|
|
824
|
+
toolNode.eventId ??= eventId;
|
|
825
|
+
toolNode.nativeTurnId ??= nativeHistoryTurnId(entry);
|
|
432
826
|
toolNode.status = entry.isError === true || entry.status === "error" ? "error" : "done";
|
|
433
827
|
toolNode.completedAt = entry.createdAt;
|
|
434
828
|
toolNode.output = entry.result ?? { content: entry.content };
|
|
435
829
|
toolNode.error = toolNode.status === "error" ? stringifyPreview(toolNode.output) : undefined;
|
|
436
830
|
attachAsyncAgentRunNode(toolNode, piboSessionId, entry.createdAt);
|
|
437
831
|
}
|
|
438
|
-
function createMissingToolResultNode(piboSessionId, entry, entryIndex, toolCallId) {
|
|
832
|
+
function createMissingToolResultNode(piboSessionId, entry, entryIndex, toolCallId, eventId, toolInvocationOrdinal = 0, qualifyProjectionIdentity = false) {
|
|
439
833
|
const source = traceSource(entry.source);
|
|
834
|
+
const toolNodeId = historyToolNodeId(entry, toolCallId, eventId, toolInvocationOrdinal, qualifyProjectionIdentity);
|
|
440
835
|
return {
|
|
441
|
-
id:
|
|
836
|
+
id: toolNodeId,
|
|
442
837
|
entryId: entry.nativeEntryId,
|
|
838
|
+
nativeTurnId: nativeHistoryTurnId(entry),
|
|
443
839
|
piboSessionId,
|
|
444
|
-
eventId
|
|
840
|
+
eventId,
|
|
841
|
+
parentId: historyTurnParentId(entry, eventId),
|
|
445
842
|
toolCallId,
|
|
446
843
|
type: "tool.result",
|
|
447
844
|
title: entry.toolName ?? "Tool Result",
|
|
448
845
|
status: "done",
|
|
449
846
|
startedAt: entry.createdAt,
|
|
450
847
|
source,
|
|
451
|
-
stableKey:
|
|
848
|
+
stableKey: toolNodeId,
|
|
452
849
|
orderKey: historyTraceOrder(historyIndex(entry, entryIndex), 0, "tool.result", source),
|
|
453
850
|
children: [],
|
|
454
851
|
};
|
|
@@ -459,9 +856,10 @@ function createAssistantMessageNode(input) {
|
|
|
459
856
|
return {
|
|
460
857
|
id: eventIdentity ? assistantMessageNodeId(eventIdentity) : `${historyEntryNodeId(input.entry)}:response`,
|
|
461
858
|
entryId: input.entry.nativeEntryId,
|
|
859
|
+
nativeTurnId: nativeHistoryTurnId(input.entry),
|
|
462
860
|
piboSessionId: input.piboSessionId,
|
|
463
861
|
eventId: input.eventId,
|
|
464
|
-
parentId: input.entry
|
|
862
|
+
parentId: historyTurnParentId(input.entry, input.eventId),
|
|
465
863
|
type: "assistant.message",
|
|
466
864
|
title: "Agent Message",
|
|
467
865
|
status: input.status,
|
|
@@ -491,12 +889,80 @@ function historyMessageStatus(entry) {
|
|
|
491
889
|
return "running";
|
|
492
890
|
return "done";
|
|
493
891
|
}
|
|
494
|
-
function
|
|
495
|
-
return
|
|
892
|
+
function canonicalProductTurnId(entry, timing, fallbackEventId) {
|
|
893
|
+
return fallbackEventId ?? timing?.eventId ?? entry.turnId;
|
|
894
|
+
}
|
|
895
|
+
function nativeHistoryTurnId(entry) {
|
|
896
|
+
return entry.nativeTurnId ?? (entry.source === "native" ? entry.turnId : undefined);
|
|
897
|
+
}
|
|
898
|
+
function historyTurnParentId(entry, eventId) {
|
|
899
|
+
return entry.source === "product" && eventId ? messageTurnNodeId(eventId) : undefined;
|
|
496
900
|
}
|
|
497
901
|
function historyEntryNodeId(entry) {
|
|
902
|
+
if (entry.source === "native" && entry.historyPosition)
|
|
903
|
+
return `history:${entry.historyPosition}`;
|
|
498
904
|
return entry.source === "native" && entry.nativeEntryId ? `entry:${entry.nativeEntryId}` : `history:${entry.id}`;
|
|
499
905
|
}
|
|
906
|
+
function historyToolNodeId(entry, toolCallId, eventId, toolInvocationOrdinal, qualifyProjectionIdentity) {
|
|
907
|
+
if (entry.source !== "native" || !qualifyProjectionIdentity)
|
|
908
|
+
return `tool:${toolCallId}`;
|
|
909
|
+
return qualifiedHistoryToolNodeId(toolCallId, eventId ?? `native-history-tool:${entry.historyPosition ?? entry.id}`, toolInvocationOrdinal);
|
|
910
|
+
}
|
|
911
|
+
function nativeToolCallIdsRequiringQualification(entries, reconciliationProof) {
|
|
912
|
+
const localNativeToolCallIds = nativeToolCallIds(entries);
|
|
913
|
+
if (!reconciliationProof)
|
|
914
|
+
return duplicateNativeToolCallIds(entries);
|
|
915
|
+
if (!reconciliationProof.complete
|
|
916
|
+
|| reconciliationProof.entries.length > TRACE_RECONCILIATION_ENTRY_CAP
|
|
917
|
+
|| !structuralClaimMatchesPage(entries, reconciliationProof))
|
|
918
|
+
return localNativeToolCallIds;
|
|
919
|
+
return duplicateNativeToolCallIds(reconciliationProof.entries);
|
|
920
|
+
}
|
|
921
|
+
function nativeToolCallIds(entries) {
|
|
922
|
+
const result = new Set();
|
|
923
|
+
for (const entry of entries) {
|
|
924
|
+
if (entry.type !== "message" || entry.source !== "native")
|
|
925
|
+
continue;
|
|
926
|
+
if (entry.role === "assistant") {
|
|
927
|
+
for (const part of historyMessageParts(entry)) {
|
|
928
|
+
if (part.type === "tool_call")
|
|
929
|
+
result.add(part.toolCallId);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else if (entry.role === "tool" && entry.toolCallId) {
|
|
933
|
+
result.add(entry.toolCallId);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return result;
|
|
937
|
+
}
|
|
938
|
+
function duplicateNativeToolCallIds(entries) {
|
|
939
|
+
const counts = new Map();
|
|
940
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
941
|
+
const entry = entries[index];
|
|
942
|
+
if (entry.type !== "message" || (entry.role !== "assistant" && entry.role !== "tool"))
|
|
943
|
+
continue;
|
|
944
|
+
const turn = collectAssistantTurn(entries, index);
|
|
945
|
+
const callIds = new Set();
|
|
946
|
+
for (const { entry: turnEntry } of turn.entries) {
|
|
947
|
+
if (turnEntry.source !== "native")
|
|
948
|
+
continue;
|
|
949
|
+
if (turnEntry.role === "assistant") {
|
|
950
|
+
for (const part of historyMessageParts(turnEntry)) {
|
|
951
|
+
if (part.type !== "tool_call")
|
|
952
|
+
continue;
|
|
953
|
+
callIds.add(part.toolCallId);
|
|
954
|
+
counts.set(part.toolCallId, (counts.get(part.toolCallId) ?? 0) + 1);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
else if (turnEntry.toolCallId && !callIds.has(turnEntry.toolCallId)) {
|
|
958
|
+
callIds.add(turnEntry.toolCallId);
|
|
959
|
+
counts.set(turnEntry.toolCallId, (counts.get(turnEntry.toolCallId) ?? 0) + 1);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
index = turn.nextIndex - 1;
|
|
963
|
+
}
|
|
964
|
+
return new Set([...counts].filter(([, count]) => count > 1).map(([toolCallId]) => toolCallId));
|
|
965
|
+
}
|
|
500
966
|
function historyEntryStableKey(entry) {
|
|
501
967
|
return historyEntryNodeId(entry);
|
|
502
968
|
}
|
|
@@ -511,9 +977,43 @@ function normalizedPrompt(value) {
|
|
|
511
977
|
return normalized || undefined;
|
|
512
978
|
}
|
|
513
979
|
function parsedTimestamp(value) {
|
|
514
|
-
if (
|
|
980
|
+
if (typeof value !== "string")
|
|
981
|
+
return undefined;
|
|
982
|
+
const match = PERSISTED_TIMESTAMP_PATTERN.exec(value);
|
|
983
|
+
if (!match)
|
|
984
|
+
return undefined;
|
|
985
|
+
const year = Number(match[1]);
|
|
986
|
+
const month = Number(match[2]);
|
|
987
|
+
const day = Number(match[3]);
|
|
988
|
+
const hour = Number(match[4]);
|
|
989
|
+
const minute = Number(match[5]);
|
|
990
|
+
const second = Number(match[6]);
|
|
991
|
+
const millisecond = Number((match[7] ?? "").padEnd(3, "0").slice(0, 3));
|
|
992
|
+
const offsetHour = Number(match[10] ?? 0);
|
|
993
|
+
const offsetMinute = Number(match[11] ?? 0);
|
|
994
|
+
if (month < 1 || month > 12 ||
|
|
995
|
+
day < 1 || day > 31 ||
|
|
996
|
+
hour > 23 ||
|
|
997
|
+
minute > 59 ||
|
|
998
|
+
second > 59 ||
|
|
999
|
+
offsetHour > 23 ||
|
|
1000
|
+
offsetMinute > 59)
|
|
1001
|
+
return undefined;
|
|
1002
|
+
const local = new Date(0);
|
|
1003
|
+
local.setUTCFullYear(year, month - 1, day);
|
|
1004
|
+
local.setUTCHours(hour, minute, second, millisecond);
|
|
1005
|
+
if (local.getUTCFullYear() !== year ||
|
|
1006
|
+
local.getUTCMonth() !== month - 1 ||
|
|
1007
|
+
local.getUTCDate() !== day ||
|
|
1008
|
+
local.getUTCHours() !== hour ||
|
|
1009
|
+
local.getUTCMinutes() !== minute ||
|
|
1010
|
+
local.getUTCSeconds() !== second)
|
|
515
1011
|
return undefined;
|
|
516
|
-
const
|
|
1012
|
+
const offsetSign = match[9] === "-" ? -1 : 1;
|
|
1013
|
+
const offsetMs = match[8] === "Z"
|
|
1014
|
+
? 0
|
|
1015
|
+
: offsetSign * (offsetHour * 60 + offsetMinute) * 60_000;
|
|
1016
|
+
const timestamp = local.getTime() - offsetMs;
|
|
517
1017
|
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
518
1018
|
}
|
|
519
1019
|
function hasVisibleText(value) {
|