@mindstudio-ai/remy 0.1.252 → 0.1.254
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/headless.js +144 -64
- package/dist/index.js +152 -71
- package/dist/prompt/compiled/files.md +26 -8
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -914,6 +914,54 @@ ${loadPlanStatus(onboardingState)}
|
|
|
914
914
|
return resolveIncludes(template);
|
|
915
915
|
}
|
|
916
916
|
|
|
917
|
+
// src/automatedActions/sentinel.ts
|
|
918
|
+
function sentinel(name) {
|
|
919
|
+
return `@@automated::${name}@@`;
|
|
920
|
+
}
|
|
921
|
+
function automatedMessage(name, body) {
|
|
922
|
+
return body ? `${sentinel(name)}
|
|
923
|
+
${body}` : sentinel(name);
|
|
924
|
+
}
|
|
925
|
+
function hasSentinel(text, name) {
|
|
926
|
+
return text.startsWith(sentinel(name));
|
|
927
|
+
}
|
|
928
|
+
function isAutomatedMessage(text) {
|
|
929
|
+
return text.startsWith("@@automated::");
|
|
930
|
+
}
|
|
931
|
+
function parseSentinel(text) {
|
|
932
|
+
const match = text.match(/^@@automated::(\w+)@@(.*)/s);
|
|
933
|
+
if (!match) {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
return { name: match[1], remainder: match[2] };
|
|
937
|
+
}
|
|
938
|
+
function stripSentinelLine(text) {
|
|
939
|
+
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
940
|
+
}
|
|
941
|
+
function buildBackgroundResultsMessage(results) {
|
|
942
|
+
const xml = results.map(
|
|
943
|
+
(r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
|
|
944
|
+
${r.result}
|
|
945
|
+
</tool_result>`
|
|
946
|
+
).join("\n\n");
|
|
947
|
+
const plural = results.length > 1 ? "s" : "";
|
|
948
|
+
const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
|
|
949
|
+
<background_results>
|
|
950
|
+
${xml}
|
|
951
|
+
</background_results>`;
|
|
952
|
+
return automatedMessage("background_results", body);
|
|
953
|
+
}
|
|
954
|
+
function mergeBackgroundResultsMessages(messages) {
|
|
955
|
+
const results = [];
|
|
956
|
+
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
957
|
+
for (const msg of messages) {
|
|
958
|
+
for (const m of msg.matchAll(toolRe)) {
|
|
959
|
+
results.push({ toolCallId: m[1], name: m[2], result: m[3] });
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return buildBackgroundResultsMessage(results);
|
|
963
|
+
}
|
|
964
|
+
|
|
917
965
|
// src/tools/spec/readSpec.ts
|
|
918
966
|
import fs5 from "fs/promises";
|
|
919
967
|
|
|
@@ -3187,54 +3235,6 @@ function startStatusWatcher(config) {
|
|
|
3187
3235
|
};
|
|
3188
3236
|
}
|
|
3189
3237
|
|
|
3190
|
-
// src/automatedActions/sentinel.ts
|
|
3191
|
-
function sentinel(name) {
|
|
3192
|
-
return `@@automated::${name}@@`;
|
|
3193
|
-
}
|
|
3194
|
-
function automatedMessage(name, body) {
|
|
3195
|
-
return body ? `${sentinel(name)}
|
|
3196
|
-
${body}` : sentinel(name);
|
|
3197
|
-
}
|
|
3198
|
-
function hasSentinel(text, name) {
|
|
3199
|
-
return text.startsWith(sentinel(name));
|
|
3200
|
-
}
|
|
3201
|
-
function isAutomatedMessage(text) {
|
|
3202
|
-
return text.startsWith("@@automated::");
|
|
3203
|
-
}
|
|
3204
|
-
function parseSentinel(text) {
|
|
3205
|
-
const match = text.match(/^@@automated::(\w+)@@(.*)/s);
|
|
3206
|
-
if (!match) {
|
|
3207
|
-
return null;
|
|
3208
|
-
}
|
|
3209
|
-
return { name: match[1], remainder: match[2] };
|
|
3210
|
-
}
|
|
3211
|
-
function stripSentinelLine(text) {
|
|
3212
|
-
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
3213
|
-
}
|
|
3214
|
-
function buildBackgroundResultsMessage(results) {
|
|
3215
|
-
const xml = results.map(
|
|
3216
|
-
(r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
|
|
3217
|
-
${r.result}
|
|
3218
|
-
</tool_result>`
|
|
3219
|
-
).join("\n\n");
|
|
3220
|
-
const plural = results.length > 1 ? "s" : "";
|
|
3221
|
-
const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
|
|
3222
|
-
<background_results>
|
|
3223
|
-
${xml}
|
|
3224
|
-
</background_results>`;
|
|
3225
|
-
return automatedMessage("background_results", body);
|
|
3226
|
-
}
|
|
3227
|
-
function mergeBackgroundResultsMessages(messages) {
|
|
3228
|
-
const results = [];
|
|
3229
|
-
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
3230
|
-
for (const msg of messages) {
|
|
3231
|
-
for (const m of msg.matchAll(toolRe)) {
|
|
3232
|
-
results.push({ toolCallId: m[1], name: m[2], result: m[3] });
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
return buildBackgroundResultsMessage(results);
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
3238
|
// src/subagents/common/cleanMessages.ts
|
|
3239
3239
|
function findLastSummaryCheckpoint(messages, name) {
|
|
3240
3240
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3290,11 +3290,18 @@ function cleanMessagesForApi(messages) {
|
|
|
3290
3290
|
(b) => b.type === "summary" && b.name === "conversation"
|
|
3291
3291
|
);
|
|
3292
3292
|
if (summaryBlock && summaryBlock.type === "summary") {
|
|
3293
|
+
const recent = summaryBlock.recent ? `
|
|
3294
|
+
|
|
3295
|
+
<recent_messages>
|
|
3296
|
+
The last few turns of the summarized conversation, quoted exactly as they were said.
|
|
3297
|
+
|
|
3298
|
+
${summaryBlock.recent}
|
|
3299
|
+
</recent_messages>` : "";
|
|
3293
3300
|
prefix.push({
|
|
3294
3301
|
role: "user",
|
|
3295
3302
|
content: `<conversation_summary>
|
|
3296
3303
|
${summaryBlock.text}
|
|
3297
|
-
</conversation_summary
|
|
3304
|
+
</conversation_summary>${recent}`,
|
|
3298
3305
|
hidden: true
|
|
3299
3306
|
});
|
|
3300
3307
|
}
|
|
@@ -6094,6 +6101,7 @@ var SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
|
6094
6101
|
var SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
6095
6102
|
async function compactConversation(messages, apiConfig, model) {
|
|
6096
6103
|
const endIndex = findSafeInsertionPoint(messages);
|
|
6104
|
+
const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
|
|
6097
6105
|
const summaries = [];
|
|
6098
6106
|
const tasks = [];
|
|
6099
6107
|
const conversationMessages = getConversationMessagesForSummary(
|
|
@@ -6150,6 +6158,7 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
6150
6158
|
"Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
|
|
6151
6159
|
);
|
|
6152
6160
|
}
|
|
6161
|
+
const recent = collectRecentNarrative(conversationMessages);
|
|
6153
6162
|
const checkpointMessages = summaries.map((s) => ({
|
|
6154
6163
|
role: "user",
|
|
6155
6164
|
hidden: true,
|
|
@@ -6158,12 +6167,16 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
6158
6167
|
type: "summary",
|
|
6159
6168
|
name: s.name,
|
|
6160
6169
|
text: s.text,
|
|
6170
|
+
...s.name === "conversation" && recent ? { recent } : {},
|
|
6161
6171
|
startedAt: Date.now()
|
|
6162
6172
|
}
|
|
6163
6173
|
]
|
|
6164
6174
|
}));
|
|
6165
|
-
log8.info("Compaction complete", {
|
|
6166
|
-
|
|
6175
|
+
log8.info("Compaction complete", {
|
|
6176
|
+
summaries: summaries.length,
|
|
6177
|
+
recentNarrativeChars: recent.length
|
|
6178
|
+
});
|
|
6179
|
+
return { checkpoints: checkpointMessages, boundary };
|
|
6167
6180
|
}
|
|
6168
6181
|
function findSafeInsertionPoint(messages, fromIndex = messages.length) {
|
|
6169
6182
|
let idx = fromIndex;
|
|
@@ -6239,6 +6252,58 @@ function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
|
6239
6252
|
}
|
|
6240
6253
|
return collected;
|
|
6241
6254
|
}
|
|
6255
|
+
var RECENT_NARRATIVE_REPLIES = 3;
|
|
6256
|
+
var RECENT_NARRATIVE_MAX_CHARS = 2e4;
|
|
6257
|
+
function narrativeText(msg) {
|
|
6258
|
+
if (msg.role === "assistant") {
|
|
6259
|
+
if (!Array.isArray(msg.content)) {
|
|
6260
|
+
return null;
|
|
6261
|
+
}
|
|
6262
|
+
const blocks = msg.content;
|
|
6263
|
+
if (blocks.some((b) => b.type === "tool")) {
|
|
6264
|
+
return null;
|
|
6265
|
+
}
|
|
6266
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
6267
|
+
return text || null;
|
|
6268
|
+
}
|
|
6269
|
+
if (msg.role !== "user" || msg.toolCallId || msg.hidden) {
|
|
6270
|
+
return null;
|
|
6271
|
+
}
|
|
6272
|
+
if (typeof msg.content !== "string" || isAutomatedMessage(msg.content)) {
|
|
6273
|
+
return null;
|
|
6274
|
+
}
|
|
6275
|
+
const body = msg.content.trim();
|
|
6276
|
+
if (body) {
|
|
6277
|
+
return body;
|
|
6278
|
+
}
|
|
6279
|
+
return msg.attachments?.find((a) => a.transcript)?.transcript?.trim() || null;
|
|
6280
|
+
}
|
|
6281
|
+
function collectRecentNarrative(messages) {
|
|
6282
|
+
const quoted = [];
|
|
6283
|
+
let replies = 0;
|
|
6284
|
+
let chars = 0;
|
|
6285
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
6286
|
+
const msg = messages[i];
|
|
6287
|
+
const text = narrativeText(msg);
|
|
6288
|
+
if (!text) {
|
|
6289
|
+
continue;
|
|
6290
|
+
}
|
|
6291
|
+
if (quoted.length > 0) {
|
|
6292
|
+
if (replies >= RECENT_NARRATIVE_REPLIES) {
|
|
6293
|
+
break;
|
|
6294
|
+
}
|
|
6295
|
+
if (chars + text.length > RECENT_NARRATIVE_MAX_CHARS) {
|
|
6296
|
+
break;
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
quoted.push(`[${msg.role}]: ${text}`);
|
|
6300
|
+
chars += text.length;
|
|
6301
|
+
if (msg.role === "assistant") {
|
|
6302
|
+
replies++;
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
return quoted.reverse().join("\n\n");
|
|
6306
|
+
}
|
|
6242
6307
|
function serializeForSummary(messages) {
|
|
6243
6308
|
const toolNameById = /* @__PURE__ */ new Map();
|
|
6244
6309
|
for (const msg of messages) {
|
|
@@ -6763,15 +6828,26 @@ function clearSession(state) {
|
|
|
6763
6828
|
|
|
6764
6829
|
// src/compaction/trigger.ts
|
|
6765
6830
|
var log10 = createLogger("compaction:trigger");
|
|
6766
|
-
var
|
|
6831
|
+
var pending = null;
|
|
6767
6832
|
var inflightCompaction = null;
|
|
6768
6833
|
function applyPendingSummaries(state) {
|
|
6769
|
-
const
|
|
6770
|
-
|
|
6834
|
+
const drained = pending;
|
|
6835
|
+
pending = null;
|
|
6836
|
+
if (!drained || drained.checkpoints.length === 0) {
|
|
6771
6837
|
return;
|
|
6772
6838
|
}
|
|
6773
|
-
|
|
6774
|
-
|
|
6839
|
+
let idx;
|
|
6840
|
+
if (!drained.boundary) {
|
|
6841
|
+
idx = 0;
|
|
6842
|
+
} else {
|
|
6843
|
+
const at = state.messages.indexOf(drained.boundary);
|
|
6844
|
+
idx = at === -1 ? 0 : at + 1;
|
|
6845
|
+
}
|
|
6846
|
+
state.messages.splice(idx, 0, ...drained.checkpoints);
|
|
6847
|
+
log10.info("Checkpoint applied", {
|
|
6848
|
+
index: idx,
|
|
6849
|
+
messageCount: state.messages.length
|
|
6850
|
+
});
|
|
6775
6851
|
saveSession(state);
|
|
6776
6852
|
}
|
|
6777
6853
|
var listener = null;
|
|
@@ -6782,14 +6858,18 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
6782
6858
|
if (inflightCompaction) {
|
|
6783
6859
|
return inflightCompaction;
|
|
6784
6860
|
}
|
|
6861
|
+
if (pending) {
|
|
6862
|
+
log10.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
6863
|
+
return Promise.resolve();
|
|
6864
|
+
}
|
|
6785
6865
|
const { blocking = false, requestId, model } = opts;
|
|
6786
6866
|
listener?.({ type: "started", blocking, requestId });
|
|
6787
6867
|
inflightCompaction = compactConversation(
|
|
6788
6868
|
state.messages,
|
|
6789
6869
|
apiConfig,
|
|
6790
6870
|
resolveModel("conversationSummarizer", state.models, model)
|
|
6791
|
-
).then((
|
|
6792
|
-
|
|
6871
|
+
).then((result) => {
|
|
6872
|
+
pending = result;
|
|
6793
6873
|
listener?.({ type: "complete", requestId });
|
|
6794
6874
|
log10.info("Compaction complete");
|
|
6795
6875
|
}).catch((err) => {
|
|
@@ -8963,9 +9043,9 @@ var HeadlessSession = class {
|
|
|
8963
9043
|
if (this.currentAbort) {
|
|
8964
9044
|
this.currentAbort.abort();
|
|
8965
9045
|
}
|
|
8966
|
-
for (const [id,
|
|
8967
|
-
clearTimeout(
|
|
8968
|
-
|
|
9046
|
+
for (const [id, pending2] of this.pendingTools) {
|
|
9047
|
+
clearTimeout(pending2.timeout);
|
|
9048
|
+
pending2.resolve(USER_CANCELLED_RESULT);
|
|
8969
9049
|
this.pendingTools.delete(id);
|
|
8970
9050
|
}
|
|
8971
9051
|
return this.queue.removeWhere((item) => item.source !== "user");
|
|
@@ -9002,10 +9082,10 @@ var HeadlessSession = class {
|
|
|
9002
9082
|
if (action === "tool_result" && parsed.id) {
|
|
9003
9083
|
const id = parsed.id;
|
|
9004
9084
|
const result = parsed.result ?? "";
|
|
9005
|
-
const
|
|
9006
|
-
if (
|
|
9085
|
+
const pending2 = this.pendingTools.get(id);
|
|
9086
|
+
if (pending2) {
|
|
9007
9087
|
this.pendingTools.delete(id);
|
|
9008
|
-
|
|
9088
|
+
pending2.resolve(result);
|
|
9009
9089
|
} else if (!this.running) {
|
|
9010
9090
|
log15.info("Late tool_result while idle, dismissing", { id });
|
|
9011
9091
|
this.emit("completed", { success: true }, requestId);
|
package/dist/index.js
CHANGED
|
@@ -1599,9 +1599,63 @@ var init_assets = __esm({
|
|
|
1599
1599
|
}
|
|
1600
1600
|
});
|
|
1601
1601
|
|
|
1602
|
+
// src/automatedActions/sentinel.ts
|
|
1603
|
+
function sentinel(name) {
|
|
1604
|
+
return `@@automated::${name}@@`;
|
|
1605
|
+
}
|
|
1606
|
+
function automatedMessage(name, body) {
|
|
1607
|
+
return body ? `${sentinel(name)}
|
|
1608
|
+
${body}` : sentinel(name);
|
|
1609
|
+
}
|
|
1610
|
+
function hasSentinel(text, name) {
|
|
1611
|
+
return text.startsWith(sentinel(name));
|
|
1612
|
+
}
|
|
1613
|
+
function isAutomatedMessage(text) {
|
|
1614
|
+
return text.startsWith("@@automated::");
|
|
1615
|
+
}
|
|
1616
|
+
function parseSentinel(text) {
|
|
1617
|
+
const match = text.match(/^@@automated::(\w+)@@(.*)/s);
|
|
1618
|
+
if (!match) {
|
|
1619
|
+
return null;
|
|
1620
|
+
}
|
|
1621
|
+
return { name: match[1], remainder: match[2] };
|
|
1622
|
+
}
|
|
1623
|
+
function stripSentinelLine(text) {
|
|
1624
|
+
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
1625
|
+
}
|
|
1626
|
+
function buildBackgroundResultsMessage(results) {
|
|
1627
|
+
const xml = results.map(
|
|
1628
|
+
(r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
|
|
1629
|
+
${r.result}
|
|
1630
|
+
</tool_result>`
|
|
1631
|
+
).join("\n\n");
|
|
1632
|
+
const plural = results.length > 1 ? "s" : "";
|
|
1633
|
+
const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
|
|
1634
|
+
<background_results>
|
|
1635
|
+
${xml}
|
|
1636
|
+
</background_results>`;
|
|
1637
|
+
return automatedMessage("background_results", body);
|
|
1638
|
+
}
|
|
1639
|
+
function mergeBackgroundResultsMessages(messages) {
|
|
1640
|
+
const results = [];
|
|
1641
|
+
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
1642
|
+
for (const msg of messages) {
|
|
1643
|
+
for (const m of msg.matchAll(toolRe)) {
|
|
1644
|
+
results.push({ toolCallId: m[1], name: m[2], result: m[3] });
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return buildBackgroundResultsMessage(results);
|
|
1648
|
+
}
|
|
1649
|
+
var init_sentinel = __esm({
|
|
1650
|
+
"src/automatedActions/sentinel.ts"() {
|
|
1651
|
+
"use strict";
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1602
1655
|
// src/compaction/index.ts
|
|
1603
1656
|
async function compactConversation(messages, apiConfig, model) {
|
|
1604
1657
|
const endIndex = findSafeInsertionPoint(messages);
|
|
1658
|
+
const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
|
|
1605
1659
|
const summaries = [];
|
|
1606
1660
|
const tasks = [];
|
|
1607
1661
|
const conversationMessages = getConversationMessagesForSummary(
|
|
@@ -1658,6 +1712,7 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
1658
1712
|
"Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
|
|
1659
1713
|
);
|
|
1660
1714
|
}
|
|
1715
|
+
const recent = collectRecentNarrative(conversationMessages);
|
|
1661
1716
|
const checkpointMessages = summaries.map((s) => ({
|
|
1662
1717
|
role: "user",
|
|
1663
1718
|
hidden: true,
|
|
@@ -1666,12 +1721,16 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
1666
1721
|
type: "summary",
|
|
1667
1722
|
name: s.name,
|
|
1668
1723
|
text: s.text,
|
|
1724
|
+
...s.name === "conversation" && recent ? { recent } : {},
|
|
1669
1725
|
startedAt: Date.now()
|
|
1670
1726
|
}
|
|
1671
1727
|
]
|
|
1672
1728
|
}));
|
|
1673
|
-
log2.info("Compaction complete", {
|
|
1674
|
-
|
|
1729
|
+
log2.info("Compaction complete", {
|
|
1730
|
+
summaries: summaries.length,
|
|
1731
|
+
recentNarrativeChars: recent.length
|
|
1732
|
+
});
|
|
1733
|
+
return { checkpoints: checkpointMessages, boundary };
|
|
1675
1734
|
}
|
|
1676
1735
|
function findSafeInsertionPoint(messages, fromIndex = messages.length) {
|
|
1677
1736
|
let idx = fromIndex;
|
|
@@ -1747,6 +1806,56 @@ function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
|
1747
1806
|
}
|
|
1748
1807
|
return collected;
|
|
1749
1808
|
}
|
|
1809
|
+
function narrativeText(msg) {
|
|
1810
|
+
if (msg.role === "assistant") {
|
|
1811
|
+
if (!Array.isArray(msg.content)) {
|
|
1812
|
+
return null;
|
|
1813
|
+
}
|
|
1814
|
+
const blocks = msg.content;
|
|
1815
|
+
if (blocks.some((b) => b.type === "tool")) {
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
1819
|
+
return text || null;
|
|
1820
|
+
}
|
|
1821
|
+
if (msg.role !== "user" || msg.toolCallId || msg.hidden) {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
if (typeof msg.content !== "string" || isAutomatedMessage(msg.content)) {
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
const body = msg.content.trim();
|
|
1828
|
+
if (body) {
|
|
1829
|
+
return body;
|
|
1830
|
+
}
|
|
1831
|
+
return msg.attachments?.find((a) => a.transcript)?.transcript?.trim() || null;
|
|
1832
|
+
}
|
|
1833
|
+
function collectRecentNarrative(messages) {
|
|
1834
|
+
const quoted = [];
|
|
1835
|
+
let replies = 0;
|
|
1836
|
+
let chars = 0;
|
|
1837
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1838
|
+
const msg = messages[i];
|
|
1839
|
+
const text = narrativeText(msg);
|
|
1840
|
+
if (!text) {
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
if (quoted.length > 0) {
|
|
1844
|
+
if (replies >= RECENT_NARRATIVE_REPLIES) {
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
if (chars + text.length > RECENT_NARRATIVE_MAX_CHARS) {
|
|
1848
|
+
break;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
quoted.push(`[${msg.role}]: ${text}`);
|
|
1852
|
+
chars += text.length;
|
|
1853
|
+
if (msg.role === "assistant") {
|
|
1854
|
+
replies++;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return quoted.reverse().join("\n\n");
|
|
1858
|
+
}
|
|
1750
1859
|
function serializeForSummary(messages) {
|
|
1751
1860
|
const toolNameById = /* @__PURE__ */ new Map();
|
|
1752
1861
|
for (const msg of messages) {
|
|
@@ -1930,12 +2039,13 @@ Write the summary of the conversation above, following your instructions.`;
|
|
|
1930
2039
|
}
|
|
1931
2040
|
return summaryText.trim();
|
|
1932
2041
|
}
|
|
1933
|
-
var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, CHUNK_CHAR_LIMIT, MIN_SUMMARY_CHARS;
|
|
2042
|
+
var log2, CONVERSATION_SUMMARY_PROMPT, SUBAGENT_SUMMARY_PROMPT, SUMMARIZABLE_SUBAGENTS, RECENT_NARRATIVE_REPLIES, RECENT_NARRATIVE_MAX_CHARS, CHUNK_CHAR_LIMIT, MIN_SUMMARY_CHARS;
|
|
1934
2043
|
var init_compaction = __esm({
|
|
1935
2044
|
"src/compaction/index.ts"() {
|
|
1936
2045
|
"use strict";
|
|
1937
2046
|
init_api();
|
|
1938
2047
|
init_assets();
|
|
2048
|
+
init_sentinel();
|
|
1939
2049
|
init_tools8();
|
|
1940
2050
|
init_logger();
|
|
1941
2051
|
init_usageLedger();
|
|
@@ -1943,6 +2053,8 @@ var init_compaction = __esm({
|
|
|
1943
2053
|
CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
|
|
1944
2054
|
SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
|
|
1945
2055
|
SUMMARIZABLE_SUBAGENTS = ["visualDesignExpert", "productVision"];
|
|
2056
|
+
RECENT_NARRATIVE_REPLIES = 3;
|
|
2057
|
+
RECENT_NARRATIVE_MAX_CHARS = 2e4;
|
|
1946
2058
|
CHUNK_CHAR_LIMIT = 2e5;
|
|
1947
2059
|
MIN_SUMMARY_CHARS = 400;
|
|
1948
2060
|
}
|
|
@@ -2111,59 +2223,6 @@ var init_surfaces = __esm({
|
|
|
2111
2223
|
}
|
|
2112
2224
|
});
|
|
2113
2225
|
|
|
2114
|
-
// src/automatedActions/sentinel.ts
|
|
2115
|
-
function sentinel(name) {
|
|
2116
|
-
return `@@automated::${name}@@`;
|
|
2117
|
-
}
|
|
2118
|
-
function automatedMessage(name, body) {
|
|
2119
|
-
return body ? `${sentinel(name)}
|
|
2120
|
-
${body}` : sentinel(name);
|
|
2121
|
-
}
|
|
2122
|
-
function hasSentinel(text, name) {
|
|
2123
|
-
return text.startsWith(sentinel(name));
|
|
2124
|
-
}
|
|
2125
|
-
function isAutomatedMessage(text) {
|
|
2126
|
-
return text.startsWith("@@automated::");
|
|
2127
|
-
}
|
|
2128
|
-
function parseSentinel(text) {
|
|
2129
|
-
const match = text.match(/^@@automated::(\w+)@@(.*)/s);
|
|
2130
|
-
if (!match) {
|
|
2131
|
-
return null;
|
|
2132
|
-
}
|
|
2133
|
-
return { name: match[1], remainder: match[2] };
|
|
2134
|
-
}
|
|
2135
|
-
function stripSentinelLine(text) {
|
|
2136
|
-
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
2137
|
-
}
|
|
2138
|
-
function buildBackgroundResultsMessage(results) {
|
|
2139
|
-
const xml = results.map(
|
|
2140
|
-
(r) => `<tool_result id="${r.toolCallId}" name="${r.name}">
|
|
2141
|
-
${r.result}
|
|
2142
|
-
</tool_result>`
|
|
2143
|
-
).join("\n\n");
|
|
2144
|
-
const plural = results.length > 1 ? "s" : "";
|
|
2145
|
-
const body = `This is an automated message containing the result${plural} of ${results.length > 1 ? "tool calls" : "a tool call"} that ${results.length > 1 ? "have" : "has"} been working in the background. This is not a direct message from the user.
|
|
2146
|
-
<background_results>
|
|
2147
|
-
${xml}
|
|
2148
|
-
</background_results>`;
|
|
2149
|
-
return automatedMessage("background_results", body);
|
|
2150
|
-
}
|
|
2151
|
-
function mergeBackgroundResultsMessages(messages) {
|
|
2152
|
-
const results = [];
|
|
2153
|
-
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
2154
|
-
for (const msg of messages) {
|
|
2155
|
-
for (const m of msg.matchAll(toolRe)) {
|
|
2156
|
-
results.push({ toolCallId: m[1], name: m[2], result: m[3] });
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
return buildBackgroundResultsMessage(results);
|
|
2160
|
-
}
|
|
2161
|
-
var init_sentinel = __esm({
|
|
2162
|
-
"src/automatedActions/sentinel.ts"() {
|
|
2163
|
-
"use strict";
|
|
2164
|
-
}
|
|
2165
|
-
});
|
|
2166
|
-
|
|
2167
2226
|
// src/subagents/common/cleanMessages.ts
|
|
2168
2227
|
function findLastSummaryCheckpoint(messages, name) {
|
|
2169
2228
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -2219,11 +2278,18 @@ function cleanMessagesForApi(messages) {
|
|
|
2219
2278
|
(b) => b.type === "summary" && b.name === "conversation"
|
|
2220
2279
|
);
|
|
2221
2280
|
if (summaryBlock && summaryBlock.type === "summary") {
|
|
2281
|
+
const recent = summaryBlock.recent ? `
|
|
2282
|
+
|
|
2283
|
+
<recent_messages>
|
|
2284
|
+
The last few turns of the summarized conversation, quoted exactly as they were said.
|
|
2285
|
+
|
|
2286
|
+
${summaryBlock.recent}
|
|
2287
|
+
</recent_messages>` : "";
|
|
2222
2288
|
prefix.push({
|
|
2223
2289
|
role: "user",
|
|
2224
2290
|
content: `<conversation_summary>
|
|
2225
2291
|
${summaryBlock.text}
|
|
2226
|
-
</conversation_summary
|
|
2292
|
+
</conversation_summary>${recent}`,
|
|
2227
2293
|
hidden: true
|
|
2228
2294
|
});
|
|
2229
2295
|
}
|
|
@@ -2662,12 +2728,23 @@ var init_session = __esm({
|
|
|
2662
2728
|
|
|
2663
2729
|
// src/compaction/trigger.ts
|
|
2664
2730
|
function applyPendingSummaries(state) {
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2731
|
+
const drained = pending;
|
|
2732
|
+
pending = null;
|
|
2733
|
+
if (!drained || drained.checkpoints.length === 0) {
|
|
2667
2734
|
return;
|
|
2668
2735
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2736
|
+
let idx;
|
|
2737
|
+
if (!drained.boundary) {
|
|
2738
|
+
idx = 0;
|
|
2739
|
+
} else {
|
|
2740
|
+
const at = state.messages.indexOf(drained.boundary);
|
|
2741
|
+
idx = at === -1 ? 0 : at + 1;
|
|
2742
|
+
}
|
|
2743
|
+
state.messages.splice(idx, 0, ...drained.checkpoints);
|
|
2744
|
+
log4.info("Checkpoint applied", {
|
|
2745
|
+
index: idx,
|
|
2746
|
+
messageCount: state.messages.length
|
|
2747
|
+
});
|
|
2671
2748
|
saveSession(state);
|
|
2672
2749
|
}
|
|
2673
2750
|
function setCompactionListener(l) {
|
|
@@ -2677,14 +2754,18 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2677
2754
|
if (inflightCompaction) {
|
|
2678
2755
|
return inflightCompaction;
|
|
2679
2756
|
}
|
|
2757
|
+
if (pending) {
|
|
2758
|
+
log4.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
2759
|
+
return Promise.resolve();
|
|
2760
|
+
}
|
|
2680
2761
|
const { blocking = false, requestId, model } = opts;
|
|
2681
2762
|
listener?.({ type: "started", blocking, requestId });
|
|
2682
2763
|
inflightCompaction = compactConversation(
|
|
2683
2764
|
state.messages,
|
|
2684
2765
|
apiConfig,
|
|
2685
2766
|
resolveModel("conversationSummarizer", state.models, model)
|
|
2686
|
-
).then((
|
|
2687
|
-
|
|
2767
|
+
).then((result) => {
|
|
2768
|
+
pending = result;
|
|
2688
2769
|
listener?.({ type: "complete", requestId });
|
|
2689
2770
|
log4.info("Compaction complete");
|
|
2690
2771
|
}).catch((err) => {
|
|
@@ -2697,7 +2778,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2697
2778
|
});
|
|
2698
2779
|
return inflightCompaction;
|
|
2699
2780
|
}
|
|
2700
|
-
var log4,
|
|
2781
|
+
var log4, pending, inflightCompaction, listener;
|
|
2701
2782
|
var init_trigger = __esm({
|
|
2702
2783
|
"src/compaction/trigger.ts"() {
|
|
2703
2784
|
"use strict";
|
|
@@ -2706,7 +2787,7 @@ var init_trigger = __esm({
|
|
|
2706
2787
|
init_surfaces();
|
|
2707
2788
|
init_session();
|
|
2708
2789
|
log4 = createLogger("compaction:trigger");
|
|
2709
|
-
|
|
2790
|
+
pending = null;
|
|
2710
2791
|
inflightCompaction = null;
|
|
2711
2792
|
listener = null;
|
|
2712
2793
|
}
|
|
@@ -9861,9 +9942,9 @@ var init_headless = __esm({
|
|
|
9861
9942
|
if (this.currentAbort) {
|
|
9862
9943
|
this.currentAbort.abort();
|
|
9863
9944
|
}
|
|
9864
|
-
for (const [id,
|
|
9865
|
-
clearTimeout(
|
|
9866
|
-
|
|
9945
|
+
for (const [id, pending2] of this.pendingTools) {
|
|
9946
|
+
clearTimeout(pending2.timeout);
|
|
9947
|
+
pending2.resolve(USER_CANCELLED_RESULT);
|
|
9867
9948
|
this.pendingTools.delete(id);
|
|
9868
9949
|
}
|
|
9869
9950
|
return this.queue.removeWhere((item) => item.source !== "user");
|
|
@@ -9900,10 +9981,10 @@ var init_headless = __esm({
|
|
|
9900
9981
|
if (action === "tool_result" && parsed.id) {
|
|
9901
9982
|
const id = parsed.id;
|
|
9902
9983
|
const result = parsed.result ?? "";
|
|
9903
|
-
const
|
|
9904
|
-
if (
|
|
9984
|
+
const pending2 = this.pendingTools.get(id);
|
|
9985
|
+
if (pending2) {
|
|
9905
9986
|
this.pendingTools.delete(id);
|
|
9906
|
-
|
|
9987
|
+
pending2.resolve(result);
|
|
9907
9988
|
} else if (!this.running) {
|
|
9908
9989
|
log15.info("Late tool_result while idle, dismissing", { id });
|
|
9909
9990
|
this.emit("completed", { success: true }, requestId);
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
# Files & Storage
|
|
2
2
|
|
|
3
|
-
Per-app blob storage
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
Per-app blob storage: user uploads, generated documents, images, marketing assets. **Think of a store
|
|
4
|
+
as a CDN-backed bucket the app talks to — not app-defined state like the database.** You declare the
|
|
5
|
+
store; what lands in it is arbitrary durable blobs the app doesn't model — no schema, nothing to
|
|
6
|
+
migrate, no dev/prod sync. **Private by default**, served on the app's own domain. (The API is
|
|
7
|
+
*shaped* like `db` — `defineStore` at module scope, import the handle, like `defineTable` — but the
|
|
8
|
+
mental model is a bucket, not rows.)
|
|
9
|
+
|
|
10
|
+
## How a store behaves (read before the API)
|
|
11
|
+
|
|
12
|
+
- **One store, shared across dev and prod — on purpose.** There's no dev copy: a file you upload in
|
|
13
|
+
the dev editor (a marketing image, a corpus to vectorize) is *already there in prod* at the same
|
|
14
|
+
stable URL. That continuity is a feature — don't fork buckets per environment.
|
|
15
|
+
- **Creates are safe by default**, because keys default to unique — `put()` mints a UUID (or a
|
|
16
|
+
content-addressed hash) when you don't pass one, so a dev write and a prod write land at different
|
|
17
|
+
keys and coexist. A collision only happens when you *choose* a fixed key.
|
|
18
|
+
- **Care goes on the destructive / fixed-key operations**, not on writing in general: `delete(key)`
|
|
19
|
+
and overwriting a **stable key** (e.g. `config/latest.json`) reach the one live store, so a dev run
|
|
20
|
+
can clobber what prod serves. A `put()` with a default key can't. (There's intentionally no bulk
|
|
21
|
+
"clear the store".)
|
|
22
|
+
- **Scenarios don't touch files — and that's correct.** A scenario truncates DB tables to seed test
|
|
23
|
+
*rows*; files are durable and left alone. A store isn't a "clean slate" you re-seed each run — upload
|
|
24
|
+
a test file once in dev and it stays. Accumulation is normal for an asset store; don't write
|
|
25
|
+
`clear()`-style reset helpers.
|
|
26
|
+
- **Need dev and prod to *not* share** something (mutable fixed-key state, or sensitive uploads a
|
|
27
|
+
developer shouldn't see)? There's no per-store isolation switch — scope the key yourself (e.g.
|
|
28
|
+
`config/${env}/…`). Rare; the shared default is right almost always.
|
|
11
29
|
|
|
12
30
|
## Defining a store
|
|
13
31
|
|