@sentry/junior 0.210.2 → 0.212.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-DruBLdjK.js → agent-Cgc7jAIH.js} +353 -21
- package/dist/{agent-hooks-BYZAFpgB.js → agent-hooks-B2Yt2b88.js} +207 -47
- package/dist/{agent-runner-Be5K-k2o.js → agent-runner-CkNX-ynf.js} +5 -5
- package/dist/api/schema.js +2 -2
- package/dist/api.d.ts +1 -1
- package/dist/api.js +72 -33
- package/dist/{app-gyu3D9Gn.d.ts → app-D4nNdzPG.d.ts} +1 -1
- package/dist/app.d.ts +1 -1
- package/dist/app.js +32 -32
- package/dist/{auth-pause-state-DedOKgr4.js → auth-pause-state-BbcDkPac.js} +6 -6
- package/dist/{briefs-CQDK-Dj2.js → briefs-4-OVELML.js} +4 -4
- package/dist/{catalog-runtime-Ddk0rxU3.js → catalog-runtime-BvN_lNVo.js} +1 -1
- package/dist/{checkpoint-Cto3lP0d.js → checkpoint-EAQ5Xerb.js} +151 -528
- package/dist/cli/chat.js +13 -13
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-Dstl8V7v.js → client-C1KycWNq.js} +2 -2
- package/dist/{config-QWyCRQ8L.js → config-C0-PnQoF.js} +2 -2
- package/dist/{conversation-AA4R1VUn.js → conversation-CRWntH-A.js} +19 -19
- package/dist/{conversation-privacy-BIClFm9b.js → conversation-privacy-BFzYxnTS.js} +1 -1
- package/dist/{credential-sync-MFTk0Zba.js → credential-sync-CZz6Vk6X.js} +20 -20
- package/dist/{db-D74z7BZH.d.ts → db-Ca9Nt5ci.d.ts} +1 -1
- package/dist/{execution-stats-BEax3h2o.js → execution-stats-CoDjBQbE.js} +3 -4
- package/dist/{executor-C5onDgaC.js → executor-CaVeGYeA.js} +39 -2
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-Dpfxr7U6.js → job-queue-BhaNX9DL.js} +1 -1
- package/dist/{logging-CKkKExae.js → logging-B5-PLvDo.js} +1 -1
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-W_3TiFJo.js → oauth-callback-server-G5q7i0Qp.js} +2 -2
- package/dist/{pending-auth-CUOLqzoR.js → pending-auth-CDndLOLO.js} +2 -2
- package/dist/{profile-DU-tdAeS.js → profile-BcZ6EBQ4.js} +4 -4
- package/dist/{prompt-C1IA4qkj.js → prompt-kRlibHYT.js} +1 -1
- package/dist/{registry-BiaaVqDO.js → registry-BIeo1ST6.js} +1 -1
- package/dist/{resolve-B178kFFr.js → resolve-BgAZ4KxF.js} +2 -2
- package/dist/{runner-CEw5Bd40.js → runner-Cqd0EG3G.js} +10 -10
- package/dist/{sandbox-egress-signals-R9n0mBQr.js → sandbox-egress-signals-DrOoklMj.js} +1 -1
- package/dist/{schema-CwTeb-BF.js → schema-xHUgeh46.js} +1 -1
- package/dist/short-title-CxQHLU3_.js +65 -0
- package/dist/{skills-DNxkkZnA.js → skills-DlrAH1on.js} +2 -2
- package/dist/{spawn-D_d9fLq3.js → spawn-BtMqHjwx.js} +2 -2
- package/dist/{stats-BvEUKgru.js → stats-D5OWazLs.js} +15 -7
- package/dist/{store-Det8MQ9g.d.ts → store-DoewL5mG.d.ts} +3 -2
- package/dist/{task-queue-DkaL57J_.js → task-queue-C-o2Q4O-.js} +3 -3
- package/dist/{task-runner-DDuflo-o.js → task-runner-Mt_TGUBH.js} +8 -8
- package/dist/{thread-state-fcmz8adQ.js → thread-state-C81fvhJu.js} +5 -5
- package/dist/{turn-execution-BNKloE6i.js → turn-execution-Ruj9SSJU.js} +3 -3
- package/dist/{turn-lifecycle-BEcSsBf7.js → turn-lifecycle-xIZNwgaq.js} +1 -1
- package/dist/{validation-CgH5VUx2.js → validation-sgYqCVwr.js} +1 -1
- package/dist/{version-CS4pJjG8.js → version-BE7Zf9Wx.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-D2ts7w_1.js → web-authorization-BXrGezGm.js} +1 -1
- package/dist/{web-input-Bxb1i41z.js → web-input-FJ7NNJDx.js} +4 -4
- package/dist/{work-HzD-98Ng.js → work-DlYdbVfB.js} +9 -9
- package/dist/{worker-DWDTNdyB.js → worker-BfxedsTU.js} +5 -5
- package/migrations/0042_conversation_metrics.sql +26 -0
- package/migrations/meta/0042_snapshot.json +3610 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +5 -5
- package/dist/usage-BU5d5wmV.js +0 -175
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { i as completeText, m as escapeXml, o as resolveGatewayModel, p as unwrapCurrentInstruction } from "./client-
|
|
2
|
-
import { _ as setSpanAttributes, f as logWarn, o as getActiveTraceId, t as buildTurnFailureResponse, u as logException } from "./logging-
|
|
3
|
-
import { n as botConfig } from "./config-
|
|
1
|
+
import { i as completeText, m as escapeXml, o as resolveGatewayModel, p as unwrapCurrentInstruction } from "./client-C1KycWNq.js";
|
|
2
|
+
import { _ as setSpanAttributes, f as logWarn, o as getActiveTraceId, t as buildTurnFailureResponse, u as logException } from "./logging-B5-PLvDo.js";
|
|
3
|
+
import { n as botConfig } from "./config-C0-PnQoF.js";
|
|
4
4
|
import { _ as parseSlackMessageTs, f as toStoredSlackActor } from "./context-Bplcgypl.js";
|
|
5
|
-
import { Bt as authenticationUnlinkedEvent, Ht as getConversationEventStore, Jt as withConversationEventLock, Kt as getSqlExecutor, Lt as JUNIOR_NATIVE_EVENT_NAMESPACE, Qt as bindProviderConversation, Rt as agentsInstructionsUpdatedEvent, Wt as getConversationStore, Yt as sanitizePostgresJson, qt as createSqlConversationEventStore, zt as authenticationLinkedEvent } from "./agent-hooks-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { A as sleep, D as fenceLock,
|
|
9
|
-
import path from "node:path";
|
|
5
|
+
import { Bt as authenticationUnlinkedEvent, Ht as getConversationEventStore, Jt as withConversationEventLock, Kt as getSqlExecutor, Lt as JUNIOR_NATIVE_EVENT_NAMESPACE, Qt as bindProviderConversation, Rt as agentsInstructionsUpdatedEvent, Wt as getConversationStore, Yt as sanitizePostgresJson, en as addAgentTurnUsage, qt as createSqlConversationEventStore, zt as authenticationLinkedEvent } from "./agent-hooks-B2Yt2b88.js";
|
|
6
|
+
import { F as agentHistoryItemSchema, H as contextProvenance, U as instructionActors, W as instructionProvenanceFor } from "./conversation-CRWntH-A.js";
|
|
7
|
+
import { n as generateShortTitle, t as fallbackShortTitle } from "./short-title-CxQHLU3_.js";
|
|
8
|
+
import { A as sleep, D as fenceLock, T as MUTATION_LOCK_TTL_MS, k as withLock, w as getStateAdapter } from "./profile-BcZ6EBQ4.js";
|
|
10
9
|
import { isDeepStrictEqual } from "node:util";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { createHash } from "node:crypto";
|
|
@@ -19,147 +18,6 @@ z.object({
|
|
|
19
18
|
role: z.string().min(1)
|
|
20
19
|
}).passthrough().transform((value) => value);
|
|
21
20
|
//#endregion
|
|
22
|
-
//#region src/chat/turn-context-tag.ts
|
|
23
|
-
/**
|
|
24
|
-
* Canonical tag name for the runtime turn context block injected into Pi
|
|
25
|
-
* messages. Shared between prompt assembly and turn context stripping to keep
|
|
26
|
-
* both in sync without requiring the full prompt module.
|
|
27
|
-
*/
|
|
28
|
-
const TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region src/chat/pi/transcript.ts
|
|
31
|
-
const RUNTIME_TURN_CONTEXT_START = `<${TURN_CONTEXT_TAG}>`;
|
|
32
|
-
const AGENTS_INSTRUCTIONS_START = "# AGENTS.md instructions";
|
|
33
|
-
function userMessageContent(message) {
|
|
34
|
-
const record = message;
|
|
35
|
-
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
36
|
-
}
|
|
37
|
-
function isRuntimeTurnContextPart(part) {
|
|
38
|
-
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && (part.text.startsWith(RUNTIME_TURN_CONTEXT_START) || part.text.startsWith(AGENTS_INSTRUCTIONS_START));
|
|
39
|
-
}
|
|
40
|
-
function hasCurrentInstruction(message) {
|
|
41
|
-
return userMessageContent(message)?.some((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.includes("<current-instruction")) ?? false;
|
|
42
|
-
}
|
|
43
|
-
function messageTimestamp$1(message) {
|
|
44
|
-
return message.timestamp;
|
|
45
|
-
}
|
|
46
|
-
function isStandaloneRuntimeContextMessage(messages, index) {
|
|
47
|
-
const message = messages[index];
|
|
48
|
-
const next = messages[index + 1];
|
|
49
|
-
return Boolean(message && next && userMessageContent(message) && userMessageContent(next) && messageTimestamp$1(message) === messageTimestamp$1(next) && !hasCurrentInstruction(message) && hasCurrentInstruction(next));
|
|
50
|
-
}
|
|
51
|
-
const EMBEDDED_THREAD_CONTEXT_PATTERN = new RegExp(`<(${[
|
|
52
|
-
"thread-context",
|
|
53
|
-
"recent-thread-messages",
|
|
54
|
-
"thread-compactions",
|
|
55
|
-
"thread-transcript",
|
|
56
|
-
"thread-background"
|
|
57
|
-
].join("|")})(?:\\s[^>]*)?>[\\s\\S]*</\\1>`, "g");
|
|
58
|
-
/** Type guard for Pi SDK assistant messages. */
|
|
59
|
-
function isAssistantMessage(value) {
|
|
60
|
-
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
61
|
-
}
|
|
62
|
-
/** Type guard for Pi SDK tool result messages. */
|
|
63
|
-
function isToolResultMessage(value) {
|
|
64
|
-
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
65
|
-
}
|
|
66
|
-
/** Check whether a tool result carries an error flag. */
|
|
67
|
-
function isToolResultError(result) {
|
|
68
|
-
if (!result || typeof result !== "object") return false;
|
|
69
|
-
return Boolean(result.isError);
|
|
70
|
-
}
|
|
71
|
-
/** Extract the tool name from a raw tool result message. */
|
|
72
|
-
function normalizeToolNameFromResult(result) {
|
|
73
|
-
if (!result || typeof result !== "object") return void 0;
|
|
74
|
-
const record = result;
|
|
75
|
-
if (typeof record.toolName === "string" && record.toolName.length > 0) return record.toolName;
|
|
76
|
-
if (typeof record.name === "string" && record.name.length > 0) return record.name;
|
|
77
|
-
}
|
|
78
|
-
/** Extract role string from a raw Pi message. */
|
|
79
|
-
function getPiMessageRole(value) {
|
|
80
|
-
if (!value || typeof value !== "object") return;
|
|
81
|
-
const role = value.role;
|
|
82
|
-
return typeof role === "string" ? role : void 0;
|
|
83
|
-
}
|
|
84
|
-
/** Return whether Pi can continue directly from the current history tail. */
|
|
85
|
-
function isContinuablePiBoundary(messages) {
|
|
86
|
-
const lastRole = getPiMessageRole(messages.at(-1));
|
|
87
|
-
return lastRole === "user" || lastRole === "toolResult";
|
|
88
|
-
}
|
|
89
|
-
/** Concatenate text content parts from an assistant message. */
|
|
90
|
-
function extractAssistantText(message) {
|
|
91
|
-
return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
|
|
92
|
-
}
|
|
93
|
-
/** Return assistant messages that belong to the terminal post-tool reply phase. */
|
|
94
|
-
function getTerminalAssistantMessages(messages) {
|
|
95
|
-
let lastNonAssistantIndex = -1;
|
|
96
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) if (!isAssistantMessage(messages[index])) {
|
|
97
|
-
lastNonAssistantIndex = index;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
return messages.slice(lastNonAssistantIndex + 1).filter(isAssistantMessage);
|
|
101
|
-
}
|
|
102
|
-
/** Remove trailing assistant messages before committing a resumable boundary. */
|
|
103
|
-
function trimTrailingAssistantMessages(messages) {
|
|
104
|
-
let end = messages.length;
|
|
105
|
-
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") end -= 1;
|
|
106
|
-
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
107
|
-
}
|
|
108
|
-
/** Return whether Pi history already carries session bootstrap context. */
|
|
109
|
-
function hasRuntimeTurnContext(messages) {
|
|
110
|
-
return messages.some((message, index) => isStandaloneRuntimeContextMessage(messages, index) || userMessageContent(message)?.some(isRuntimeTurnContextPart));
|
|
111
|
-
}
|
|
112
|
-
/** Keep only volatile bootstrap messages needed by an in-turn context replacement. */
|
|
113
|
-
function retainRuntimeTurnContext(messages) {
|
|
114
|
-
return messages.flatMap((message, index) => {
|
|
115
|
-
if (isStandaloneRuntimeContextMessage(messages, index)) return [message];
|
|
116
|
-
const runtimeContent = userMessageContent(message)?.filter(isRuntimeTurnContextPart) ?? [];
|
|
117
|
-
return runtimeContent.length > 0 ? [{
|
|
118
|
-
...message,
|
|
119
|
-
content: runtimeContent
|
|
120
|
-
}] : [];
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Reduce a runtime user-turn prompt to only the current turn's instruction.
|
|
125
|
-
*
|
|
126
|
-
* Live user prompts embed prior-thread context as `<thread-context
|
|
127
|
-
* authority="evidence-only">` (or legacy transcript/background blocks) beside
|
|
128
|
-
* the `<current-instruction>` block. Those blocks hold other participants'
|
|
129
|
-
* verbatim messages, so completed-run projections consumed by plugins must
|
|
130
|
-
* expose only the instruction authored by this turn's actor — otherwise
|
|
131
|
-
* per-entry provenance can be defeated by reading another user's text out of an
|
|
132
|
-
* instruction-authority entry. Prior thread context is projected separately as
|
|
133
|
-
* per-author context-authority entries, so dropping it here is non-lossy for
|
|
134
|
-
* plugins. This is projection-only; it never touches what the model sees during
|
|
135
|
-
* a live run.
|
|
136
|
-
*/
|
|
137
|
-
function instructionTextForProjection(text) {
|
|
138
|
-
const withoutContext = text.replace(EMBEDDED_THREAD_CONTEXT_PATTERN, "").trim();
|
|
139
|
-
return unwrapCurrentInstruction(withoutContext) ?? withoutContext;
|
|
140
|
-
}
|
|
141
|
-
/** Return the projected instruction text carried by one user message. */
|
|
142
|
-
function getUserMessageInstructionText(message) {
|
|
143
|
-
if (getPiMessageRole(message) !== "user") return "";
|
|
144
|
-
const content = message.content;
|
|
145
|
-
return instructionTextForProjection(typeof content === "string" ? content : Array.isArray(content) ? content.flatMap((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n") : "").trim();
|
|
146
|
-
}
|
|
147
|
-
/** Remove volatile runtime context before reusing messages as history. */
|
|
148
|
-
function stripRuntimeTurnContext(messages) {
|
|
149
|
-
return messages.flatMap((message, index) => {
|
|
150
|
-
if (isStandaloneRuntimeContextMessage(messages, index)) return [];
|
|
151
|
-
const content = userMessageContent(message);
|
|
152
|
-
if (!content) return [message];
|
|
153
|
-
const nextContent = content.filter((part) => !isRuntimeTurnContextPart(part));
|
|
154
|
-
if (nextContent.length === content.length) return [message];
|
|
155
|
-
if (nextContent.length === 0) return [];
|
|
156
|
-
return [{
|
|
157
|
-
...message,
|
|
158
|
-
content: nextContent
|
|
159
|
-
}];
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
//#endregion
|
|
163
21
|
//#region src/chat/pi/conversation-events.ts
|
|
164
22
|
function authorizationObservationMessage(data, createdAtMs) {
|
|
165
23
|
const label = data.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
@@ -172,9 +30,6 @@ function authorizationObservationMessage(data, createdAtMs) {
|
|
|
172
30
|
timestamp: createdAtMs
|
|
173
31
|
});
|
|
174
32
|
}
|
|
175
|
-
function durableMessages(message) {
|
|
176
|
-
return stripRuntimeTurnContext([message]);
|
|
177
|
-
}
|
|
178
33
|
/** Translate one Pi message into Junior's native durable history item. */
|
|
179
34
|
function historyItemFromPiMessage(message, provenance) {
|
|
180
35
|
const { role, ...fields } = piMessageSchema.parse(message);
|
|
@@ -239,7 +94,8 @@ function projectConversationEvents(events, options) {
|
|
|
239
94
|
if (event.data.type === "compaction" || event.data.type === "handoff") {
|
|
240
95
|
modelProfile = event.data.modelProfile;
|
|
241
96
|
replacementSeq = event.seq;
|
|
242
|
-
for (const replacement of event.data.replacementHistory)
|
|
97
|
+
for (const replacement of event.data.replacementHistory) {
|
|
98
|
+
const message = piMessageFromHistoryItem(replacement.item);
|
|
243
99
|
messages.push(message);
|
|
244
100
|
provenance.push(historyItemProvenance(replacement.item));
|
|
245
101
|
seqs.push(replacement.sourceEventSeq ?? event.seq);
|
|
@@ -247,11 +103,10 @@ function projectConversationEvents(events, options) {
|
|
|
247
103
|
continue;
|
|
248
104
|
}
|
|
249
105
|
if (event.data.type === "user_message" || event.data.type === "assistant_message" || event.data.type === "tool_result") {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
106
|
+
const message = piMessageFromHistoryItem(event.data);
|
|
107
|
+
messages.push(message);
|
|
108
|
+
provenance.push(historyItemProvenance(event.data));
|
|
109
|
+
seqs.push(event.seq);
|
|
255
110
|
continue;
|
|
256
111
|
}
|
|
257
112
|
if (event.data.type === "authorization_completed") {
|
|
@@ -274,7 +129,7 @@ const COMPACTION_TRIGGER_RATIO = .9;
|
|
|
274
129
|
const CONTEXT_INPUT_LIMIT_RATIO = .95;
|
|
275
130
|
const COMPACTION_TARGET_RATIO = .8;
|
|
276
131
|
const FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
277
|
-
function positiveInteger
|
|
132
|
+
function positiveInteger(value, fallback) {
|
|
278
133
|
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
279
134
|
}
|
|
280
135
|
/** Estimate text tokens with the shared coarse heuristic used for local budgets. */
|
|
@@ -283,12 +138,12 @@ function estimateTextTokens(text) {
|
|
|
283
138
|
}
|
|
284
139
|
/** Derive the automatic compaction threshold from model context capacity. */
|
|
285
140
|
function calculateContextCompactionTriggerTokens(model) {
|
|
286
|
-
const contextWindow = positiveInteger
|
|
141
|
+
const contextWindow = positiveInteger(model.contextWindow, FALLBACK_CONTEXT_WINDOW_TOKENS);
|
|
287
142
|
return Math.max(1, Math.floor(contextWindow * COMPACTION_TRIGGER_RATIO));
|
|
288
143
|
}
|
|
289
144
|
/** Derive the maximum estimated input size with room for uncounted request overhead. */
|
|
290
145
|
function calculateContextInputLimitTokens(model) {
|
|
291
|
-
const contextWindow = positiveInteger
|
|
146
|
+
const contextWindow = positiveInteger(model.contextWindow, FALLBACK_CONTEXT_WINDOW_TOKENS);
|
|
292
147
|
return Math.max(1, Math.floor(contextWindow * CONTEXT_INPUT_LIMIT_RATIO));
|
|
293
148
|
}
|
|
294
149
|
/** Derive the post-compaction target from the automatic trigger threshold. */
|
|
@@ -297,7 +152,7 @@ function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
|
297
152
|
}
|
|
298
153
|
/** Cap one model's advertised context capacity with the host bot configuration. */
|
|
299
154
|
function getModelContextBudget(modelId) {
|
|
300
|
-
const advertisedContextWindow = positiveInteger
|
|
155
|
+
const advertisedContextWindow = positiveInteger(resolveGatewayModel(modelId).contextWindow, FALLBACK_CONTEXT_WINDOW_TOKENS);
|
|
301
156
|
return { contextWindow: Math.min(botConfig.contextWindowTokens, advertisedContextWindow) };
|
|
302
157
|
}
|
|
303
158
|
/** Resolve the automatic compaction threshold for the active agent model. */
|
|
@@ -987,7 +842,7 @@ async function loadTurnProjection(args) {
|
|
|
987
842
|
async function loadConnectedMcpProviders(args) {
|
|
988
843
|
return connectedMcpProvidersFromEvents(await getConversationEventStore().loadHistory(args.conversationId), args.credentialSubjectId);
|
|
989
844
|
}
|
|
990
|
-
function messageTimestamp(message) {
|
|
845
|
+
function messageTimestamp$1(message) {
|
|
991
846
|
const timestamp = message.timestamp;
|
|
992
847
|
return typeof timestamp === "number" ? timestamp : Date.now();
|
|
993
848
|
}
|
|
@@ -1019,7 +874,7 @@ async function commitAcceptedReply(args) {
|
|
|
1019
874
|
await createSqlConversationEventStore(executor).append(args.conversationId, [{
|
|
1020
875
|
idempotencyKey: `message:${args.conversationMessageId}:agent`,
|
|
1021
876
|
data: historyItemFromPiMessage(agentMessage, contextProvenance),
|
|
1022
|
-
createdAtMs: messageTimestamp(agentMessage)
|
|
877
|
+
createdAtMs: messageTimestamp$1(agentMessage)
|
|
1023
878
|
}]);
|
|
1024
879
|
}
|
|
1025
880
|
await appendConversationMessages(createSqlConversationEventStore(executor), {
|
|
@@ -1037,7 +892,7 @@ async function commitMessagesLocked(args, executor) {
|
|
|
1037
892
|
const eventStore = createSqlConversationEventStore(executor);
|
|
1038
893
|
const currentEvents = await eventStore.loadCurrentHistory(args.conversationId);
|
|
1039
894
|
const current = projectConversationEvents(currentEvents, { defaultProfile: botConfig.defaultProfile });
|
|
1040
|
-
const nextLocalMessages =
|
|
895
|
+
const nextLocalMessages = args.messages.map(normalizeDurableMessage);
|
|
1041
896
|
const matchingPrefix = countDurablePrefix(current.messages, nextLocalMessages);
|
|
1042
897
|
const nextLocalProvenance = resolveCommitProvenance({
|
|
1043
898
|
existing: current,
|
|
@@ -1064,7 +919,7 @@ async function commitMessagesLocked(args, executor) {
|
|
|
1064
919
|
})) ?? [];
|
|
1065
920
|
const appendedEvents = await eventStore.append(args.conversationId, [...newMessages.map((message, index) => ({
|
|
1066
921
|
data: historyItemFromPiMessage(message, nextLocalProvenance[matchingPrefix + index]),
|
|
1067
|
-
createdAtMs: messageTimestamp(message)
|
|
922
|
+
createdAtMs: messageTimestamp$1(message)
|
|
1068
923
|
})), ...turnContextEvents]);
|
|
1069
924
|
const cursor = appendedEvents.at(-1) ?? currentEvents.at(-1);
|
|
1070
925
|
return {
|
|
@@ -1304,341 +1159,145 @@ async function recordGuardianActionReviewed(args) {
|
|
|
1304
1159
|
}]);
|
|
1305
1160
|
}
|
|
1306
1161
|
//#endregion
|
|
1307
|
-
//#region src/chat/
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
return
|
|
1321
|
-
}
|
|
1322
|
-
/** Keep tool output inside the model-facing budget with an explicit notice. */
|
|
1323
|
-
function truncateText(value, maxChars = MAX_TEXT_CHARS) {
|
|
1324
|
-
if (value.length <= maxChars) return {
|
|
1325
|
-
content: value,
|
|
1326
|
-
truncated: false
|
|
1327
|
-
};
|
|
1328
|
-
const removed = value.length - maxChars;
|
|
1329
|
-
return {
|
|
1330
|
-
content: `${value.slice(0, maxChars)}\n\n[output truncated: ${removed} characters removed]`,
|
|
1331
|
-
truncated: true
|
|
1332
|
-
};
|
|
1162
|
+
//#region src/chat/turn-context-tag.ts
|
|
1163
|
+
/**
|
|
1164
|
+
* Canonical tag name for the runtime turn context block injected into Pi
|
|
1165
|
+
* messages. Shared between prompt assembly and turn context stripping to keep
|
|
1166
|
+
* both in sync without requiring the full prompt module.
|
|
1167
|
+
*/
|
|
1168
|
+
const TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
1169
|
+
//#endregion
|
|
1170
|
+
//#region src/chat/pi/transcript.ts
|
|
1171
|
+
const RUNTIME_TURN_CONTEXT_START = `<${TURN_CONTEXT_TAG}>`;
|
|
1172
|
+
const AGENTS_INSTRUCTIONS_START = "# AGENTS.md instructions";
|
|
1173
|
+
function userMessageContent(message) {
|
|
1174
|
+
const record = message;
|
|
1175
|
+
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
1333
1176
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (!error || typeof error !== "object") return false;
|
|
1337
|
-
const candidate = error;
|
|
1338
|
-
if (candidate.code === "ENOENT") return true;
|
|
1339
|
-
return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
|
|
1340
|
-
}
|
|
1341
|
-
/** Reject an expected missing search/list path through the tool-error channel. */
|
|
1342
|
-
function missingPathSearchResult(params) {
|
|
1343
|
-
const textPath = params.displayPath ?? params.missingPath ?? params.path;
|
|
1344
|
-
throw new ToolInputError(`Path not found: ${textPath}`);
|
|
1345
|
-
}
|
|
1346
|
-
function escapeRegExp(value) {
|
|
1347
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1348
|
-
}
|
|
1349
|
-
function globToRegExp(pattern) {
|
|
1350
|
-
let source = "";
|
|
1351
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
1352
|
-
const char = pattern[index];
|
|
1353
|
-
const next = pattern[index + 1];
|
|
1354
|
-
if (char === "*" && next === "*") {
|
|
1355
|
-
if (pattern[index + 2] === "/") {
|
|
1356
|
-
source += "(?:.*/)?";
|
|
1357
|
-
index += 2;
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
source += ".*";
|
|
1361
|
-
index += 1;
|
|
1362
|
-
continue;
|
|
1363
|
-
}
|
|
1364
|
-
if (char === "*") {
|
|
1365
|
-
source += "[^/]*";
|
|
1366
|
-
continue;
|
|
1367
|
-
}
|
|
1368
|
-
if (char === "?") {
|
|
1369
|
-
source += "[^/]";
|
|
1370
|
-
continue;
|
|
1371
|
-
}
|
|
1372
|
-
source += escapeRegExp(char);
|
|
1373
|
-
}
|
|
1374
|
-
return new RegExp(`^${source}$`);
|
|
1375
|
-
}
|
|
1376
|
-
function matchesGlob(relativePath, pattern) {
|
|
1377
|
-
const matcher = globToRegExp(pattern);
|
|
1378
|
-
if (matcher.test(relativePath)) return true;
|
|
1379
|
-
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) return true;
|
|
1380
|
-
return !pattern.includes("/") && matcher.test(path.posix.basename(relativePath));
|
|
1381
|
-
}
|
|
1382
|
-
/** Keep sandbox filesystem tools scoped to the mounted workspace root. */
|
|
1383
|
-
function resolveWorkspacePath(input, fallback = ".") {
|
|
1384
|
-
const requested = (input ?? "").trim() || fallback;
|
|
1385
|
-
const absolute = requested.startsWith("/") ? requested : path.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
1386
|
-
const normalized = path.posix.normalize(absolute);
|
|
1387
|
-
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) throw new ToolInputError(`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`);
|
|
1388
|
-
return normalized;
|
|
1389
|
-
}
|
|
1390
|
-
/** Anchor ripgrep paths and globs to the requested search root. */
|
|
1391
|
-
function getRipgrepSearchLocation(root, rootIsDirectory) {
|
|
1392
|
-
if (rootIsDirectory) return {
|
|
1393
|
-
cwd: root,
|
|
1394
|
-
target: "."
|
|
1395
|
-
};
|
|
1396
|
-
return {
|
|
1397
|
-
cwd: path.posix.dirname(root),
|
|
1398
|
-
target: path.posix.basename(root)
|
|
1399
|
-
};
|
|
1177
|
+
function isRuntimeTurnContextPart(part) {
|
|
1178
|
+
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && (part.text.startsWith(RUNTIME_TURN_CONTEXT_START) || part.text.startsWith(AGENTS_INSTRUCTIONS_START));
|
|
1400
1179
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const files = [];
|
|
1404
|
-
let limitReached = false;
|
|
1405
|
-
let missingPath;
|
|
1406
|
-
const visit = async (dirPath) => {
|
|
1407
|
-
if (missingPath) return;
|
|
1408
|
-
let entries;
|
|
1409
|
-
try {
|
|
1410
|
-
entries = (await params.fs.readdir(dirPath)).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
1411
|
-
} catch (error) {
|
|
1412
|
-
if (isMissingPathError(error)) {
|
|
1413
|
-
missingPath = dirPath;
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
throw error;
|
|
1417
|
-
}
|
|
1418
|
-
for (const entry of entries) {
|
|
1419
|
-
const fullPath = path.posix.join(dirPath, entry);
|
|
1420
|
-
let stat;
|
|
1421
|
-
try {
|
|
1422
|
-
stat = await params.fs.stat(fullPath);
|
|
1423
|
-
} catch (error) {
|
|
1424
|
-
if (isMissingPathError(error)) {
|
|
1425
|
-
missingPath = fullPath;
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
throw error;
|
|
1429
|
-
}
|
|
1430
|
-
if (stat.isDirectory()) {
|
|
1431
|
-
if (!SKIPPED_DIRECTORIES.has(entry)) await visit(fullPath);
|
|
1432
|
-
if (limitReached) return;
|
|
1433
|
-
continue;
|
|
1434
|
-
}
|
|
1435
|
-
const relativePath = path.posix.relative(params.root, fullPath);
|
|
1436
|
-
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
1437
|
-
files.push(fullPath);
|
|
1438
|
-
if (params.limit && files.length >= params.limit) {
|
|
1439
|
-
limitReached = true;
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
let stat;
|
|
1446
|
-
try {
|
|
1447
|
-
stat = await params.fs.stat(params.root);
|
|
1448
|
-
} catch (error) {
|
|
1449
|
-
if (isMissingPathError(error)) return {
|
|
1450
|
-
files,
|
|
1451
|
-
limitReached: false,
|
|
1452
|
-
missingPath: params.root,
|
|
1453
|
-
missingRoot: true
|
|
1454
|
-
};
|
|
1455
|
-
throw error;
|
|
1456
|
-
}
|
|
1457
|
-
if (!stat.isDirectory()) {
|
|
1458
|
-
const relativePath = path.posix.basename(params.root);
|
|
1459
|
-
return {
|
|
1460
|
-
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
1461
|
-
limitReached: false,
|
|
1462
|
-
missingRoot: false
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
await visit(params.root);
|
|
1466
|
-
return {
|
|
1467
|
-
files,
|
|
1468
|
-
limitReached,
|
|
1469
|
-
missingPath,
|
|
1470
|
-
missingRoot: missingPath === params.root
|
|
1471
|
-
};
|
|
1180
|
+
function hasCurrentInstruction(message) {
|
|
1181
|
+
return userMessageContent(message)?.some((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.includes("<current-instruction")) ?? false;
|
|
1472
1182
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
const AGENTS_FILENAME = "AGENTS.md";
|
|
1476
|
-
const MAX_REPOSITORY_INSTRUCTIONS_BYTES = 32768;
|
|
1477
|
-
const AGENTS_REPLACEMENT_NOTICE = "These AGENTS.md instructions replace all previously provided AGENTS.md instructions.";
|
|
1478
|
-
const AGENTS_REMOVAL_NOTICE = "The previously provided AGENTS.md instructions no longer apply.";
|
|
1479
|
-
async function pathExists(fs, target) {
|
|
1480
|
-
try {
|
|
1481
|
-
await fs.stat(target);
|
|
1482
|
-
return true;
|
|
1483
|
-
} catch (error) {
|
|
1484
|
-
if (isMissingPathError(error)) return false;
|
|
1485
|
-
throw error;
|
|
1486
|
-
}
|
|
1183
|
+
function messageTimestamp(message) {
|
|
1184
|
+
return message.timestamp;
|
|
1487
1185
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
if (isMissingPathError(error)) return false;
|
|
1493
|
-
throw error;
|
|
1494
|
-
}
|
|
1186
|
+
function isStandaloneRuntimeContextMessage(messages, index) {
|
|
1187
|
+
const message = messages[index];
|
|
1188
|
+
const next = messages[index + 1];
|
|
1189
|
+
return Boolean(message && next && userMessageContent(message) && userMessageContent(next) && messageTimestamp(message) === messageTimestamp(next) && !hasCurrentInstruction(message) && hasCurrentInstruction(next));
|
|
1495
1190
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1191
|
+
const EMBEDDED_THREAD_CONTEXT_PATTERN = new RegExp(`<(${[
|
|
1192
|
+
"thread-context",
|
|
1193
|
+
"recent-thread-messages",
|
|
1194
|
+
"thread-compactions",
|
|
1195
|
+
"thread-transcript",
|
|
1196
|
+
"thread-background"
|
|
1197
|
+
].join("|")})(?:\\s[^>]*)?>[\\s\\S]*</\\1>`, "g");
|
|
1198
|
+
/** Type guard for Pi SDK assistant messages. */
|
|
1199
|
+
function isAssistantMessage(value) {
|
|
1200
|
+
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
1505
1201
|
}
|
|
1506
|
-
/**
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const entries = await fs.readdir(SANDBOX_REPOS_ROOT);
|
|
1510
|
-
const repositories = [];
|
|
1511
|
-
for (const entry of entries) {
|
|
1512
|
-
const candidate = path.posix.join(SANDBOX_REPOS_ROOT, entry);
|
|
1513
|
-
if (await directoryExists(fs, candidate) && await pathExists(fs, path.posix.join(candidate, ".git"))) repositories.push(candidate);
|
|
1514
|
-
}
|
|
1515
|
-
return repositories.sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
1202
|
+
/** Type guard for Pi SDK tool result messages. */
|
|
1203
|
+
function isToolResultMessage(value) {
|
|
1204
|
+
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
1516
1205
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
if (isMissingPathError(error)) return;
|
|
1523
|
-
throw error;
|
|
1524
|
-
}
|
|
1206
|
+
/** Check whether a tool result carries an error flag. */
|
|
1207
|
+
function isToolResultError(result) {
|
|
1208
|
+
if (!result || typeof result !== "object") return false;
|
|
1209
|
+
return Boolean(result.isError);
|
|
1525
1210
|
}
|
|
1526
|
-
/**
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
for (;;) {
|
|
1533
|
-
directories.push(current);
|
|
1534
|
-
if (current === gitRoot) break;
|
|
1535
|
-
current = path.posix.dirname(current);
|
|
1536
|
-
}
|
|
1537
|
-
directories.reverse();
|
|
1538
|
-
const sources = [];
|
|
1539
|
-
let remaining = Math.max(0, args.maxBytes ?? MAX_REPOSITORY_INSTRUCTIONS_BYTES);
|
|
1540
|
-
for (const directory of directories) {
|
|
1541
|
-
if (remaining <= 0) break;
|
|
1542
|
-
const filePath = path.posix.join(directory, AGENTS_FILENAME);
|
|
1543
|
-
const content = await readInstructionFile(args.fs, filePath);
|
|
1544
|
-
if (!content) continue;
|
|
1545
|
-
const bytes = Buffer.from(content, "utf8");
|
|
1546
|
-
const bounded = bytes.length <= remaining ? content : bytes.subarray(0, remaining).toString("utf8");
|
|
1547
|
-
sources.push({
|
|
1548
|
-
content: bounded,
|
|
1549
|
-
path: filePath
|
|
1550
|
-
});
|
|
1551
|
-
remaining -= Buffer.byteLength(bounded, "utf8");
|
|
1552
|
-
}
|
|
1553
|
-
if (sources.length === 0) return;
|
|
1554
|
-
const text = formatInstructionSources(sources);
|
|
1555
|
-
const fingerprint = createHash("sha256").update(JSON.stringify({
|
|
1556
|
-
directory: args.cwd,
|
|
1557
|
-
sources
|
|
1558
|
-
})).digest("hex");
|
|
1559
|
-
return {
|
|
1560
|
-
directory: args.cwd,
|
|
1561
|
-
fingerprint,
|
|
1562
|
-
sources,
|
|
1563
|
-
text
|
|
1564
|
-
};
|
|
1211
|
+
/** Extract the tool name from a raw tool result message. */
|
|
1212
|
+
function normalizeToolNameFromResult(result) {
|
|
1213
|
+
if (!result || typeof result !== "object") return void 0;
|
|
1214
|
+
const record = result;
|
|
1215
|
+
if (typeof record.toolName === "string" && record.toolName.length > 0) return record.toolName;
|
|
1216
|
+
if (typeof record.name === "string" && record.name.length > 0) return record.name;
|
|
1565
1217
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1218
|
+
/** Extract role string from a raw Pi message. */
|
|
1219
|
+
function getPiMessageRole(value) {
|
|
1220
|
+
if (!value || typeof value !== "object") return;
|
|
1221
|
+
const role = value.role;
|
|
1222
|
+
return typeof role === "string" ? role : void 0;
|
|
1569
1223
|
}
|
|
1570
|
-
/**
|
|
1571
|
-
function
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const sources = bundles.flatMap((bundle) => bundle.sources);
|
|
1575
|
-
const text = formatInstructionSources(sources);
|
|
1576
|
-
return {
|
|
1577
|
-
fingerprint: createHash("sha256").update(JSON.stringify({
|
|
1578
|
-
directories: bundles.map((bundle) => bundle.directory ?? null),
|
|
1579
|
-
sources
|
|
1580
|
-
})).digest("hex"),
|
|
1581
|
-
sources,
|
|
1582
|
-
text
|
|
1583
|
-
};
|
|
1224
|
+
/** Return whether Pi can continue directly from the current history tail. */
|
|
1225
|
+
function isContinuablePiBoundary(messages) {
|
|
1226
|
+
const lastRole = getPiMessageRole(messages.at(-1));
|
|
1227
|
+
return lastRole === "user" || lastRole === "toolResult";
|
|
1584
1228
|
}
|
|
1585
|
-
/**
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
maxBytes: remaining
|
|
1596
|
-
});
|
|
1597
|
-
if (!instructions) continue;
|
|
1598
|
-
bundles.push(instructions);
|
|
1599
|
-
remaining -= instructions.sources.reduce((total, source) => total + Buffer.byteLength(source.content, "utf8"), 0);
|
|
1229
|
+
/** Concatenate text content parts from an assistant message. */
|
|
1230
|
+
function extractAssistantText(message) {
|
|
1231
|
+
return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
|
|
1232
|
+
}
|
|
1233
|
+
/** Return assistant messages that belong to the terminal post-tool reply phase. */
|
|
1234
|
+
function getTerminalAssistantMessages(messages) {
|
|
1235
|
+
let lastNonAssistantIndex = -1;
|
|
1236
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) if (!isAssistantMessage(messages[index])) {
|
|
1237
|
+
lastNonAssistantIndex = index;
|
|
1238
|
+
break;
|
|
1600
1239
|
}
|
|
1601
|
-
return
|
|
1240
|
+
return messages.slice(lastNonAssistantIndex + 1).filter(isAssistantMessage);
|
|
1602
1241
|
}
|
|
1603
|
-
/**
|
|
1604
|
-
function
|
|
1605
|
-
|
|
1242
|
+
/** Remove trailing assistant messages before committing a resumable boundary. */
|
|
1243
|
+
function trimTrailingAssistantMessages(messages) {
|
|
1244
|
+
let end = messages.length;
|
|
1245
|
+
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") end -= 1;
|
|
1246
|
+
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
1606
1247
|
}
|
|
1607
|
-
/**
|
|
1608
|
-
function
|
|
1609
|
-
return
|
|
1610
|
-
role: "user",
|
|
1611
|
-
content: [{
|
|
1612
|
-
type: "text",
|
|
1613
|
-
text: renderAgentsInstructions(args)
|
|
1614
|
-
}],
|
|
1615
|
-
timestamp: args.timestamp ?? Date.now()
|
|
1616
|
-
};
|
|
1248
|
+
/** Return whether Pi history already carries session bootstrap context. */
|
|
1249
|
+
function hasRuntimeTurnContext(messages) {
|
|
1250
|
+
return messages.some((message, index) => isStandaloneRuntimeContextMessage(messages, index) || userMessageContent(message)?.some(isRuntimeTurnContextPart));
|
|
1617
1251
|
}
|
|
1618
|
-
/**
|
|
1619
|
-
function
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1252
|
+
/** Keep only volatile bootstrap messages needed by an in-turn context replacement. */
|
|
1253
|
+
function retainRuntimeTurnContext(messages) {
|
|
1254
|
+
return messages.flatMap((message, index) => {
|
|
1255
|
+
if (isStandaloneRuntimeContextMessage(messages, index)) return [message];
|
|
1256
|
+
const runtimeContent = userMessageContent(message)?.filter(isRuntimeTurnContextPart) ?? [];
|
|
1257
|
+
return runtimeContent.length > 0 ? [{
|
|
1258
|
+
...message,
|
|
1259
|
+
content: runtimeContent
|
|
1260
|
+
}] : [];
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Reduce a runtime user-turn prompt to only the current turn's instruction.
|
|
1265
|
+
*
|
|
1266
|
+
* Live user prompts embed prior-thread context as `<thread-context
|
|
1267
|
+
* authority="evidence-only">` (or legacy transcript/background blocks) beside
|
|
1268
|
+
* the `<current-instruction>` block. Those blocks hold other participants'
|
|
1269
|
+
* verbatim messages, so completed-run projections consumed by plugins must
|
|
1270
|
+
* expose only the instruction authored by this turn's actor — otherwise
|
|
1271
|
+
* per-entry provenance can be defeated by reading another user's text out of an
|
|
1272
|
+
* instruction-authority entry. Prior thread context is projected separately as
|
|
1273
|
+
* per-author context-authority entries, so dropping it here is non-lossy for
|
|
1274
|
+
* plugins. This is projection-only; it never touches what the model sees during
|
|
1275
|
+
* a live run.
|
|
1276
|
+
*/
|
|
1277
|
+
function instructionTextForProjection(text) {
|
|
1278
|
+
const withoutContext = text.replace(EMBEDDED_THREAD_CONTEXT_PATTERN, "").trim();
|
|
1279
|
+
return unwrapCurrentInstruction(withoutContext) ?? withoutContext;
|
|
1280
|
+
}
|
|
1281
|
+
/** Return the projected instruction text carried by one user message. */
|
|
1282
|
+
function getUserMessageInstructionText(message) {
|
|
1283
|
+
if (getPiMessageRole(message) !== "user") return "";
|
|
1284
|
+
const content = message.content;
|
|
1285
|
+
return instructionTextForProjection(typeof content === "string" ? content : Array.isArray(content) ? content.flatMap((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n") : "").trim();
|
|
1638
1286
|
}
|
|
1639
|
-
/**
|
|
1640
|
-
function
|
|
1641
|
-
return
|
|
1287
|
+
/** Remove runtime context from a non-model projection or explicit replacement. */
|
|
1288
|
+
function stripRuntimeTurnContext(messages) {
|
|
1289
|
+
return messages.flatMap((message, index) => {
|
|
1290
|
+
if (isStandaloneRuntimeContextMessage(messages, index)) return [];
|
|
1291
|
+
const content = userMessageContent(message);
|
|
1292
|
+
if (!content) return [message];
|
|
1293
|
+
const nextContent = content.filter((part) => !isRuntimeTurnContextPart(part));
|
|
1294
|
+
if (nextContent.length === content.length) return [message];
|
|
1295
|
+
if (nextContent.length === 0) return [];
|
|
1296
|
+
return [{
|
|
1297
|
+
...message,
|
|
1298
|
+
content: nextContent
|
|
1299
|
+
}];
|
|
1300
|
+
});
|
|
1642
1301
|
}
|
|
1643
1302
|
//#endregion
|
|
1644
1303
|
//#region src/chat/services/turn-limit.ts
|
|
@@ -1893,8 +1552,7 @@ async function recordConversationActivityMetadata(args) {
|
|
|
1893
1552
|
visibility: hasParent ? void 0 : args.destinationVisibility
|
|
1894
1553
|
});
|
|
1895
1554
|
}
|
|
1896
|
-
function materializeTurnRecord(stored, piProjection, turnStartMessageIndex,
|
|
1897
|
-
const restoredProjection = restoreVolatileContext && (stored.state === "running" || stored.state === "paused") ? restoreRuntimeContext(piProjection, stored.runtimeContext) : piProjection;
|
|
1555
|
+
function materializeTurnRecord(stored, piProjection, turnStartMessageIndex, runtimeMetrics) {
|
|
1898
1556
|
return {
|
|
1899
1557
|
...definedProps$1({ actor: stored.actor }),
|
|
1900
1558
|
schemaVersion: stored.schemaVersion,
|
|
@@ -1906,8 +1564,8 @@ function materializeTurnRecord(stored, piProjection, turnStartMessageIndex, rest
|
|
|
1906
1564
|
startedAtMs: stored.startedAtMs,
|
|
1907
1565
|
lastProgressAtMs: stored.lastProgressAtMs,
|
|
1908
1566
|
updatedAtMs: stored.updatedAtMs,
|
|
1909
|
-
piMessages:
|
|
1910
|
-
piMessageProvenance:
|
|
1567
|
+
piMessages: piProjection.messages,
|
|
1568
|
+
piMessageProvenance: piProjection.provenance,
|
|
1911
1569
|
actors: instructionActors(piProjection.provenance),
|
|
1912
1570
|
cumulativeDurationMs: runtimeMetrics?.cumulativeDurationMs ?? 0,
|
|
1913
1571
|
...definedProps$1({
|
|
@@ -1927,37 +1585,6 @@ function materializeTurnRecord(stored, piProjection, turnStartMessageIndex, rest
|
|
|
1927
1585
|
})
|
|
1928
1586
|
};
|
|
1929
1587
|
}
|
|
1930
|
-
function restoreRuntimeContext(projection, runtimeContext) {
|
|
1931
|
-
if (!runtimeContext || runtimeContext.length === 0) return projection;
|
|
1932
|
-
const restoredMessages = [...projection.messages];
|
|
1933
|
-
const restoredProvenance = [...projection.provenance];
|
|
1934
|
-
const unmatchedRuntimeContext = [];
|
|
1935
|
-
for (const runtimeMessage of runtimeContext) {
|
|
1936
|
-
const runtime = runtimeMessage;
|
|
1937
|
-
const targetIndex = restoredMessages.findIndex((message) => {
|
|
1938
|
-
const candidate = message;
|
|
1939
|
-
return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
|
|
1940
|
-
});
|
|
1941
|
-
if (targetIndex < 0) {
|
|
1942
|
-
if (isAgentsInstructionsMessage(runtimeMessage)) {
|
|
1943
|
-
const followingIndex = restoredMessages.findIndex((message) => {
|
|
1944
|
-
const timestamp = message.timestamp;
|
|
1945
|
-
return typeof runtime.timestamp === "number" && typeof timestamp === "number" && timestamp > runtime.timestamp;
|
|
1946
|
-
});
|
|
1947
|
-
const insertionIndex = followingIndex < 0 ? restoredMessages.length : followingIndex;
|
|
1948
|
-
restoredMessages.splice(insertionIndex, 0, runtimeMessage);
|
|
1949
|
-
restoredProvenance.splice(insertionIndex, 0, contextProvenance);
|
|
1950
|
-
} else unmatchedRuntimeContext.push(runtimeMessage);
|
|
1951
|
-
continue;
|
|
1952
|
-
}
|
|
1953
|
-
restoredMessages.splice(targetIndex, 0, runtimeMessage);
|
|
1954
|
-
restoredProvenance.splice(targetIndex, 0, contextProvenance);
|
|
1955
|
-
}
|
|
1956
|
-
return {
|
|
1957
|
-
messages: [...unmatchedRuntimeContext, ...restoredMessages],
|
|
1958
|
-
provenance: [...unmatchedRuntimeContext.map(() => contextProvenance), ...restoredProvenance]
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
1588
|
/** Read only the stored metadata record without materializing transcript logs. */
|
|
1962
1589
|
async function getStoredTurnRecord(conversationId, turnId) {
|
|
1963
1590
|
const stateAdapter = getStateAdapter();
|
|
@@ -1982,7 +1609,7 @@ async function materializeStoredTurnRecord(conversationId, turnId, followCurrent
|
|
|
1982
1609
|
const turnStartMessageIndex = followsReplacement ? 0 : parsed.turnStartSeq === void 0 ? void 0 : piProjection.seqs.filter((seq) => seq <= parsed.turnStartSeq).length;
|
|
1983
1610
|
const conversation = await getConversationStore().get({ conversationId });
|
|
1984
1611
|
const executionMetrics = executionMetricsForTurn(conversation, turnId);
|
|
1985
|
-
return materializeTurnRecord(parsed, piProjection, turnStartMessageIndex,
|
|
1612
|
+
return materializeTurnRecord(parsed, piProjection, turnStartMessageIndex, {
|
|
1986
1613
|
channelName: conversation?.channelName,
|
|
1987
1614
|
cumulativeDurationMs: executionMetrics?.durationMs,
|
|
1988
1615
|
cumulativeUsage: executionMetrics?.usage
|
|
@@ -2022,7 +1649,6 @@ function buildStoredRecord(args) {
|
|
|
2022
1649
|
source: args.source,
|
|
2023
1650
|
resultMessageId: args.resultMessageId,
|
|
2024
1651
|
resumedFromSliceId: args.resumedFromSliceId,
|
|
2025
|
-
runtimeContext: args.runtimeContext && args.runtimeContext.length > 0 ? args.runtimeContext : void 0,
|
|
2026
1652
|
surface: args.surface,
|
|
2027
1653
|
traceId: args.traceId,
|
|
2028
1654
|
turnStartSeq: args.turnStartSeq
|
|
@@ -2054,7 +1680,7 @@ async function setStoredRecord(args) {
|
|
|
2054
1680
|
return materializeTurnRecord(storedRecord, {
|
|
2055
1681
|
messages: [...args.piMessages],
|
|
2056
1682
|
provenance: [...args.piMessageProvenance]
|
|
2057
|
-
}, args.turnStartMessageIndex,
|
|
1683
|
+
}, args.turnStartMessageIndex, args.runtimeMetrics);
|
|
2058
1684
|
}
|
|
2059
1685
|
/**
|
|
2060
1686
|
* Transition an unfinished turn only if the caller still holds the
|
|
@@ -2127,9 +1753,7 @@ async function upsertTurnRecordLocked(args, fence) {
|
|
|
2127
1753
|
turnId: args.turnId
|
|
2128
1754
|
} } : void 0
|
|
2129
1755
|
});
|
|
2130
|
-
const durableTurnStartMessageIndex = args.turnStartMessageIndex
|
|
2131
|
-
const runtimeContext = retainRuntimeTurnContext(args.piMessages);
|
|
2132
|
-
const retainedRuntimeContext = runtimeContext.length > 0 ? runtimeContext : existingRecord?.historyVersion === commit.historyVersion ? existingRecord.runtimeContext : void 0;
|
|
1756
|
+
const durableTurnStartMessageIndex = args.turnStartMessageIndex;
|
|
2133
1757
|
const turnStartSeq = durableTurnStartMessageIndex === void 0 ? existingRecord?.turnStartSeq : durableTurnStartMessageIndex <= 0 ? -1 : commit.messageSeqs[durableTurnStartMessageIndex - 1] ?? commit.committedSeq;
|
|
2134
1758
|
const turnStartMessageIndex = durableTurnStartMessageIndex ?? (turnStartSeq === void 0 ? void 0 : commit.messageSeqs.filter((seq) => seq <= turnStartSeq).length);
|
|
2135
1759
|
const turnSource = args.source ?? existingRecord?.source;
|
|
@@ -2173,7 +1797,6 @@ async function upsertTurnRecordLocked(args, fence) {
|
|
|
2173
1797
|
source: args.source ?? existingRecord?.source,
|
|
2174
1798
|
resultMessageId: args.resultMessageId ?? existingRecord?.resultMessageId,
|
|
2175
1799
|
resumedFromSliceId: args.resumedFromSliceId,
|
|
2176
|
-
runtimeContext: args.state === "running" || args.state === "paused" ? retainedRuntimeContext : void 0,
|
|
2177
1800
|
startedAtMs: existingRecord?.startedAtMs,
|
|
2178
1801
|
surface: args.surface ?? existingRecord?.surface,
|
|
2179
1802
|
traceId: args.traceId ?? existingRecord?.traceId,
|
|
@@ -2448,4 +2071,4 @@ async function saveDone(args) {
|
|
|
2448
2071
|
});
|
|
2449
2072
|
}
|
|
2450
2073
|
//#endregion
|
|
2451
|
-
export {
|
|
2074
|
+
export { resolveConversationTitle as $, TURN_CONTEXT_TAG as A, recordAuthenticationLinked as B, isContinuablePiBoundary as C, retainRuntimeTurnContext as D, normalizeToolNameFromResult as E, loadProjection as F, recordSubscribedReplyRoute as G, recordAuthorizationRequested as H, loadTurnRoute as I, appendConversationMessages as J, recordToolExecutionStarted as K, openConversationProjection as L, commitMessages as M, loadConnectedMcpProviders as N, stripRuntimeTurnContext as O, loadConversationProjection as P, persistConversationMessageSummaries as Q, recordAgentsInstructionsUpdated as R, isAssistantMessage as S, isToolResultMessage as T, recordGuardianActionReviewed as U, recordAuthorizationCompleted as V, recordMcpProviderConnected as W, persistConversationMessages as X, hydrateConversationMessages as Y, projectConversationMessages as Z, getPiMessageRole as _, completeTurnRecord as a, isHumanConversationMessage as at, hasRuntimeTurnContext as b, getTurnRecordForResume as c, recordDeliveredAssistantMessage as ct, persistWithRetry as d, estimateTextTokens as dt, appendThreadContextMessages as et, TurnSliceLimitExceededError as f, getAgentContextCompactionTriggerTokens as ft, extractAssistantText as g, isTurnExecutionLimitExceededError as h, piMessageFromHistoryItem as ht, abandonTurnRecord as i, getConversationMessageSlackTs as it, commitAcceptedReply as j, trimTrailingAssistantMessages as k, listTurnSummaries as l, turnHasReply as lt, buildTurnErrorResponse as m, historyItemFromPiMessage as mt, loadTurnCheckpoint as n, createConversationMemoryService as nt, failTurnRecord as o, markConversationMessage as ot, assertTurnToolCallLimit as p, getAgentContextInputLimitTokens as pt, recordTurnRoute as q, saveTurnCheckpoint as r, estimateConversationContextTokens as rt, getTurnRecord as s, normalizeConversationText as st, continuableMessages as t, buildConversationContext as tt, recordTurnSummary as u, upsertConversationMessage as ut, getTerminalAssistantMessages as v, isToolResultError as w, instructionTextForProjection as x, getUserMessageInstructionText as y, recordAttachmentsDelivered as z };
|