@mindstudio-ai/remy 0.1.293 → 0.1.295
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/automatedActions/reviseFromAnnotatedImage.md +3 -1
- package/dist/headless.d.ts +29 -12
- package/dist/headless.js +232 -75
- package/dist/index.js +233 -76
- package/dist/prompt/compiled/sdk-actions.md +1 -1
- package/dist/prompt/skills/taskAgents.md +30 -3
- package/dist/prompt/static/coding.md +1 -1
- package/dist/prompt/static/instructions.md +2 -0
- package/dist/subagents/codeSanityCheck/prompt.md +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,12 +195,17 @@ async function* streamChat(params) {
|
|
|
195
195
|
}
|
|
196
196
|
const isStall = err?.message === "stream_stall";
|
|
197
197
|
const errorMessage = isStall ? "Stream stalled \u2014 no data received for 5 minutes" : `Network error: stream interrupted \u2014 ${err?.message ?? "unknown"}`;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
const wasAborted = !isStall && !!signal?.aborted;
|
|
199
|
+
const logAt = wasAborted ? log.warn : log.error;
|
|
200
|
+
logAt(
|
|
201
|
+
wasAborted ? "Request aborted mid-stream" : isStall ? "Stream stalled" : "Stream interrupted",
|
|
202
|
+
{
|
|
203
|
+
requestId,
|
|
204
|
+
...subAgentId && { subAgentId },
|
|
205
|
+
durationMs: Date.now() - startTime,
|
|
206
|
+
error: errorMessage
|
|
207
|
+
}
|
|
208
|
+
);
|
|
204
209
|
yield { type: "error", error: errorMessage };
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
@@ -867,7 +872,7 @@ var init_presentPublishPlan = __esm({
|
|
|
867
872
|
presentPublishPlanTool = {
|
|
868
873
|
definition: {
|
|
869
874
|
name: "presentPublishPlan",
|
|
870
|
-
description: "Present a publish changelog to the user for approval \u2014 the consent gate of the release flow, used when the user has asked to publish (the Publish button or an explicit chat request; the `publishing` skill covers the full sequence). Write a clear markdown summary of what changed since the last deploy. The user
|
|
875
|
+
description: "Present a publish changelog to the user for approval \u2014 the consent gate of the release flow, used when the user has asked to publish (the Publish button or an explicit chat request; the `publishing` skill covers the full sequence). Write a clear markdown summary of what changed since the last deploy. The user reviews this in the editor area and can approve or dismiss. Call this BEFORE committing or pushing.",
|
|
871
876
|
inputSchema: {
|
|
872
877
|
type: "object",
|
|
873
878
|
properties: {
|
|
@@ -1034,7 +1039,7 @@ var init_promptUser = __esm({
|
|
|
1034
1039
|
promptUserTool = {
|
|
1035
1040
|
definition: {
|
|
1036
1041
|
name: "promptUser",
|
|
1037
|
-
description: 'Ask the user structured questions. Choose type first: "form" for structured intake (5+ questions,
|
|
1042
|
+
description: 'Ask the user structured questions. Choose type first: "form" for structured intake (5+ questions, opens as a full form in the editor area), "inline" for quick clarifications or confirmations. Blocks until the user responds. Result contains `_dismissed: true` if the user dismisses without answering.',
|
|
1038
1043
|
inputSchema: {
|
|
1039
1044
|
type: "object",
|
|
1040
1045
|
properties: {
|
|
@@ -1677,7 +1682,7 @@ var init_sentinel = __esm({
|
|
|
1677
1682
|
});
|
|
1678
1683
|
|
|
1679
1684
|
// src/compaction/index.ts
|
|
1680
|
-
async function compactConversation(messages, apiConfig, model) {
|
|
1685
|
+
async function compactConversation(messages, apiConfig, model, signal) {
|
|
1681
1686
|
const endIndex = findSafeInsertionPoint(messages);
|
|
1682
1687
|
const boundary = endIndex > 0 ? messages[endIndex - 1] : null;
|
|
1683
1688
|
const summaries = [];
|
|
@@ -1694,7 +1699,8 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
1694
1699
|
"conversation",
|
|
1695
1700
|
CONVERSATION_SUMMARY_PROMPT,
|
|
1696
1701
|
conversationMessages,
|
|
1697
|
-
model
|
|
1702
|
+
model,
|
|
1703
|
+
{ signal }
|
|
1698
1704
|
).then((text) => {
|
|
1699
1705
|
if (text) {
|
|
1700
1706
|
summaries.push({ name: "conversation", text });
|
|
@@ -1717,7 +1723,8 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
1717
1723
|
name,
|
|
1718
1724
|
SUBAGENT_SUMMARY_PROMPT,
|
|
1719
1725
|
subagentMessages,
|
|
1720
|
-
model
|
|
1726
|
+
model,
|
|
1727
|
+
{ signal }
|
|
1721
1728
|
).then((text) => {
|
|
1722
1729
|
if (text) {
|
|
1723
1730
|
summaries.push({ name, text });
|
|
@@ -1733,7 +1740,7 @@ async function compactConversation(messages, apiConfig, model) {
|
|
|
1733
1740
|
await Promise.all(tasks);
|
|
1734
1741
|
if (conversationFailed) {
|
|
1735
1742
|
throw new Error(
|
|
1736
|
-
"Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
|
|
1743
|
+
signal?.aborted ? "Compaction cancelled. History left intact." : "Could not summarize the conversation \u2014 the model did not return a usable summary. History left intact."
|
|
1737
1744
|
);
|
|
1738
1745
|
}
|
|
1739
1746
|
const recent = collectRecentNarrative(conversationMessages);
|
|
@@ -1969,7 +1976,10 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
1969
1976
|
// A split driven by size gets its children a retry of their own. A
|
|
1970
1977
|
// split that IS the retry does not, or a model that will not
|
|
1971
1978
|
// summarize at any size fans out call after call before giving up.
|
|
1972
|
-
{
|
|
1979
|
+
{
|
|
1980
|
+
allowRetry: opts.forceChunk ? false : allowRetry,
|
|
1981
|
+
...opts.signal && { signal: opts.signal }
|
|
1982
|
+
}
|
|
1973
1983
|
)
|
|
1974
1984
|
)
|
|
1975
1985
|
);
|
|
@@ -1992,7 +2002,8 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
1992
2002
|
name,
|
|
1993
2003
|
compactionPrompt,
|
|
1994
2004
|
serialized,
|
|
1995
|
-
model
|
|
2005
|
+
model,
|
|
2006
|
+
opts.signal
|
|
1996
2007
|
);
|
|
1997
2008
|
if (summaryText === null) {
|
|
1998
2009
|
return null;
|
|
@@ -2020,7 +2031,7 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
|
|
|
2020
2031
|
{ forceChunk: true, allowRetry: false }
|
|
2021
2032
|
);
|
|
2022
2033
|
}
|
|
2023
|
-
async function runSummaryCall(apiConfig, name, compactionPrompt, serialized, model) {
|
|
2034
|
+
async function runSummaryCall(apiConfig, name, compactionPrompt, serialized, model, signal) {
|
|
2024
2035
|
const userContent = `Conversation to summarize:
|
|
2025
2036
|
|
|
2026
2037
|
${serialized}
|
|
@@ -2042,7 +2053,8 @@ Write the summary of the conversation above, following your instructions.`;
|
|
|
2042
2053
|
// Each summary call carries a unique 100-200KB chunk that is never
|
|
2043
2054
|
// re-read (parallel siblings can't read each other's in-flight writes
|
|
2044
2055
|
// either) — a cache write here is pure waste.
|
|
2045
|
-
cachePolicy: "oneshot"
|
|
2056
|
+
cachePolicy: "oneshot",
|
|
2057
|
+
...signal && { signal }
|
|
2046
2058
|
})) {
|
|
2047
2059
|
if (event.type === "text") {
|
|
2048
2060
|
summaryText += event.text;
|
|
@@ -2066,7 +2078,11 @@ Write the summary of the conversation above, following your instructions.`;
|
|
|
2066
2078
|
}
|
|
2067
2079
|
}
|
|
2068
2080
|
if (!summaryText.trim()) {
|
|
2069
|
-
|
|
2081
|
+
if (signal?.aborted) {
|
|
2082
|
+
log2.info("Summary cancelled", { name });
|
|
2083
|
+
} else {
|
|
2084
|
+
log2.warn("Empty summary generated", { name });
|
|
2085
|
+
}
|
|
2070
2086
|
return null;
|
|
2071
2087
|
}
|
|
2072
2088
|
return summaryText.trim();
|
|
@@ -2861,6 +2877,14 @@ var init_session = __esm({
|
|
|
2861
2877
|
function getInflightCompaction() {
|
|
2862
2878
|
return inflightCompaction;
|
|
2863
2879
|
}
|
|
2880
|
+
function cancelInflightCompaction() {
|
|
2881
|
+
if (!inflightCompaction || !inflightAbort) {
|
|
2882
|
+
return false;
|
|
2883
|
+
}
|
|
2884
|
+
log4.info("Cancelling in-flight compaction");
|
|
2885
|
+
inflightAbort.abort();
|
|
2886
|
+
return true;
|
|
2887
|
+
}
|
|
2864
2888
|
function summariesOf(result) {
|
|
2865
2889
|
const out = [];
|
|
2866
2890
|
for (const msg of result.checkpoints) {
|
|
@@ -2927,10 +2951,13 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2927
2951
|
}
|
|
2928
2952
|
const { blocking = false, requestId, model, origin, toolCallId } = opts;
|
|
2929
2953
|
listener?.({ type: "started", blocking, requestId, origin, toolCallId });
|
|
2954
|
+
const abort = new AbortController();
|
|
2955
|
+
inflightAbort = abort;
|
|
2930
2956
|
inflightCompaction = compactConversation(
|
|
2931
2957
|
state.messages,
|
|
2932
2958
|
apiConfig,
|
|
2933
|
-
resolveModel("conversationSummarizer", state.models, model)
|
|
2959
|
+
resolveModel("conversationSummarizer", state.models, model),
|
|
2960
|
+
abort.signal
|
|
2934
2961
|
).then((result) => {
|
|
2935
2962
|
pending = result;
|
|
2936
2963
|
const summaries = summariesOf(result);
|
|
@@ -2942,16 +2969,27 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2942
2969
|
log4.info("Compaction complete");
|
|
2943
2970
|
return summaries;
|
|
2944
2971
|
}).catch((err) => {
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2972
|
+
const cancelled = abort.signal.aborted;
|
|
2973
|
+
const message = cancelled ? "Compaction cancelled" : err.message || "Compaction failed";
|
|
2974
|
+
listener?.({
|
|
2975
|
+
type: "complete",
|
|
2976
|
+
error: message,
|
|
2977
|
+
...cancelled && { cancelled: true },
|
|
2978
|
+
requestId
|
|
2979
|
+
});
|
|
2980
|
+
if (cancelled) {
|
|
2981
|
+
log4.info("Compaction cancelled");
|
|
2982
|
+
} else {
|
|
2983
|
+
log4.error("Compaction failed", { error: message });
|
|
2984
|
+
}
|
|
2985
|
+
throw cancelled ? new CompactionCancelledError() : err;
|
|
2949
2986
|
}).finally(() => {
|
|
2950
2987
|
inflightCompaction = null;
|
|
2988
|
+
inflightAbort = null;
|
|
2951
2989
|
});
|
|
2952
2990
|
return inflightCompaction;
|
|
2953
2991
|
}
|
|
2954
|
-
var log4, pending, inflightCompaction, SUMMARY_SECTION_LABELS, listener;
|
|
2992
|
+
var log4, CompactionCancelledError, pending, inflightCompaction, inflightAbort, SUMMARY_SECTION_LABELS, listener;
|
|
2955
2993
|
var init_trigger = __esm({
|
|
2956
2994
|
"src/compaction/trigger.ts"() {
|
|
2957
2995
|
"use strict";
|
|
@@ -2960,8 +2998,15 @@ var init_trigger = __esm({
|
|
|
2960
2998
|
init_surfaces();
|
|
2961
2999
|
init_session();
|
|
2962
3000
|
log4 = createLogger("compaction:trigger");
|
|
3001
|
+
CompactionCancelledError = class extends Error {
|
|
3002
|
+
constructor() {
|
|
3003
|
+
super("Compaction cancelled \u2014 no checkpoint was created.");
|
|
3004
|
+
this.name = "CompactionCancelledError";
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
2963
3007
|
pending = null;
|
|
2964
3008
|
inflightCompaction = null;
|
|
3009
|
+
inflightAbort = null;
|
|
2965
3010
|
SUMMARY_SECTION_LABELS = {
|
|
2966
3011
|
conversation: "Conversation",
|
|
2967
3012
|
visualDesignExpert: "Design Agent Thread",
|
|
@@ -3018,7 +3063,9 @@ var init_compactConversation = __esm({
|
|
|
3018
3063
|
onBackgroundComplete?.(
|
|
3019
3064
|
toolCallId,
|
|
3020
3065
|
"compactConversation",
|
|
3021
|
-
|
|
3066
|
+
// A cancel is the user's own doing — report it as an outcome, not as
|
|
3067
|
+
// an error the block renders as a failure.
|
|
3068
|
+
err instanceof CompactionCancelledError ? err.message : `Error: ${err.message || "Compaction failed"}`
|
|
3022
3069
|
);
|
|
3023
3070
|
}).finally(() => {
|
|
3024
3071
|
toolRegistry?.unregister(toolCallId);
|
|
@@ -10147,6 +10194,11 @@ var init_stats = __esm({
|
|
|
10147
10194
|
});
|
|
10148
10195
|
|
|
10149
10196
|
// src/headless/messageQueue.ts
|
|
10197
|
+
function holdRestoredUserItems(items) {
|
|
10198
|
+
return items.map(
|
|
10199
|
+
(item) => item.source === "user" ? { ...item, held: true } : item
|
|
10200
|
+
);
|
|
10201
|
+
}
|
|
10150
10202
|
var MessageQueue;
|
|
10151
10203
|
var init_messageQueue = __esm({
|
|
10152
10204
|
"src/headless/messageQueue.ts"() {
|
|
@@ -10162,33 +10214,36 @@ var init_messageQueue = __esm({
|
|
|
10162
10214
|
this.items.push(item);
|
|
10163
10215
|
this.onChange?.();
|
|
10164
10216
|
}
|
|
10165
|
-
|
|
10166
|
-
|
|
10217
|
+
/**
|
|
10218
|
+
* Index of the first deliverable item, or -1 when there is none.
|
|
10219
|
+
*
|
|
10220
|
+
* Held items are out of the delivery sequence, so the drain skips past them
|
|
10221
|
+
* rather than stopping at them: a message the user parked sits at the head
|
|
10222
|
+
* of the array, and stopping there would strand Remy's own chain steps and
|
|
10223
|
+
* background results queued behind it.
|
|
10224
|
+
*/
|
|
10225
|
+
firstDeliverableIndex() {
|
|
10226
|
+
return this.items.findIndex((item) => !item.held);
|
|
10227
|
+
}
|
|
10228
|
+
/** Remove and return the item at `i`; fires onChange. */
|
|
10229
|
+
takeAt(i) {
|
|
10230
|
+
const [item] = this.items.splice(i, 1);
|
|
10167
10231
|
if (item) {
|
|
10168
10232
|
this.onChange?.();
|
|
10169
10233
|
}
|
|
10170
10234
|
return item;
|
|
10171
10235
|
}
|
|
10172
|
-
/** Remove and return
|
|
10173
|
-
|
|
10174
|
-
if (
|
|
10236
|
+
/** Remove and return `count` items starting at `start`; fires onChange once. */
|
|
10237
|
+
takeRange(start, count) {
|
|
10238
|
+
if (count <= 0) {
|
|
10175
10239
|
return [];
|
|
10176
10240
|
}
|
|
10177
|
-
const items = this.items.splice(
|
|
10241
|
+
const items = this.items.splice(start, count);
|
|
10178
10242
|
if (items.length > 0) {
|
|
10179
10243
|
this.onChange?.();
|
|
10180
10244
|
}
|
|
10181
10245
|
return items;
|
|
10182
10246
|
}
|
|
10183
|
-
/** Remove and return all queued items. */
|
|
10184
|
-
drain() {
|
|
10185
|
-
if (this.items.length === 0) {
|
|
10186
|
-
return [];
|
|
10187
|
-
}
|
|
10188
|
-
const all = this.items.splice(0);
|
|
10189
|
-
this.onChange?.();
|
|
10190
|
-
return all;
|
|
10191
|
-
}
|
|
10192
10247
|
/**
|
|
10193
10248
|
* Remove all items matching `predicate`. Fires onChange only if something
|
|
10194
10249
|
* was removed. Returns the removed items.
|
|
@@ -10207,6 +10262,49 @@ var init_messageQueue = __esm({
|
|
|
10207
10262
|
}
|
|
10208
10263
|
return removed;
|
|
10209
10264
|
}
|
|
10265
|
+
/**
|
|
10266
|
+
* Mark matching items `held` — waiting on the user rather than on the agent.
|
|
10267
|
+
* Fires onChange only if something changed. Returns the held items.
|
|
10268
|
+
*/
|
|
10269
|
+
holdWhere(predicate) {
|
|
10270
|
+
const held = [];
|
|
10271
|
+
let changed = false;
|
|
10272
|
+
for (const item of this.items) {
|
|
10273
|
+
if (!predicate(item)) {
|
|
10274
|
+
continue;
|
|
10275
|
+
}
|
|
10276
|
+
changed = changed || !item.held;
|
|
10277
|
+
item.held = true;
|
|
10278
|
+
held.push(item);
|
|
10279
|
+
}
|
|
10280
|
+
if (changed) {
|
|
10281
|
+
this.onChange?.();
|
|
10282
|
+
}
|
|
10283
|
+
return held;
|
|
10284
|
+
}
|
|
10285
|
+
/**
|
|
10286
|
+
* Release held items so the normal drain picks them up again — all of them,
|
|
10287
|
+
* or one by command requestId. Fires onChange only if something changed.
|
|
10288
|
+
* Returns the released items.
|
|
10289
|
+
*/
|
|
10290
|
+
releaseHeld(id) {
|
|
10291
|
+
const released = [];
|
|
10292
|
+
for (const item of this.items) {
|
|
10293
|
+
if (!item.held || id !== void 0 && item.command.requestId !== id) {
|
|
10294
|
+
continue;
|
|
10295
|
+
}
|
|
10296
|
+
delete item.held;
|
|
10297
|
+
released.push(item);
|
|
10298
|
+
}
|
|
10299
|
+
if (released.length > 0) {
|
|
10300
|
+
this.onChange?.();
|
|
10301
|
+
}
|
|
10302
|
+
return released;
|
|
10303
|
+
}
|
|
10304
|
+
/** Whether anything in the queue will drain on its own (i.e. isn't held). */
|
|
10305
|
+
hasDeliverable() {
|
|
10306
|
+
return this.items.some((item) => !item.held);
|
|
10307
|
+
}
|
|
10210
10308
|
/**
|
|
10211
10309
|
* Change a queued item's delivery semantics, keyed by its command
|
|
10212
10310
|
* requestId. Fires onChange (→ persist + queue_changed) on success.
|
|
@@ -10222,14 +10320,35 @@ var init_messageQueue = __esm({
|
|
|
10222
10320
|
this.onChange?.();
|
|
10223
10321
|
return item;
|
|
10224
10322
|
}
|
|
10323
|
+
/**
|
|
10324
|
+
* Promote an item to ASAP: release any hold, and move it to the head so it
|
|
10325
|
+
* is the next thing delivered. One onChange for all of it.
|
|
10326
|
+
*
|
|
10327
|
+
* Position matters only when no turn is running — mid-turn, ASAP items are
|
|
10328
|
+
* pulled by predicate at the next tool boundary regardless of where they sit
|
|
10329
|
+
* (`takeSteering`), jumping whatever is queued ahead of them. Moving to the
|
|
10330
|
+
* head makes the idle case behave the same way, and matches the card, which
|
|
10331
|
+
* already renders promoted items above everything else.
|
|
10332
|
+
*
|
|
10333
|
+
* Returns the item, or undefined if nothing matches (e.g. it was already
|
|
10334
|
+
* consumed by the running turn).
|
|
10335
|
+
*/
|
|
10336
|
+
promoteToFront(id) {
|
|
10337
|
+
const idx = this.items.findIndex((it) => it.command.requestId === id);
|
|
10338
|
+
if (idx === -1) {
|
|
10339
|
+
return void 0;
|
|
10340
|
+
}
|
|
10341
|
+
const [item] = this.items.splice(idx, 1);
|
|
10342
|
+
item.delivery = "asap";
|
|
10343
|
+
delete item.held;
|
|
10344
|
+
this.items.unshift(item);
|
|
10345
|
+
this.onChange?.();
|
|
10346
|
+
return item;
|
|
10347
|
+
}
|
|
10225
10348
|
/** Copy of current queue contents (for surfacing on events). */
|
|
10226
10349
|
snapshot() {
|
|
10227
10350
|
return [...this.items];
|
|
10228
10351
|
}
|
|
10229
|
-
/** Return the next item without removing it. */
|
|
10230
|
-
peek() {
|
|
10231
|
-
return this.items[0];
|
|
10232
|
-
}
|
|
10233
10352
|
/** Return the item at index `i` without removing it. */
|
|
10234
10353
|
peekAt(i) {
|
|
10235
10354
|
return this.items[i];
|
|
@@ -10357,7 +10476,7 @@ var init_headless = __esm({
|
|
|
10357
10476
|
});
|
|
10358
10477
|
await initOrgContext(this.config);
|
|
10359
10478
|
const resumed = loadSession(this.state);
|
|
10360
|
-
this.queue = new MessageQueue(loadQueue(), () => {
|
|
10479
|
+
this.queue = new MessageQueue(holdRestoredUserItems(loadQueue()), () => {
|
|
10361
10480
|
this.persistStats();
|
|
10362
10481
|
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
10363
10482
|
});
|
|
@@ -10410,13 +10529,16 @@ var init_headless = __esm({
|
|
|
10410
10529
|
});
|
|
10411
10530
|
}
|
|
10412
10531
|
} else {
|
|
10413
|
-
const data =
|
|
10532
|
+
const data = {
|
|
10533
|
+
...event.error && { error: event.error },
|
|
10534
|
+
...event.cancelled && { cancelled: true }
|
|
10535
|
+
};
|
|
10414
10536
|
this.emit("compaction_complete", data, event.requestId);
|
|
10415
10537
|
if (this.syntheticCompactionId) {
|
|
10416
10538
|
const id = this.syntheticCompactionId;
|
|
10417
10539
|
this.syntheticCompactionId = null;
|
|
10418
|
-
const result = event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? []);
|
|
10419
|
-
const isError = !!event.error;
|
|
10540
|
+
const result = event.cancelled ? "Compaction cancelled \u2014 no checkpoint was created." : event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? []);
|
|
10541
|
+
const isError = !!event.error && !event.cancelled;
|
|
10420
10542
|
for (let i = this.state.messages.length - 1; i >= 0; i--) {
|
|
10421
10543
|
const msg = this.state.messages[i];
|
|
10422
10544
|
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
@@ -11097,7 +11219,11 @@ var init_headless = __esm({
|
|
|
11097
11219
|
this.applyPendingBlockUpdates();
|
|
11098
11220
|
}
|
|
11099
11221
|
async handleMessage(parsed, requestId) {
|
|
11100
|
-
|
|
11222
|
+
const foldIn = !this.running && !getInflightCompaction() && this.queue.length > 0 && !isAutomatedMessage(parsed.text ?? "");
|
|
11223
|
+
if (foldIn) {
|
|
11224
|
+
this.queue.releaseHeld();
|
|
11225
|
+
}
|
|
11226
|
+
if (this.running || getInflightCompaction() || foldIn) {
|
|
11101
11227
|
const command = { ...parsed };
|
|
11102
11228
|
if (requestId && command.requestId === void 0) {
|
|
11103
11229
|
command.requestId = requestId;
|
|
@@ -11134,8 +11260,9 @@ var init_headless = __esm({
|
|
|
11134
11260
|
return item.source === "user" && isAutomatedMessage(item.command.text ?? "");
|
|
11135
11261
|
}
|
|
11136
11262
|
/**
|
|
11137
|
-
* Drain the queue in
|
|
11138
|
-
* User messages arriving during the drain will be enqueued
|
|
11263
|
+
* Drain the queue in FIFO order over its deliverable items. Caller must hold
|
|
11264
|
+
* `running = true`. User messages arriving during the drain will be enqueued
|
|
11265
|
+
* behind current items.
|
|
11139
11266
|
*
|
|
11140
11267
|
* The queue serves two purposes with opposite delivery semantics:
|
|
11141
11268
|
* - Sequencer: chain steps and sentinel-bearing user items are pipeline
|
|
@@ -11145,12 +11272,21 @@ var init_headless = __esm({
|
|
|
11145
11272
|
* merged turn, so the model reconciles all of it at once instead of
|
|
11146
11273
|
* burning a full turn per item (and possibly executing instructions a
|
|
11147
11274
|
* later queued message already amended).
|
|
11275
|
+
*
|
|
11276
|
+
* Held items are not in the delivery sequence at all: the drain starts at the
|
|
11277
|
+
* first deliverable item and never merges across a held one. It skips rather
|
|
11278
|
+
* than stops because a held message sits at the head of the array — stopping
|
|
11279
|
+
* there would strand the chain steps and background results behind it.
|
|
11148
11280
|
*/
|
|
11149
11281
|
async drainQueueLoop() {
|
|
11150
|
-
|
|
11151
|
-
const
|
|
11282
|
+
for (; ; ) {
|
|
11283
|
+
const at = this.queue.firstDeliverableIndex();
|
|
11284
|
+
if (at === -1) {
|
|
11285
|
+
return;
|
|
11286
|
+
}
|
|
11287
|
+
const head = this.queue.peekAt(at);
|
|
11152
11288
|
if (head.command.action === "compact") {
|
|
11153
|
-
this.queue.
|
|
11289
|
+
this.queue.takeAt(at);
|
|
11154
11290
|
await triggerCompaction(this.state, this.config, {
|
|
11155
11291
|
blocking: true,
|
|
11156
11292
|
requestId: head.command.requestId,
|
|
@@ -11162,13 +11298,13 @@ var init_headless = __esm({
|
|
|
11162
11298
|
continue;
|
|
11163
11299
|
}
|
|
11164
11300
|
if (head.source === "chain") {
|
|
11165
|
-
const item = this.queue.
|
|
11301
|
+
const item = this.queue.takeAt(at);
|
|
11166
11302
|
const rid = item.command.requestId ?? `chain-${Date.now()}`;
|
|
11167
11303
|
await this.runSingleTurn(item.command, rid, true);
|
|
11168
11304
|
continue;
|
|
11169
11305
|
}
|
|
11170
11306
|
if (this.isDrainBarrier(head)) {
|
|
11171
|
-
const item = this.queue.
|
|
11307
|
+
const item = this.queue.takeAt(at);
|
|
11172
11308
|
const rid = item.command.requestId ?? `user-${Date.now()}`;
|
|
11173
11309
|
await this.runSingleTurn(item.command, rid, false, true);
|
|
11174
11310
|
continue;
|
|
@@ -11176,8 +11312,8 @@ var init_headless = __esm({
|
|
|
11176
11312
|
let n = 1;
|
|
11177
11313
|
let batchOb = head.command.onboardingState;
|
|
11178
11314
|
for (; ; n++) {
|
|
11179
|
-
const it = this.queue.peekAt(n);
|
|
11180
|
-
if (!it || it.source === "chain" || this.isDrainBarrier(it)) {
|
|
11315
|
+
const it = this.queue.peekAt(at + n);
|
|
11316
|
+
if (!it || it.held || it.source === "chain" || this.isDrainBarrier(it)) {
|
|
11181
11317
|
break;
|
|
11182
11318
|
}
|
|
11183
11319
|
const ob = it.command.onboardingState;
|
|
@@ -11188,7 +11324,7 @@ var init_headless = __esm({
|
|
|
11188
11324
|
batchOb = ob;
|
|
11189
11325
|
}
|
|
11190
11326
|
}
|
|
11191
|
-
const batch = this.queue.
|
|
11327
|
+
const batch = this.queue.takeRange(at, n);
|
|
11192
11328
|
await this.runMergedTurn(batch);
|
|
11193
11329
|
}
|
|
11194
11330
|
}
|
|
@@ -11198,7 +11334,7 @@ var init_headless = __esm({
|
|
|
11198
11334
|
* and by kickDrain (background-completion-initiated).
|
|
11199
11335
|
*/
|
|
11200
11336
|
async resumeQueue() {
|
|
11201
|
-
if (this.running || this.queue.
|
|
11337
|
+
if (this.running || !this.queue.hasDeliverable()) {
|
|
11202
11338
|
return;
|
|
11203
11339
|
}
|
|
11204
11340
|
this.running = true;
|
|
@@ -11217,7 +11353,7 @@ var init_headless = __esm({
|
|
|
11217
11353
|
* racing any currently-synchronous path.
|
|
11218
11354
|
*/
|
|
11219
11355
|
kickDrain() {
|
|
11220
|
-
if (this.running || this.queue.
|
|
11356
|
+
if (this.running || !this.queue.hasDeliverable()) {
|
|
11221
11357
|
return;
|
|
11222
11358
|
}
|
|
11223
11359
|
setTimeout(() => this.resumeQueue(), 0);
|
|
@@ -11246,28 +11382,42 @@ var init_headless = __esm({
|
|
|
11246
11382
|
};
|
|
11247
11383
|
}
|
|
11248
11384
|
/**
|
|
11249
|
-
*
|
|
11250
|
-
*
|
|
11251
|
-
*
|
|
11252
|
-
*
|
|
11253
|
-
*
|
|
11254
|
-
*
|
|
11385
|
+
* Stop everything the user can see running: the turn, an in-flight
|
|
11386
|
+
* compaction, and any external tool waiting on a result. Flushes the
|
|
11387
|
+
* follow-ups that belonged to the turn (`chain`/`background`) and HOLDS the
|
|
11388
|
+
* `source: 'user'` items — those are independent user intent, so they're
|
|
11389
|
+
* kept, but they no longer run on their own.
|
|
11390
|
+
*
|
|
11391
|
+
* Holding is the difference between Stop working and Stop looking broken.
|
|
11392
|
+
* These items used to drain immediately: `executeTurn` swallows the abort,
|
|
11393
|
+
* so `handleMessage` fell through to `drainQueueLoop` with `running` still
|
|
11394
|
+
* held and the next turn began in the same tick — the spinner never stopped,
|
|
11395
|
+
* and every additional press hit a turn that had just started. They now wait
|
|
11396
|
+
* in the queue card until the user sends again or promotes one.
|
|
11255
11397
|
*
|
|
11256
|
-
*
|
|
11257
|
-
*
|
|
11258
|
-
*
|
|
11259
|
-
*
|
|
11398
|
+
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
11399
|
+
* message and outlives the turn that started it, so leaving it running means
|
|
11400
|
+
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
11401
|
+
* gate re-compacts on the next turn if the context is still too big.
|
|
11402
|
+
*
|
|
11403
|
+
* Messages already absorbed into the in-flight merged turn are NOT held —
|
|
11404
|
+
* they were delivered into the turn that's being cancelled and each gets a
|
|
11405
|
+
* `{cancelled, absorbed:true}` terminal. Only items still sitting in the
|
|
11406
|
+
* queue survive.
|
|
11260
11407
|
*/
|
|
11261
11408
|
handleCancel() {
|
|
11262
11409
|
if (this.currentAbort) {
|
|
11263
11410
|
this.currentAbort.abort();
|
|
11264
11411
|
}
|
|
11412
|
+
const cancelledCompaction = cancelInflightCompaction();
|
|
11265
11413
|
for (const [id, pending2] of this.pendingTools) {
|
|
11266
11414
|
clearTimeout(pending2.timeout);
|
|
11267
11415
|
pending2.resolve(USER_CANCELLED_RESULT);
|
|
11268
11416
|
this.pendingTools.delete(id);
|
|
11269
11417
|
}
|
|
11270
|
-
|
|
11418
|
+
const flushed = this.queue.removeWhere((item) => item.source !== "user");
|
|
11419
|
+
const held = this.queue.holdWhere((item) => item.source === "user");
|
|
11420
|
+
return { flushed, held, cancelledCompaction };
|
|
11271
11421
|
}
|
|
11272
11422
|
/**
|
|
11273
11423
|
* Remove pending queued messages — all user messages, or one by id.
|
|
@@ -11376,12 +11526,14 @@ var init_headless = __esm({
|
|
|
11376
11526
|
return;
|
|
11377
11527
|
}
|
|
11378
11528
|
if (action === "cancel") {
|
|
11379
|
-
const
|
|
11529
|
+
const { flushed, held, cancelledCompaction } = this.handleCancel();
|
|
11380
11530
|
this.emit(
|
|
11381
11531
|
"completed",
|
|
11382
11532
|
{
|
|
11383
11533
|
success: true,
|
|
11384
|
-
...
|
|
11534
|
+
...flushed.length > 0 && { cancelledMessages: flushed },
|
|
11535
|
+
...held.length > 0 && { heldMessages: held },
|
|
11536
|
+
...cancelledCompaction && { cancelledCompaction: true }
|
|
11385
11537
|
},
|
|
11386
11538
|
requestId
|
|
11387
11539
|
);
|
|
@@ -11420,7 +11572,12 @@ var init_headless = __esm({
|
|
|
11420
11572
|
);
|
|
11421
11573
|
return;
|
|
11422
11574
|
}
|
|
11423
|
-
|
|
11575
|
+
if (delivery === "asap") {
|
|
11576
|
+
this.queue.promoteToFront(id);
|
|
11577
|
+
this.kickDrain();
|
|
11578
|
+
} else {
|
|
11579
|
+
this.queue.setDelivery(id, delivery);
|
|
11580
|
+
}
|
|
11424
11581
|
this.emit("completed", { success: true }, requestId);
|
|
11425
11582
|
return;
|
|
11426
11583
|
}
|
|
@@ -11486,7 +11643,7 @@ var init_headless = __esm({
|
|
|
11486
11643
|
}
|
|
11487
11644
|
this.emit("completed", { success: true }, requestId);
|
|
11488
11645
|
} catch (err) {
|
|
11489
|
-
const error = err.message || "Compaction failed";
|
|
11646
|
+
const error = err instanceof CompactionCancelledError ? "cancelled" : err.message || "Compaction failed";
|
|
11490
11647
|
this.emit("completed", { success: false, error }, requestId);
|
|
11491
11648
|
}
|
|
11492
11649
|
return;
|
|
@@ -11504,7 +11661,7 @@ var init_headless = __esm({
|
|
|
11504
11661
|
);
|
|
11505
11662
|
return;
|
|
11506
11663
|
}
|
|
11507
|
-
if (this.queue.
|
|
11664
|
+
if (!this.queue.hasDeliverable()) {
|
|
11508
11665
|
this.emit("completed", { success: true }, requestId);
|
|
11509
11666
|
return;
|
|
11510
11667
|
}
|
|
@@ -153,4 +153,4 @@ Consider the ways in which AI can be incorporated into backend methods to solve
|
|
|
153
153
|
|
|
154
154
|
### Task Agents
|
|
155
155
|
|
|
156
|
-
For multi-step tasks where the model needs to autonomously compose actions (research + scrape + generate, enrichment pipelines, content creation), use `runTask()` instead of chaining actions manually. It runs an agent loop and returns structured JSON. Its tools can include SDK actions
|
|
156
|
+
For multi-step tasks where the model needs to autonomously compose actions (research + scrape + generate, enrichment pipelines, content creation), use `runTask()` instead of chaining actions manually. It runs an agent loop and returns structured JSON. Its tools can include SDK actions, your app's own methods, and inline functions defined at the call site, so the agent can read your data to decide what to do next and write results back itself. Load the `taskAgents` skill before writing one — it is the full reference.
|