@agent-native/core 0.203.0-nightly-20261007064822 → 0.203.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/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/model-config.d.ts +2 -2
- package/dist/agent/model-config.js +0 -2
- package/dist/agent/thread-data-builder.d.ts +6 -0
- package/dist/agent/thread-data-builder.js +747 -25
- package/dist/agent/thread-message-projection.d.ts +20 -0
- package/dist/agent/thread-message-projection.js +300 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/chat-threads/store.d.ts +4 -1
- package/dist/chat-threads/store.js +92 -17
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +10 -4
- package/dist/cli/skills-content/turn-into-app-skill.js +2454 -705
- package/dist/cli/skills.d.ts +6 -0
- package/dist/cli/skills.js +8 -2
- package/dist/client/chat/agentkit-agent-native.js +839 -89
- package/dist/client/chat/agentkit-protocol.js +67 -17
- package/dist/collab/routes.d.ts +2 -2
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/mcp/build-server.d.ts +5 -1
- package/dist/mcp/build-server.js +56 -13
- package/dist/mcp/embed-app.js +6 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/index.d.ts +2 -2
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-chat/mcp-options.d.ts +4 -0
- package/dist/server/agent-chat-plugin.js +97 -22
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/onboarding-html.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/embed-auth.js +5 -4
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -24
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/deployment-recovery.md +53 -0
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/documentation-proof.md +7 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +284 -461
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +2 -2
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +65 -90
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/local-run-and-deploy.md +161 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/review-loop.md +384 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +116 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +206 -190
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-archetypes.md +381 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-direction.md +323 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/ui-palettes.md +564 -0
- package/dist/triggers/actions/manage-automation.d.ts +7 -7
- package/docs/content/observability.mdx +136 -0
- package/package.json +4 -4
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -24
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/deployment-recovery.md +53 -0
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/documentation-proof.md +7 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +284 -461
- package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +2 -2
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +65 -90
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/local-run-and-deploy.md +161 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/review-loop.md +384 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +116 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/spreadsheet-source.md +206 -190
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-archetypes.md +381 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-direction.md +323 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/ui-palettes.md +564 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function threadMessageRecord(value: unknown): Record<string, unknown> | undefined;
|
|
2
|
+
/** AgentKit messages that represent durable root assistant messages. */
|
|
3
|
+
export declare function projectRootAssistantMessages(input: {
|
|
4
|
+
rootMessages: unknown;
|
|
5
|
+
snapshotMessages: unknown;
|
|
6
|
+
events?: unknown;
|
|
7
|
+
runs?: unknown;
|
|
8
|
+
toolCalls?: unknown;
|
|
9
|
+
}): {
|
|
10
|
+
representedRootMessageIds: Set<string>;
|
|
11
|
+
snapshotMessageIdsByRootMessageId: Map<string, string>;
|
|
12
|
+
};
|
|
13
|
+
/** Root assistant IDs represented by the AgentKit snapshot. */
|
|
14
|
+
export declare function representedRootAssistantMessageIds(input: {
|
|
15
|
+
rootMessages: unknown;
|
|
16
|
+
snapshotMessages: unknown;
|
|
17
|
+
events?: unknown;
|
|
18
|
+
runs?: unknown;
|
|
19
|
+
toolCalls?: unknown;
|
|
20
|
+
}): Set<string>;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
const REPOSITORY_PART_MEDIA_TYPE = "application/x-agent-native-repository-part";
|
|
2
|
+
function record(value) {
|
|
3
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
function message(value) {
|
|
8
|
+
const outer = record(value);
|
|
9
|
+
return record(outer?.message) ?? outer;
|
|
10
|
+
}
|
|
11
|
+
export function threadMessageRecord(value) {
|
|
12
|
+
return message(value);
|
|
13
|
+
}
|
|
14
|
+
function parts(value) {
|
|
15
|
+
const content = value.parts ?? value.content;
|
|
16
|
+
const values = Array.isArray(content)
|
|
17
|
+
? content
|
|
18
|
+
: typeof content === "string"
|
|
19
|
+
? [{ type: "text", text: content }]
|
|
20
|
+
: [];
|
|
21
|
+
return values.flatMap((part) => {
|
|
22
|
+
const entry = record(part);
|
|
23
|
+
if (!entry)
|
|
24
|
+
return [];
|
|
25
|
+
return entry.type === "tool-call"
|
|
26
|
+
? [
|
|
27
|
+
{
|
|
28
|
+
type: "data",
|
|
29
|
+
mediaType: REPOSITORY_PART_MEDIA_TYPE,
|
|
30
|
+
data: entry,
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
: [entry];
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function explicitRunId(value) {
|
|
37
|
+
const runId = record(value.metadata)?.runId;
|
|
38
|
+
return typeof runId === "string" ? runId : undefined;
|
|
39
|
+
}
|
|
40
|
+
function foldedRunIds(value) {
|
|
41
|
+
const custom = record(record(value.metadata)?.custom);
|
|
42
|
+
const folded = Array.isArray(custom?.foldedRunIds)
|
|
43
|
+
? custom.foldedRunIds.filter((runId) => typeof runId === "string")
|
|
44
|
+
: [];
|
|
45
|
+
const runId = explicitRunId(value);
|
|
46
|
+
return [...new Set([...folded, ...(runId ? [runId] : [])])];
|
|
47
|
+
}
|
|
48
|
+
function eventRunIds(events, runs) {
|
|
49
|
+
const result = new Map();
|
|
50
|
+
const remember = (messageId, runId) => {
|
|
51
|
+
if (typeof messageId !== "string" || typeof runId !== "string")
|
|
52
|
+
return;
|
|
53
|
+
const ids = result.get(messageId) ?? new Set();
|
|
54
|
+
ids.add(runId);
|
|
55
|
+
result.set(messageId, ids);
|
|
56
|
+
};
|
|
57
|
+
for (const value of Array.isArray(events) ? events : []) {
|
|
58
|
+
const event = record(value);
|
|
59
|
+
const eventMessage = record(event?.message);
|
|
60
|
+
if ((event?.type === "message.created" ||
|
|
61
|
+
event?.type === "message.completed") &&
|
|
62
|
+
eventMessage?.role === "assistant") {
|
|
63
|
+
remember(eventMessage.id ?? event?.messageId, event?.runId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const value of Array.isArray(runs) ? runs : []) {
|
|
67
|
+
const run = record(value);
|
|
68
|
+
remember(run?.activeMessageId, run?.id);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function eventMessageIdsByRun(runIdsByMessageId) {
|
|
73
|
+
const result = new Map();
|
|
74
|
+
for (const [messageId, runIds] of runIdsByMessageId) {
|
|
75
|
+
for (const runId of runIds) {
|
|
76
|
+
const messageIds = result.get(runId) ?? new Set();
|
|
77
|
+
messageIds.add(messageId);
|
|
78
|
+
result.set(runId, messageIds);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
function assistantRunId(value, runIdsByMessageId) {
|
|
84
|
+
if (value.role !== "assistant")
|
|
85
|
+
return undefined;
|
|
86
|
+
const eventRunIds = typeof value.id === "string" ? runIdsByMessageId.get(value.id) : undefined;
|
|
87
|
+
return eventRunIds?.size === 1 ? [...eventRunIds][0] : explicitRunId(value);
|
|
88
|
+
}
|
|
89
|
+
function text(value, ignoreNonTextParts = false) {
|
|
90
|
+
if (value.role !== "assistant")
|
|
91
|
+
return undefined;
|
|
92
|
+
const messageParts = parts(value);
|
|
93
|
+
if (!ignoreNonTextParts &&
|
|
94
|
+
messageParts.some((part) => part.type !== "text" &&
|
|
95
|
+
part.type !== "reasoning" &&
|
|
96
|
+
!(part.type === "data" &&
|
|
97
|
+
part.mediaType === REPOSITORY_PART_MEDIA_TYPE &&
|
|
98
|
+
record(part.data)?.type === "tool-call"))) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return messageParts
|
|
102
|
+
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
103
|
+
.map((part) => part.text)
|
|
104
|
+
.join("");
|
|
105
|
+
}
|
|
106
|
+
function reasoning(value) {
|
|
107
|
+
return parts(value)
|
|
108
|
+
.flatMap((part) => part.type === "reasoning" && typeof part.text === "string"
|
|
109
|
+
? [part.text]
|
|
110
|
+
: [])
|
|
111
|
+
.join("");
|
|
112
|
+
}
|
|
113
|
+
function isPrefixCompatible(left, right) {
|
|
114
|
+
return left.startsWith(right) || right.startsWith(left);
|
|
115
|
+
}
|
|
116
|
+
function rootToolCalls(value) {
|
|
117
|
+
return parts(value).flatMap((part) => {
|
|
118
|
+
if (part.type !== "data" || part.mediaType !== REPOSITORY_PART_MEDIA_TYPE) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
const data = record(part.data);
|
|
122
|
+
return data?.type === "tool-call" ? [data] : [];
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function sameValue(left, right) {
|
|
126
|
+
if (Object.is(left, right))
|
|
127
|
+
return true;
|
|
128
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
129
|
+
return (Array.isArray(left) &&
|
|
130
|
+
Array.isArray(right) &&
|
|
131
|
+
left.length === right.length &&
|
|
132
|
+
left.every((value, index) => sameValue(value, right[index])));
|
|
133
|
+
}
|
|
134
|
+
const leftRecord = record(left);
|
|
135
|
+
const rightRecord = record(right);
|
|
136
|
+
if (!leftRecord || !rightRecord)
|
|
137
|
+
return false;
|
|
138
|
+
const leftKeys = Object.keys(leftRecord).sort();
|
|
139
|
+
const rightKeys = Object.keys(rightRecord).sort();
|
|
140
|
+
return (leftKeys.length === rightKeys.length &&
|
|
141
|
+
leftKeys.every((key, index) => key === rightKeys[index] &&
|
|
142
|
+
sameValue(leftRecord[key], rightRecord[key])));
|
|
143
|
+
}
|
|
144
|
+
function representedToolCallMessageIds(value, toolCalls) {
|
|
145
|
+
const calls = rootToolCalls(value);
|
|
146
|
+
const byId = new Map((Array.isArray(toolCalls) ? toolCalls : []).flatMap((entry) => {
|
|
147
|
+
const call = record(entry);
|
|
148
|
+
return typeof call?.id === "string" ? [[call.id, call]] : [];
|
|
149
|
+
}));
|
|
150
|
+
const messageIds = new Set();
|
|
151
|
+
for (const rootCall of calls) {
|
|
152
|
+
const id = typeof rootCall.toolCallId === "string"
|
|
153
|
+
? rootCall.toolCallId
|
|
154
|
+
: typeof rootCall.id === "string"
|
|
155
|
+
? rootCall.id
|
|
156
|
+
: undefined;
|
|
157
|
+
const call = id ? byId.get(id) : undefined;
|
|
158
|
+
if (!call ||
|
|
159
|
+
(typeof rootCall.toolName === "string" &&
|
|
160
|
+
rootCall.toolName !== call.name) ||
|
|
161
|
+
(Object.prototype.hasOwnProperty.call(rootCall, "args") &&
|
|
162
|
+
!sameValue(rootCall.args, call.input)) ||
|
|
163
|
+
(Object.prototype.hasOwnProperty.call(rootCall, "result") &&
|
|
164
|
+
!sameValue(rootCall.result, call.output))) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (typeof call.messageId === "string")
|
|
168
|
+
messageIds.add(call.messageId);
|
|
169
|
+
}
|
|
170
|
+
return messageIds;
|
|
171
|
+
}
|
|
172
|
+
function contentMatches(root, candidate, toolMessageIds) {
|
|
173
|
+
const rootText = text(root);
|
|
174
|
+
const candidateText = text(candidate);
|
|
175
|
+
if (rootText === undefined || candidateText === undefined)
|
|
176
|
+
return false;
|
|
177
|
+
const sameText = Boolean(rootText && candidateText && isPrefixCompatible(rootText, candidateText));
|
|
178
|
+
const reasoningMatches = Boolean(reasoning(root) && reasoning(root) === reasoning(candidate));
|
|
179
|
+
const toolCallMatch = Boolean(typeof candidate.id === "string" && toolMessageIds?.has(candidate.id));
|
|
180
|
+
if (rootToolCalls(root).length > 0 && toolMessageIds === undefined) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return ((sameText && (rootText.length > 0 || candidateText.length > 0)) ||
|
|
184
|
+
(reasoningMatches && !rootText && !candidateText) ||
|
|
185
|
+
(toolCallMatch && !rootText && !candidateText));
|
|
186
|
+
}
|
|
187
|
+
function foldedProjectionMatches(root, snapshotMessagesByRunId, foldedIds, messageIdsByRun) {
|
|
188
|
+
if (foldedIds.length < 2)
|
|
189
|
+
return false;
|
|
190
|
+
const groups = foldedIds.map((runId) => (snapshotMessagesByRunId.get(runId) ?? []).filter((candidate) => candidate.id !== root.id));
|
|
191
|
+
const presentIndices = groups.flatMap((group, index) => group.length > 0 ? [index] : []);
|
|
192
|
+
if (presentIndices.length === 0)
|
|
193
|
+
return false;
|
|
194
|
+
const first = presentIndices[0];
|
|
195
|
+
const last = presentIndices.at(-1);
|
|
196
|
+
if (last - first + 1 !== presentIndices.length)
|
|
197
|
+
return false;
|
|
198
|
+
const projectedText = groups
|
|
199
|
+
.slice(first, last + 1)
|
|
200
|
+
.flatMap((group, index) => {
|
|
201
|
+
const runId = foldedIds[first + index];
|
|
202
|
+
if (group.some((candidate) => explicitRunId(candidate) !== runId &&
|
|
203
|
+
messageIdsByRun.get(runId)?.size !== 1)) {
|
|
204
|
+
return [undefined];
|
|
205
|
+
}
|
|
206
|
+
return group.map((candidate) => text(candidate, true));
|
|
207
|
+
});
|
|
208
|
+
if (projectedText.some((value) => value === undefined))
|
|
209
|
+
return false;
|
|
210
|
+
const candidateText = projectedText.join("");
|
|
211
|
+
const rootText = text(root);
|
|
212
|
+
if (!rootText || !candidateText)
|
|
213
|
+
return false;
|
|
214
|
+
const isPrefix = first === 0 && rootText.startsWith(candidateText);
|
|
215
|
+
const isSuffix = last === foldedIds.length - 1 && rootText.endsWith(candidateText);
|
|
216
|
+
return isPrefix || isSuffix;
|
|
217
|
+
}
|
|
218
|
+
/** AgentKit messages that represent durable root assistant messages. */
|
|
219
|
+
export function projectRootAssistantMessages(input) {
|
|
220
|
+
const rootMessages = Array.isArray(input.rootMessages)
|
|
221
|
+
? input.rootMessages.flatMap((value) => {
|
|
222
|
+
const entry = message(value);
|
|
223
|
+
return entry ? [entry] : [];
|
|
224
|
+
})
|
|
225
|
+
: [];
|
|
226
|
+
const snapshotMessages = Array.isArray(input.snapshotMessages)
|
|
227
|
+
? input.snapshotMessages.flatMap((value) => {
|
|
228
|
+
const entry = message(value);
|
|
229
|
+
return entry ? [entry] : [];
|
|
230
|
+
})
|
|
231
|
+
: [];
|
|
232
|
+
const runIdsByMessageId = eventRunIds(input.events, input.runs);
|
|
233
|
+
const messageIdsByRun = eventMessageIdsByRun(runIdsByMessageId);
|
|
234
|
+
const snapshotMessagesByRunId = new Map();
|
|
235
|
+
for (const candidate of snapshotMessages) {
|
|
236
|
+
const runId = assistantRunId(candidate, runIdsByMessageId);
|
|
237
|
+
if (!runId)
|
|
238
|
+
continue;
|
|
239
|
+
const candidates = snapshotMessagesByRunId.get(runId);
|
|
240
|
+
if (candidates)
|
|
241
|
+
candidates.push(candidate);
|
|
242
|
+
else
|
|
243
|
+
snapshotMessagesByRunId.set(runId, [candidate]);
|
|
244
|
+
}
|
|
245
|
+
const represented = new Set();
|
|
246
|
+
const directMatches = new Map();
|
|
247
|
+
for (const root of rootMessages) {
|
|
248
|
+
if (root.role !== "assistant" || typeof root.id !== "string")
|
|
249
|
+
continue;
|
|
250
|
+
const runId = explicitRunId(root);
|
|
251
|
+
if (!runId)
|
|
252
|
+
continue;
|
|
253
|
+
const toolMessageIds = representedToolCallMessageIds(root, input.toolCalls);
|
|
254
|
+
const sameRunCandidates = (snapshotMessagesByRunId.get(runId) ?? []).filter((candidate) => candidate.id !== root.id);
|
|
255
|
+
const sameRunMatches = sameRunCandidates.filter((candidate) => contentMatches(root, candidate, toolMessageIds));
|
|
256
|
+
const rootText = text(root);
|
|
257
|
+
const matchingCandidate = sameRunMatches[0];
|
|
258
|
+
const exactTextMatch = matchingCandidate !== undefined &&
|
|
259
|
+
rootText !== undefined &&
|
|
260
|
+
text(matchingCandidate) === rootText;
|
|
261
|
+
const clientCanMatchPartial = matchingCandidate !== undefined &&
|
|
262
|
+
(explicitRunId(matchingCandidate) === runId ||
|
|
263
|
+
(typeof matchingCandidate.id === "string" &&
|
|
264
|
+
runIdsByMessageId.get(matchingCandidate.id)?.size === 1 &&
|
|
265
|
+
messageIdsByRun.get(runId)?.size === 1));
|
|
266
|
+
if (sameRunMatches.length === 1 &&
|
|
267
|
+
(exactTextMatch ||
|
|
268
|
+
(sameRunCandidates.length === 1 && clientCanMatchPartial))) {
|
|
269
|
+
represented.add(root.id);
|
|
270
|
+
if (typeof matchingCandidate.id === "string") {
|
|
271
|
+
directMatches.set(root.id, matchingCandidate.id);
|
|
272
|
+
}
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (toolMessageIds !== undefined &&
|
|
276
|
+
foldedProjectionMatches(root, snapshotMessagesByRunId, foldedRunIds(root), messageIdsByRun)) {
|
|
277
|
+
represented.add(root.id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const directMatchCountBySnapshotId = new Map();
|
|
281
|
+
for (const snapshotId of directMatches.values()) {
|
|
282
|
+
directMatchCountBySnapshotId.set(snapshotId, (directMatchCountBySnapshotId.get(snapshotId) ?? 0) + 1);
|
|
283
|
+
}
|
|
284
|
+
const snapshotMessageIdsByRootMessageId = new Map();
|
|
285
|
+
for (const [rootId, snapshotId] of directMatches) {
|
|
286
|
+
if (directMatchCountBySnapshotId.get(snapshotId) !== 1) {
|
|
287
|
+
represented.delete(rootId);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
snapshotMessageIdsByRootMessageId.set(rootId, snapshotId);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
representedRootMessageIds: represented,
|
|
294
|
+
snapshotMessageIdsByRootMessageId,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** Root assistant IDs represented by the AgentKit snapshot. */
|
|
298
|
+
export function representedRootAssistantMessageIds(input) {
|
|
299
|
+
return projectRootAssistantMessages(input).representedRootMessageIds;
|
|
300
|
+
}
|
|
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
139
139
|
duplicate?: undefined;
|
|
140
140
|
eventId?: undefined;
|
|
141
141
|
} | {
|
|
142
|
-
error?: undefined;
|
|
143
142
|
accepted: boolean;
|
|
144
143
|
duplicate: boolean;
|
|
145
144
|
eventId: string;
|
|
145
|
+
error?: undefined;
|
|
146
146
|
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentRunOptions } from "@agent-native/agentkit/protocol";
|
|
2
|
+
import { type ThreadAnnotationSnapshotConflict } from "../agent/thread-data-builder.js";
|
|
2
3
|
import { type AccessContext } from "../sharing/access.js";
|
|
3
4
|
import { type ShareRole } from "../sharing/schema.js";
|
|
4
5
|
export declare function withThreadDataLock<T>(threadId: string, fn: () => Promise<T>): Promise<T>;
|
|
@@ -180,6 +181,8 @@ export interface UpdateThreadDataOptions {
|
|
|
180
181
|
preserveExistingQueuedMessages?: boolean;
|
|
181
182
|
preserveExistingTopLevelKeys?: boolean;
|
|
182
183
|
preserveCurrentMetadata?: boolean;
|
|
184
|
+
preserveCurrentTitleAndPreview?: boolean;
|
|
185
|
+
onAnnotationConflict?: (conflict: ThreadAnnotationSnapshotConflict) => void;
|
|
183
186
|
transformThreadData?: (currentThreadData: string) => string | {
|
|
184
187
|
threadData: string;
|
|
185
188
|
preview?: string;
|
|
@@ -187,7 +190,7 @@ export interface UpdateThreadDataOptions {
|
|
|
187
190
|
maxAttempts?: number;
|
|
188
191
|
ignoreConflicts?: boolean;
|
|
189
192
|
}
|
|
190
|
-
export declare function updateThreadData(id: string, threadData: string, title: string, preview: string, messageCount: number, options?: UpdateThreadDataOptions): Promise<
|
|
193
|
+
export declare function updateThreadData(id: string, threadData: string, title: string, preview: string, messageCount: number, options?: UpdateThreadDataOptions): Promise<boolean>;
|
|
191
194
|
export interface ThreadEngineMeta {
|
|
192
195
|
engineName: string;
|
|
193
196
|
model: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { mergeThreadDataForClientSave, normalizeThreadRepository, normalizeThreadTitle, } from "../agent/thread-data-builder.js";
|
|
3
|
+
import { representedRootAssistantMessageIds, threadMessageRecord, } from "../agent/thread-message-projection.js";
|
|
3
4
|
import { getDbExec } from "../db/client.js";
|
|
4
5
|
import { createGetDb } from "../db/create-get-db.js";
|
|
5
6
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
|
|
@@ -286,7 +287,78 @@ function countThreadMessages(value, fallback) {
|
|
|
286
287
|
if (repoMessageCount === undefined && agentKitMessageCount === undefined) {
|
|
287
288
|
return fallback;
|
|
288
289
|
}
|
|
289
|
-
|
|
290
|
+
const rootMessages = Array.isArray(repo.messages) ? repo.messages : [];
|
|
291
|
+
const agentKitMessages = Array.isArray(repo.agentKit?.messages)
|
|
292
|
+
? repo.agentKit.messages
|
|
293
|
+
: [];
|
|
294
|
+
const mirroredRootIds = representedRootAssistantMessageIds({
|
|
295
|
+
rootMessages,
|
|
296
|
+
snapshotMessages: agentKitMessages,
|
|
297
|
+
events: repo.agentKit?.events,
|
|
298
|
+
runs: repo.agentKit?.runs,
|
|
299
|
+
toolCalls: repo.agentKit?.toolCalls,
|
|
300
|
+
});
|
|
301
|
+
const agentKitUserMessageId = (entry) => {
|
|
302
|
+
const message = threadMessageRecord(entry);
|
|
303
|
+
if (message?.role !== "user")
|
|
304
|
+
return undefined;
|
|
305
|
+
const metadata = message.metadata;
|
|
306
|
+
const custom = metadata && typeof metadata === "object" && !Array.isArray(metadata)
|
|
307
|
+
? metadata.custom
|
|
308
|
+
: undefined;
|
|
309
|
+
const agentKitMessageId = custom && typeof custom === "object" && !Array.isArray(custom)
|
|
310
|
+
? custom.agentKitMessageId
|
|
311
|
+
: undefined;
|
|
312
|
+
return typeof agentKitMessageId === "string"
|
|
313
|
+
? agentKitMessageId
|
|
314
|
+
: undefined;
|
|
315
|
+
};
|
|
316
|
+
const snapshotUserMessageCounts = new Map();
|
|
317
|
+
for (const entry of agentKitMessages) {
|
|
318
|
+
const message = threadMessageRecord(entry);
|
|
319
|
+
if (message?.role !== "user" || typeof message.id !== "string")
|
|
320
|
+
continue;
|
|
321
|
+
snapshotUserMessageCounts.set(message.id, (snapshotUserMessageCounts.get(message.id) ?? 0) + 1);
|
|
322
|
+
}
|
|
323
|
+
const rootUserMirrorIdsByAgentKitId = new Map();
|
|
324
|
+
for (const entry of rootMessages) {
|
|
325
|
+
const message = threadMessageRecord(entry);
|
|
326
|
+
const agentKitId = agentKitUserMessageId(entry);
|
|
327
|
+
if (typeof message?.id !== "string" || !agentKitId)
|
|
328
|
+
continue;
|
|
329
|
+
const rootIds = rootUserMirrorIdsByAgentKitId.get(agentKitId);
|
|
330
|
+
if (rootIds)
|
|
331
|
+
rootIds.push(message.id);
|
|
332
|
+
else
|
|
333
|
+
rootUserMirrorIdsByAgentKitId.set(agentKitId, [message.id]);
|
|
334
|
+
}
|
|
335
|
+
const mirroredRootUserIds = new Set();
|
|
336
|
+
for (const [agentKitId, rootIds] of rootUserMirrorIdsByAgentKitId) {
|
|
337
|
+
if (rootIds.length === 1 &&
|
|
338
|
+
snapshotUserMessageCounts.get(agentKitId) === 1) {
|
|
339
|
+
mirroredRootUserIds.add(rootIds[0]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const messageIds = new Set();
|
|
343
|
+
let unkeyedMessages = 0;
|
|
344
|
+
const countMessage = (entry) => {
|
|
345
|
+
const message = threadMessageRecord(entry);
|
|
346
|
+
if (typeof message?.id === "string")
|
|
347
|
+
messageIds.add(message.id);
|
|
348
|
+
else
|
|
349
|
+
unkeyedMessages += 1;
|
|
350
|
+
};
|
|
351
|
+
for (const entry of rootMessages) {
|
|
352
|
+
const message = threadMessageRecord(entry);
|
|
353
|
+
if (typeof message?.id === "string" &&
|
|
354
|
+
(mirroredRootIds.has(message.id) || mirroredRootUserIds.has(message.id))) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
countMessage(entry);
|
|
358
|
+
}
|
|
359
|
+
for (const entry of agentKitMessages)
|
|
360
|
+
countMessage(entry);
|
|
361
|
+
return messageIds.size + unkeyedMessages;
|
|
290
362
|
}
|
|
291
363
|
function forkThreadData(threadData, forkId, fromMessageId) {
|
|
292
364
|
const parsed = normalizeThreadRepository(JSON.parse(threadData));
|
|
@@ -1010,32 +1082,31 @@ export async function updateThreadData(id, threadData, title, preview, messageCo
|
|
|
1010
1082
|
try {
|
|
1011
1083
|
const current = await getThread(id);
|
|
1012
1084
|
if (!current)
|
|
1013
|
-
return;
|
|
1085
|
+
return false;
|
|
1014
1086
|
const transformed = options.transformThreadData?.(current.threadData);
|
|
1015
1087
|
const incomingThreadData = typeof transformed === "string"
|
|
1016
1088
|
? transformed
|
|
1017
1089
|
: (transformed?.threadData ?? threadData);
|
|
1018
1090
|
let nextThreadData = incomingThreadData;
|
|
1019
1091
|
let nextMessageCount = messageCount;
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
catch {
|
|
1029
|
-
// Keep the caller's serialized value if either JSON blob is malformed.
|
|
1030
|
-
}
|
|
1092
|
+
const annotationConflicts = [];
|
|
1093
|
+
const merged = mergeThreadDataForClientSave(parseThreadData(current.threadData), parseThreadData(incomingThreadData), {
|
|
1094
|
+
preserveExistingQueuedMessages: options.preserveExistingQueuedMessages ?? true,
|
|
1095
|
+
preserveExistingTopLevelKeys: options.preserveExistingTopLevelKeys ?? true,
|
|
1096
|
+
onAnnotationConflict: (conflict) => annotationConflicts.push(conflict),
|
|
1097
|
+
});
|
|
1098
|
+
nextThreadData = JSON.stringify(merged);
|
|
1099
|
+
nextMessageCount = countThreadMessages(merged, messageCount);
|
|
1031
1100
|
const nextUpdatedAt = Math.max(Date.now(), current.updatedAt + 1);
|
|
1032
1101
|
// Completion persistence can race the separate generated-title save.
|
|
1033
1102
|
// Keep a title already committed by that save when this caller only has
|
|
1034
1103
|
// its stale empty snapshot.
|
|
1035
|
-
const
|
|
1104
|
+
const preserveCurrentTitleAndPreview = options.preserveCurrentMetadata ||
|
|
1105
|
+
options.preserveCurrentTitleAndPreview;
|
|
1106
|
+
const nextTitle = preserveCurrentTitleAndPreview
|
|
1036
1107
|
? current.title
|
|
1037
1108
|
: title || current.title;
|
|
1038
|
-
const nextPreview =
|
|
1109
|
+
const nextPreview = preserveCurrentTitleAndPreview
|
|
1039
1110
|
? current.preview
|
|
1040
1111
|
: typeof transformed === "object" && transformed.preview !== undefined
|
|
1041
1112
|
? transformed.preview
|
|
@@ -1054,8 +1125,11 @@ export async function updateThreadData(id, threadData, title, preview, messageCo
|
|
|
1054
1125
|
],
|
|
1055
1126
|
});
|
|
1056
1127
|
if (result.rowsAffected > 0) {
|
|
1128
|
+
for (const conflict of annotationConflicts) {
|
|
1129
|
+
options.onAnnotationConflict?.(conflict);
|
|
1130
|
+
}
|
|
1057
1131
|
emitChatThreadChange(id);
|
|
1058
|
-
return;
|
|
1132
|
+
return true;
|
|
1059
1133
|
}
|
|
1060
1134
|
lastConflict = true;
|
|
1061
1135
|
}
|
|
@@ -1074,12 +1148,13 @@ export async function updateThreadData(id, threadData, title, preview, messageCo
|
|
|
1074
1148
|
throw lastError;
|
|
1075
1149
|
if (lastConflict) {
|
|
1076
1150
|
if (options.ignoreConflicts)
|
|
1077
|
-
return;
|
|
1151
|
+
return false;
|
|
1078
1152
|
const error = new Error(`Failed to update chat thread ${id} after concurrent write conflicts.`);
|
|
1079
1153
|
error.statusCode = 409;
|
|
1080
1154
|
error.statusMessage = error.message;
|
|
1081
1155
|
throw error;
|
|
1082
1156
|
}
|
|
1157
|
+
return false;
|
|
1083
1158
|
}
|
|
1084
1159
|
/**
|
|
1085
1160
|
* Read the engine pinned to a thread (stored in thread_data JSON).
|