@mindstudio-ai/remy 0.1.322 → 0.1.324
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/_resumed.md +3 -0
- package/dist/headless.d.ts +17 -3
- package/dist/headless.js +265 -117
- package/dist/index.js +282 -131
- package/package.json +1 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
This step was already started once and was interrupted before it finished. Work may have continued in the conversation since — including work that covers part or all of it.
|
|
2
|
+
|
|
3
|
+
Take stock of the current state of the project before you act, and pick up from there rather than starting over. If everything below has already been done, say so briefly and end the turn.
|
package/dist/headless.d.ts
CHANGED
|
@@ -270,6 +270,19 @@ declare class HeadlessSession {
|
|
|
270
270
|
* pipeline where it stopped. Holding only the remainder would resume one step
|
|
271
271
|
* PAST the interruption, polishing and finalizing half-built code.
|
|
272
272
|
*
|
|
273
|
+
* That re-queued step is marked `resumed`, which is what stops it being a
|
|
274
|
+
* verbatim replay. The message the user sends to un-pause a build is usually
|
|
275
|
+
* the rest of that build ("try again, I fixed the adapter") and Remy does the
|
|
276
|
+
* work inside that turn — so re-delivering the original "build everything
|
|
277
|
+
* now" afterwards had it rebuild an app it had just finished.
|
|
278
|
+
*
|
|
279
|
+
* `reason: 'shutdown'` is the environment taking the agent down rather than a
|
|
280
|
+
* person pressing Stop. It pauses identically — nothing may start a turn
|
|
281
|
+
* while the workspace is being tarred — but tags the chain steps it holds so
|
|
282
|
+
* the next boot releases them and the pipeline carries on by itself. Called
|
|
283
|
+
* from the stdin `cancel` (the sandbox's pre-destroy quiesce) and from our own
|
|
284
|
+
* SIGTERM handler, whichever gets there first.
|
|
285
|
+
*
|
|
273
286
|
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
274
287
|
* message and outlives the turn that started it, so leaving it running means
|
|
275
288
|
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
@@ -285,9 +298,10 @@ declare class HeadlessSession {
|
|
|
285
298
|
* Remove pending queued messages: all user messages (no id), or a single item
|
|
286
299
|
* by id. Does not affect the in-flight turn (use `cancel` for that).
|
|
287
300
|
*
|
|
288
|
-
* Held chain items — a paused pipeline — are removable only by explicit id
|
|
289
|
-
*
|
|
290
|
-
*
|
|
301
|
+
* Held chain items — a paused pipeline — are removable only by explicit id:
|
|
302
|
+
* the queue card's Discard sends one call per step, and its per-step X sends
|
|
303
|
+
* one. The id-less form is the card's "Clear", which is about the user's own
|
|
304
|
+
* messages and shouldn't get to decide the pipeline's fate. A DELIVERABLE
|
|
291
305
|
* chain item is never removable: that's live pipeline work. (The step
|
|
292
306
|
* actually running isn't in the queue at all — drainQueueLoop takes it out
|
|
293
307
|
* before running it.)
|
package/dist/headless.js
CHANGED
|
@@ -981,6 +981,34 @@ function parseSentinel(text) {
|
|
|
981
981
|
}
|
|
982
982
|
return { name: match[1], remainder: match[2] };
|
|
983
983
|
}
|
|
984
|
+
function sentinelParams(text) {
|
|
985
|
+
const parsed = parseSentinel(text);
|
|
986
|
+
if (!parsed) {
|
|
987
|
+
return {};
|
|
988
|
+
}
|
|
989
|
+
const firstLine = parsed.remainder.split("\n")[0].trim();
|
|
990
|
+
if (!firstLine) {
|
|
991
|
+
return {};
|
|
992
|
+
}
|
|
993
|
+
try {
|
|
994
|
+
const value = JSON.parse(firstLine);
|
|
995
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
996
|
+
} catch {
|
|
997
|
+
return {};
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function setSentinelParams(text, patch) {
|
|
1001
|
+
const parsed = parseSentinel(text);
|
|
1002
|
+
if (!parsed) {
|
|
1003
|
+
return text;
|
|
1004
|
+
}
|
|
1005
|
+
const newlineAt = parsed.remainder.indexOf("\n");
|
|
1006
|
+
const body = newlineAt === -1 ? "" : parsed.remainder.slice(newlineAt + 1);
|
|
1007
|
+
const params = { ...sentinelParams(text), ...patch };
|
|
1008
|
+
const line = `${sentinel(parsed.name)}${JSON.stringify(params)}`;
|
|
1009
|
+
return body ? `${line}
|
|
1010
|
+
${body}` : line;
|
|
1011
|
+
}
|
|
984
1012
|
function stripSentinelLine(text) {
|
|
985
1013
|
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
986
1014
|
}
|
|
@@ -2334,11 +2362,23 @@ var bashTool = {
|
|
|
2334
2362
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
2335
2363
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
2336
2364
|
return new Promise((resolve4) => {
|
|
2337
|
-
const child = spawn2("
|
|
2365
|
+
const child = spawn2("bash", ["-c", input.command], {
|
|
2338
2366
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
2339
2367
|
// process happens to be", which is the project root only by luck.
|
|
2340
2368
|
cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
|
|
2341
|
-
|
|
2369
|
+
// Output is rendered in a terminal view in the IDE, so ask tools for
|
|
2370
|
+
// color. The devbox image sets none of these — it declares only
|
|
2371
|
+
// WORKSPACE_DIR, LANG, LC_ALL, PIP_BREAK_SYSTEM_PACKAGES,
|
|
2372
|
+
// NPM_CONFIG_PREFIX and PATH — and a container gets no TERM unless
|
|
2373
|
+
// something sets it, so without TERM anything driving terminfo assumes
|
|
2374
|
+
// a dumb terminal. Same trio the sandbox's pty handler sets, for the
|
|
2375
|
+
// same reason; FORCE_COLOR alone only reaches the Node/chalk ecosystem.
|
|
2376
|
+
env: {
|
|
2377
|
+
...process.env,
|
|
2378
|
+
TERM: "xterm-256color",
|
|
2379
|
+
CLICOLOR: "1",
|
|
2380
|
+
FORCE_COLOR: "1"
|
|
2381
|
+
}
|
|
2342
2382
|
});
|
|
2343
2383
|
let output = "";
|
|
2344
2384
|
child.stdout.on("data", (chunk) => {
|
|
@@ -3374,6 +3414,10 @@ function acquireBrowserLock() {
|
|
|
3374
3414
|
// src/toolRegistry.ts
|
|
3375
3415
|
var log6 = createLogger("tool-registry");
|
|
3376
3416
|
var USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
3417
|
+
var ENV_INTERRUPTED_RESULT = "[INTERRUPTED] The environment shut down while this tool was running. No user action was involved \u2014 pick up from here.";
|
|
3418
|
+
function cancelledToolResult(signal) {
|
|
3419
|
+
return signal?.reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT;
|
|
3420
|
+
}
|
|
3377
3421
|
var ToolRegistry = class {
|
|
3378
3422
|
entries = /* @__PURE__ */ new Map();
|
|
3379
3423
|
onEvent;
|
|
@@ -3448,6 +3492,20 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
3448
3492
|
};
|
|
3449
3493
|
|
|
3450
3494
|
// src/recording.ts
|
|
3495
|
+
function collectRecordings(messages) {
|
|
3496
|
+
const recordings = [];
|
|
3497
|
+
for (const msg of messages) {
|
|
3498
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
3499
|
+
continue;
|
|
3500
|
+
}
|
|
3501
|
+
for (const block of msg.content) {
|
|
3502
|
+
if (block.type === "tool" && block.recording) {
|
|
3503
|
+
recordings.push(block.recording);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
return recordings;
|
|
3508
|
+
}
|
|
3451
3509
|
function liftRecording(result) {
|
|
3452
3510
|
if (!result.includes('"recording"')) {
|
|
3453
3511
|
return { result };
|
|
@@ -3503,6 +3561,18 @@ function capToolResult(result, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
3503
3561
|
}
|
|
3504
3562
|
var MAX_SUBAGENT_RESULT_BYTES = 32 * 1024;
|
|
3505
3563
|
var MAX_SUBAGENT_TRANSCRIPT_BYTES = 512 * 1024;
|
|
3564
|
+
function attachSubAgentTranscript(block, messages) {
|
|
3565
|
+
if (!block.recordings) {
|
|
3566
|
+
const recordings = collectRecordings(messages);
|
|
3567
|
+
if (recordings.length > 0) {
|
|
3568
|
+
block.recordings = recordings;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
block.subAgentMessages = capSubAgentTranscript(messages);
|
|
3572
|
+
}
|
|
3573
|
+
function dropToolResultMessages(messages) {
|
|
3574
|
+
return messages.filter((m) => !(m.role === "user" && m.toolCallId));
|
|
3575
|
+
}
|
|
3506
3576
|
function capSubAgentTranscript(messages) {
|
|
3507
3577
|
for (const msg of messages) {
|
|
3508
3578
|
capMessageForHistory(msg, MAX_SUBAGENT_RESULT_BYTES);
|
|
@@ -3547,7 +3617,7 @@ function capMessageForHistory(msg, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
3547
3617
|
);
|
|
3548
3618
|
}
|
|
3549
3619
|
if (Array.isArray(block.subAgentMessages)) {
|
|
3550
|
-
block
|
|
3620
|
+
attachSubAgentTranscript(block, block.subAgentMessages);
|
|
3551
3621
|
}
|
|
3552
3622
|
}
|
|
3553
3623
|
} else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
|
|
@@ -3892,7 +3962,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3892
3962
|
messages: thisInvocation()
|
|
3893
3963
|
};
|
|
3894
3964
|
}
|
|
3895
|
-
return { text:
|
|
3965
|
+
return { text: cancelledToolResult(signal), messages: thisInvocation() };
|
|
3896
3966
|
}
|
|
3897
3967
|
let lastToolResult = "";
|
|
3898
3968
|
let watchedBlocks = [];
|
|
@@ -4128,7 +4198,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4128
4198
|
if (signal?.aborted) {
|
|
4129
4199
|
return {
|
|
4130
4200
|
id: tc.id,
|
|
4131
|
-
result:
|
|
4201
|
+
result: cancelledToolResult(signal),
|
|
4132
4202
|
isError: true
|
|
4133
4203
|
};
|
|
4134
4204
|
}
|
|
@@ -4245,7 +4315,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4245
4315
|
}
|
|
4246
4316
|
const innerMsgs = subAgentMessages.get(r.id);
|
|
4247
4317
|
if (innerMsgs) {
|
|
4248
|
-
block
|
|
4318
|
+
attachSubAgentTranscript(block, innerMsgs);
|
|
4249
4319
|
}
|
|
4250
4320
|
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4251
4321
|
try {
|
|
@@ -4787,13 +4857,12 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4787
4857
|
},
|
|
4788
4858
|
toolRegistry: context.toolRegistry
|
|
4789
4859
|
});
|
|
4790
|
-
context.subAgentMessages?.set(
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
(m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(
|
|
4794
|
-
(b) => b.type === "tool" && b.name === "browserCommand" && !!b.recording
|
|
4795
|
-
)
|
|
4860
|
+
context.subAgentMessages?.set(
|
|
4861
|
+
context.toolCallId,
|
|
4862
|
+
dropToolResultMessages(result.messages)
|
|
4796
4863
|
);
|
|
4864
|
+
const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
|
|
4865
|
+
const recorded = collectRecordings(result.messages).length > 0;
|
|
4797
4866
|
return {
|
|
4798
4867
|
text: result.text,
|
|
4799
4868
|
recorded,
|
|
@@ -4948,6 +5017,79 @@ var screenshotTool = {
|
|
|
4948
5017
|
execute: (input, context) => executeScreenshot(input, context?.onLog, context)
|
|
4949
5018
|
};
|
|
4950
5019
|
|
|
5020
|
+
// src/tools/common/scrapeWebUrl.ts
|
|
5021
|
+
var CONTENT_BUDGET_BYTES = MAX_TOOL_RESULT_BYTES - 4 * 1024;
|
|
5022
|
+
var TRUNCATED_SUFFIX = "\n\n[output truncated]";
|
|
5023
|
+
async function fetchWebPage(url, opts) {
|
|
5024
|
+
const raw = await runMindstudioCli(
|
|
5025
|
+
[
|
|
5026
|
+
"scrape-url",
|
|
5027
|
+
"--url",
|
|
5028
|
+
url,
|
|
5029
|
+
"--page-options",
|
|
5030
|
+
JSON.stringify({ onlyMainContent: true, screenshot: opts.screenshot })
|
|
5031
|
+
],
|
|
5032
|
+
{
|
|
5033
|
+
onLog: opts.onLog,
|
|
5034
|
+
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
5035
|
+
caller: opts.caller
|
|
5036
|
+
}
|
|
5037
|
+
);
|
|
5038
|
+
return shapeFetchResult(url, raw);
|
|
5039
|
+
}
|
|
5040
|
+
function shapeFetchResult(url, raw) {
|
|
5041
|
+
const body = raw.endsWith(TRUNCATED_SUFFIX) ? raw.slice(0, -TRUNCATED_SUFFIX.length) : raw;
|
|
5042
|
+
let envelope;
|
|
5043
|
+
try {
|
|
5044
|
+
envelope = JSON.parse(body);
|
|
5045
|
+
} catch {
|
|
5046
|
+
return raw;
|
|
5047
|
+
}
|
|
5048
|
+
if (!envelope || typeof envelope !== "object" || typeof envelope.content !== "string") {
|
|
5049
|
+
return raw;
|
|
5050
|
+
}
|
|
5051
|
+
const shot = envelope.screenshot;
|
|
5052
|
+
const screenshot = typeof shot === "string" ? shot : Array.isArray(shot) && typeof shot[0] === "string" ? shot[0] : void 0;
|
|
5053
|
+
return JSON.stringify({
|
|
5054
|
+
url,
|
|
5055
|
+
...screenshot ? { screenshot } : {},
|
|
5056
|
+
content: capContent(envelope.content)
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
function capContent(content) {
|
|
5060
|
+
const total = Buffer.byteLength(content, "utf-8");
|
|
5061
|
+
if (total <= CONTENT_BUDGET_BYTES) {
|
|
5062
|
+
return content;
|
|
5063
|
+
}
|
|
5064
|
+
const head = Buffer.from(content, "utf-8").subarray(0, CONTENT_BUDGET_BYTES).toString("utf-8");
|
|
5065
|
+
return head + `
|
|
5066
|
+
|
|
5067
|
+
[page content truncated at ${(CONTENT_BUDGET_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 narrow what you need or fetch a sub-page]`;
|
|
5068
|
+
}
|
|
5069
|
+
var scrapeWebUrlTool = {
|
|
5070
|
+
definition: {
|
|
5071
|
+
name: "scrapeWebUrl",
|
|
5072
|
+
description: "Fetch a web page. Returns its main content as markdown plus `screenshot`, a full-page capture the person sees in the editor. Read the markdown; analyze the screenshot with analyzeImage only when the visual design matters. Use this when you need to fetch or analyze content from a website.",
|
|
5073
|
+
inputSchema: {
|
|
5074
|
+
type: "object",
|
|
5075
|
+
properties: {
|
|
5076
|
+
url: {
|
|
5077
|
+
type: "string",
|
|
5078
|
+
description: "The URL to fetch."
|
|
5079
|
+
}
|
|
5080
|
+
},
|
|
5081
|
+
required: ["url"]
|
|
5082
|
+
}
|
|
5083
|
+
},
|
|
5084
|
+
async execute(input, context) {
|
|
5085
|
+
return fetchWebPage(input.url, {
|
|
5086
|
+
screenshot: true,
|
|
5087
|
+
caller: "parent",
|
|
5088
|
+
onLog: context?.onLog
|
|
5089
|
+
});
|
|
5090
|
+
}
|
|
5091
|
+
};
|
|
5092
|
+
|
|
4951
5093
|
// src/subagents/research/tools.ts
|
|
4952
5094
|
var searchGoogleDefinition = {
|
|
4953
5095
|
name: "searchGoogle",
|
|
@@ -5007,22 +5149,6 @@ async function executeSearchGoogle(input, onLog, caller) {
|
|
|
5007
5149
|
}
|
|
5008
5150
|
);
|
|
5009
5151
|
}
|
|
5010
|
-
async function executeScrapeWebUrl(input, onLog, caller) {
|
|
5011
|
-
return runMindstudioCli(
|
|
5012
|
-
[
|
|
5013
|
-
"scrape-url",
|
|
5014
|
-
"--url",
|
|
5015
|
-
String(input.url),
|
|
5016
|
-
"--page-options",
|
|
5017
|
-
JSON.stringify({ onlyMainContent: true })
|
|
5018
|
-
],
|
|
5019
|
-
{
|
|
5020
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
5021
|
-
onLog,
|
|
5022
|
-
caller
|
|
5023
|
-
}
|
|
5024
|
-
);
|
|
5025
|
-
}
|
|
5026
5152
|
|
|
5027
5153
|
// src/subagents/research/index.ts
|
|
5028
5154
|
var BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
|
|
@@ -5048,7 +5174,11 @@ async function runResearch(task, context) {
|
|
|
5048
5174
|
return executeSearchGoogle(toolInput, childCtx.onLog, "research");
|
|
5049
5175
|
}
|
|
5050
5176
|
if (name === "scrapeWebUrl") {
|
|
5051
|
-
return
|
|
5177
|
+
return fetchWebPage(String(toolInput.url), {
|
|
5178
|
+
screenshot: false,
|
|
5179
|
+
caller: "research",
|
|
5180
|
+
onLog: childCtx.onLog
|
|
5181
|
+
});
|
|
5052
5182
|
}
|
|
5053
5183
|
return executeTool(name, toolInput, childCtx);
|
|
5054
5184
|
},
|
|
@@ -5096,7 +5226,7 @@ __export(scrapeWebUrl_exports, {
|
|
|
5096
5226
|
});
|
|
5097
5227
|
var definition = {
|
|
5098
5228
|
name: "scrapeWebUrl",
|
|
5099
|
-
description: "Fetch
|
|
5229
|
+
description: "Fetch a web page as markdown, plus `screenshot`, a full-page capture you can analyze. Use for reading a specific URL \u2014 a site the user referenced, a brand to match, a page the researcher cited that you want in full.",
|
|
5100
5230
|
inputSchema: {
|
|
5101
5231
|
type: "object",
|
|
5102
5232
|
properties: {
|
|
@@ -5109,17 +5239,11 @@ var definition = {
|
|
|
5109
5239
|
}
|
|
5110
5240
|
};
|
|
5111
5241
|
async function execute(input, onLog) {
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
input.url,
|
|
5118
|
-
"--page-options",
|
|
5119
|
-
JSON.stringify(pageOptions)
|
|
5120
|
-
],
|
|
5121
|
-
{ onLog, caller: "designExpert", maxBuffer: SCRAPE_MAX_BUFFER }
|
|
5122
|
-
);
|
|
5242
|
+
return fetchWebPage(String(input.url), {
|
|
5243
|
+
screenshot: true,
|
|
5244
|
+
caller: "designExpert",
|
|
5245
|
+
onLog
|
|
5246
|
+
});
|
|
5123
5247
|
}
|
|
5124
5248
|
|
|
5125
5249
|
// src/subagents/designExpert/tools/analyzeDesign.ts
|
|
@@ -7300,50 +7424,6 @@ var reviewExistingProjectTool = {
|
|
|
7300
7424
|
}
|
|
7301
7425
|
};
|
|
7302
7426
|
|
|
7303
|
-
// src/tools/common/scrapeWebUrl.ts
|
|
7304
|
-
var scrapeWebUrlTool = {
|
|
7305
|
-
definition: {
|
|
7306
|
-
name: "scrapeWebUrl",
|
|
7307
|
-
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
7308
|
-
inputSchema: {
|
|
7309
|
-
type: "object",
|
|
7310
|
-
properties: {
|
|
7311
|
-
url: {
|
|
7312
|
-
type: "string",
|
|
7313
|
-
description: "The URL to fetch."
|
|
7314
|
-
},
|
|
7315
|
-
screenshot: {
|
|
7316
|
-
type: "boolean",
|
|
7317
|
-
description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
|
|
7318
|
-
}
|
|
7319
|
-
},
|
|
7320
|
-
required: ["url"]
|
|
7321
|
-
}
|
|
7322
|
-
},
|
|
7323
|
-
async execute(input, context) {
|
|
7324
|
-
const url = input.url;
|
|
7325
|
-
const screenshot = input.screenshot;
|
|
7326
|
-
const pageOptions = { onlyMainContent: true };
|
|
7327
|
-
if (screenshot) {
|
|
7328
|
-
pageOptions.screenshot = true;
|
|
7329
|
-
}
|
|
7330
|
-
return runMindstudioCli(
|
|
7331
|
-
[
|
|
7332
|
-
"scrape-url",
|
|
7333
|
-
"--url",
|
|
7334
|
-
url,
|
|
7335
|
-
"--page-options",
|
|
7336
|
-
JSON.stringify(pageOptions)
|
|
7337
|
-
],
|
|
7338
|
-
{
|
|
7339
|
-
onLog: context?.onLog,
|
|
7340
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
7341
|
-
caller: "parent"
|
|
7342
|
-
}
|
|
7343
|
-
);
|
|
7344
|
-
}
|
|
7345
|
-
};
|
|
7346
|
-
|
|
7347
7427
|
// src/tools/index.ts
|
|
7348
7428
|
function deriveContext(parent, toolCallId, onLog) {
|
|
7349
7429
|
return { ...parent, toolCallId, onLog };
|
|
@@ -8829,17 +8909,11 @@ function resolveAction(text) {
|
|
|
8829
8909
|
if (!parsed) {
|
|
8830
8910
|
return null;
|
|
8831
8911
|
}
|
|
8832
|
-
const { name: triggerName
|
|
8912
|
+
const { name: triggerName } = parsed;
|
|
8833
8913
|
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
8834
8914
|
return null;
|
|
8835
8915
|
}
|
|
8836
|
-
|
|
8837
|
-
if (remainder) {
|
|
8838
|
-
try {
|
|
8839
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
8840
|
-
} catch {
|
|
8841
|
-
}
|
|
8842
|
-
}
|
|
8916
|
+
const params = sentinelParams(text);
|
|
8843
8917
|
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
8844
8918
|
let next;
|
|
8845
8919
|
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
@@ -8854,8 +8928,16 @@ function resolveAction(text) {
|
|
|
8854
8928
|
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
8855
8929
|
body = body.replaceAll(`{{${key}}}`, str);
|
|
8856
8930
|
}
|
|
8931
|
+
const resumed = params.resumed === true;
|
|
8932
|
+
if (resumed) {
|
|
8933
|
+
body = `${readAsset("automatedActions", "_resumed.md")}
|
|
8934
|
+
|
|
8935
|
+
${body}`;
|
|
8936
|
+
}
|
|
8857
8937
|
return {
|
|
8858
|
-
message: automatedMessage(triggerName, body),
|
|
8938
|
+
message: resumed ? setSentinelParams(automatedMessage(triggerName, body), {
|
|
8939
|
+
resumed: true
|
|
8940
|
+
}) : automatedMessage(triggerName, body),
|
|
8859
8941
|
next
|
|
8860
8942
|
};
|
|
8861
8943
|
}
|
|
@@ -9238,7 +9320,10 @@ async function runTurn(params) {
|
|
|
9238
9320
|
const automated = parseSentinel(entry.text);
|
|
9239
9321
|
if (automated) {
|
|
9240
9322
|
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
9241
|
-
|
|
9323
|
+
const resumed = sentinelParams(entry.text).resumed === true;
|
|
9324
|
+
parts.push(
|
|
9325
|
+
`Automated action: ${automated.name}${resumed ? " (resuming interrupted work)" : ""}`
|
|
9326
|
+
);
|
|
9242
9327
|
hasUserSignal = true;
|
|
9243
9328
|
}
|
|
9244
9329
|
} else if (entry.text) {
|
|
@@ -9627,7 +9712,11 @@ async function runTurn(params) {
|
|
|
9627
9712
|
const results = await Promise.all(
|
|
9628
9713
|
toolCalls.map(async (tc) => {
|
|
9629
9714
|
if (signal?.aborted) {
|
|
9630
|
-
return {
|
|
9715
|
+
return {
|
|
9716
|
+
id: tc.id,
|
|
9717
|
+
result: cancelledToolResult(signal),
|
|
9718
|
+
isError: true
|
|
9719
|
+
};
|
|
9631
9720
|
}
|
|
9632
9721
|
const toolStart = Date.now();
|
|
9633
9722
|
let settle;
|
|
@@ -9646,7 +9735,7 @@ async function runTurn(params) {
|
|
|
9646
9735
|
};
|
|
9647
9736
|
const cascadeAbort = () => {
|
|
9648
9737
|
toolAbort.abort();
|
|
9649
|
-
safeSettle(
|
|
9738
|
+
safeSettle(cancelledToolResult(signal), true);
|
|
9650
9739
|
};
|
|
9651
9740
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
9652
9741
|
const run = async (input) => {
|
|
@@ -9755,7 +9844,7 @@ async function runTurn(params) {
|
|
|
9755
9844
|
block.completedAt = Date.now();
|
|
9756
9845
|
const msgs = subAgentMessages.get(r.id);
|
|
9757
9846
|
if (msgs) {
|
|
9758
|
-
block
|
|
9847
|
+
attachSubAgentTranscript(block, msgs);
|
|
9759
9848
|
}
|
|
9760
9849
|
}
|
|
9761
9850
|
}
|
|
@@ -10007,6 +10096,15 @@ function holdRestoredUserItems(items) {
|
|
|
10007
10096
|
(item) => item.source === "user" ? { ...item, held: true } : item
|
|
10008
10097
|
);
|
|
10009
10098
|
}
|
|
10099
|
+
function releaseShutdownHolds(items) {
|
|
10100
|
+
return items.map((item) => {
|
|
10101
|
+
if (item.heldBy !== "shutdown" || item.source === "user") {
|
|
10102
|
+
return item;
|
|
10103
|
+
}
|
|
10104
|
+
const { held: _held, heldBy: _heldBy, ...released } = item;
|
|
10105
|
+
return released;
|
|
10106
|
+
});
|
|
10107
|
+
}
|
|
10010
10108
|
var MessageQueue = class {
|
|
10011
10109
|
items = [];
|
|
10012
10110
|
onChange;
|
|
@@ -10078,16 +10176,27 @@ var MessageQueue = class {
|
|
|
10078
10176
|
/**
|
|
10079
10177
|
* Mark matching items `held` — waiting on the user rather than on the agent.
|
|
10080
10178
|
* Fires onChange only if something changed. Returns the held items.
|
|
10179
|
+
*
|
|
10180
|
+
* `heldBy` is stamped only on items this call actually transitions. An item
|
|
10181
|
+
* that was ALREADY held keeps whatever hold it had: otherwise a shutdown
|
|
10182
|
+
* arriving after a user's Stop — a box reaped while they were away from the
|
|
10183
|
+
* tab — would retag their deliberate pause as environmental and the next
|
|
10184
|
+
* boot would auto-run the very build they stopped.
|
|
10081
10185
|
*/
|
|
10082
|
-
holdWhere(predicate) {
|
|
10186
|
+
holdWhere(predicate, heldBy) {
|
|
10083
10187
|
const held = [];
|
|
10084
10188
|
let changed = false;
|
|
10085
10189
|
for (const item of this.items) {
|
|
10086
10190
|
if (!predicate(item)) {
|
|
10087
10191
|
continue;
|
|
10088
10192
|
}
|
|
10089
|
-
|
|
10090
|
-
|
|
10193
|
+
if (!item.held) {
|
|
10194
|
+
changed = true;
|
|
10195
|
+
item.held = true;
|
|
10196
|
+
if (heldBy) {
|
|
10197
|
+
item.heldBy = heldBy;
|
|
10198
|
+
}
|
|
10199
|
+
}
|
|
10091
10200
|
held.push(item);
|
|
10092
10201
|
}
|
|
10093
10202
|
if (changed) {
|
|
@@ -10135,6 +10244,7 @@ var MessageQueue = class {
|
|
|
10135
10244
|
const [item] = this.items.splice(idx, 1);
|
|
10136
10245
|
item.delivery = "asap";
|
|
10137
10246
|
delete item.held;
|
|
10247
|
+
delete item.heldBy;
|
|
10138
10248
|
this.items.unshift(item);
|
|
10139
10249
|
this.onChange?.();
|
|
10140
10250
|
return item;
|
|
@@ -10158,6 +10268,7 @@ var MessageQueue = class {
|
|
|
10158
10268
|
continue;
|
|
10159
10269
|
}
|
|
10160
10270
|
delete item.held;
|
|
10271
|
+
delete item.heldBy;
|
|
10161
10272
|
released.push(item);
|
|
10162
10273
|
}
|
|
10163
10274
|
const back = this.items.filter(defer);
|
|
@@ -10287,10 +10398,13 @@ var HeadlessSession = class {
|
|
|
10287
10398
|
});
|
|
10288
10399
|
await initOrgContext(this.config);
|
|
10289
10400
|
const resumed = loadSession(this.state);
|
|
10290
|
-
this.queue = new MessageQueue(
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10401
|
+
this.queue = new MessageQueue(
|
|
10402
|
+
releaseShutdownHolds(holdRestoredUserItems(loadQueue())),
|
|
10403
|
+
() => {
|
|
10404
|
+
this.persistStats();
|
|
10405
|
+
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
10406
|
+
}
|
|
10407
|
+
);
|
|
10294
10408
|
this.passivePen = loadPassiveResults();
|
|
10295
10409
|
this.persistStats();
|
|
10296
10410
|
if (resumed) {
|
|
@@ -10412,6 +10526,11 @@ var HeadlessSession = class {
|
|
|
10412
10526
|
this.emit("ready");
|
|
10413
10527
|
}
|
|
10414
10528
|
shutdown = () => {
|
|
10529
|
+
try {
|
|
10530
|
+
this.handleCancel("shutdown");
|
|
10531
|
+
} catch (err) {
|
|
10532
|
+
log17.warn("Shutdown cancel failed", { error: err?.message });
|
|
10533
|
+
}
|
|
10415
10534
|
this.emit("stopping");
|
|
10416
10535
|
this.emit("stopped");
|
|
10417
10536
|
process.exit(0);
|
|
@@ -11349,6 +11468,19 @@ var HeadlessSession = class {
|
|
|
11349
11468
|
* pipeline where it stopped. Holding only the remainder would resume one step
|
|
11350
11469
|
* PAST the interruption, polishing and finalizing half-built code.
|
|
11351
11470
|
*
|
|
11471
|
+
* That re-queued step is marked `resumed`, which is what stops it being a
|
|
11472
|
+
* verbatim replay. The message the user sends to un-pause a build is usually
|
|
11473
|
+
* the rest of that build ("try again, I fixed the adapter") and Remy does the
|
|
11474
|
+
* work inside that turn — so re-delivering the original "build everything
|
|
11475
|
+
* now" afterwards had it rebuild an app it had just finished.
|
|
11476
|
+
*
|
|
11477
|
+
* `reason: 'shutdown'` is the environment taking the agent down rather than a
|
|
11478
|
+
* person pressing Stop. It pauses identically — nothing may start a turn
|
|
11479
|
+
* while the workspace is being tarred — but tags the chain steps it holds so
|
|
11480
|
+
* the next boot releases them and the pipeline carries on by itself. Called
|
|
11481
|
+
* from the stdin `cancel` (the sandbox's pre-destroy quiesce) and from our own
|
|
11482
|
+
* SIGTERM handler, whichever gets there first.
|
|
11483
|
+
*
|
|
11352
11484
|
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
11353
11485
|
* message and outlives the turn that started it, so leaving it running means
|
|
11354
11486
|
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
@@ -11359,14 +11491,16 @@ var HeadlessSession = class {
|
|
|
11359
11491
|
* `{cancelled, absorbed:true}` terminal. Only items still sitting in the
|
|
11360
11492
|
* queue survive.
|
|
11361
11493
|
*/
|
|
11362
|
-
handleCancel() {
|
|
11494
|
+
handleCancel(reason) {
|
|
11363
11495
|
if (this.currentAbort) {
|
|
11364
|
-
this.currentAbort.abort();
|
|
11496
|
+
this.currentAbort.abort(reason);
|
|
11365
11497
|
}
|
|
11366
11498
|
const cancelledCompaction = cancelInflightCompaction();
|
|
11367
11499
|
for (const [id, pending2] of this.pendingTools) {
|
|
11368
11500
|
clearTimeout(pending2.timeout);
|
|
11369
|
-
pending2.resolve(
|
|
11501
|
+
pending2.resolve(
|
|
11502
|
+
reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT
|
|
11503
|
+
);
|
|
11370
11504
|
this.pendingTools.delete(id);
|
|
11371
11505
|
}
|
|
11372
11506
|
const flushed = this.queue.removeWhere(
|
|
@@ -11377,7 +11511,13 @@ var HeadlessSession = class {
|
|
|
11377
11511
|
this.queue.unshift({
|
|
11378
11512
|
command: {
|
|
11379
11513
|
action: "message",
|
|
11380
|
-
|
|
11514
|
+
// Marked so it comes back as a resumption rather than a replay: the
|
|
11515
|
+
// step already ran once, and whatever the user does between here and
|
|
11516
|
+
// its re-delivery may well be the rest of it. resolveAction turns
|
|
11517
|
+
// this param into a preamble, and both the chat row and the queue
|
|
11518
|
+
// card label the step off it. Merged, so a step interrupted twice
|
|
11519
|
+
// keeps its own params and re-marking stays idempotent.
|
|
11520
|
+
text: setSentinelParams(step.text, { resumed: true }),
|
|
11381
11521
|
onboardingState: step.onboardingState,
|
|
11382
11522
|
// Fresh id: the original command's terminal has already gone out as
|
|
11383
11523
|
// cancelled, and one command gets exactly one `completed`.
|
|
@@ -11385,13 +11525,18 @@ var HeadlessSession = class {
|
|
|
11385
11525
|
},
|
|
11386
11526
|
source: "chain",
|
|
11387
11527
|
enqueuedAt: Date.now(),
|
|
11388
|
-
held: true
|
|
11528
|
+
held: true,
|
|
11529
|
+
...reason === "shutdown" && { heldBy: reason }
|
|
11389
11530
|
});
|
|
11390
11531
|
this.currentChainStep = null;
|
|
11391
11532
|
}
|
|
11392
|
-
const held =
|
|
11393
|
-
|
|
11394
|
-
|
|
11533
|
+
const held = [
|
|
11534
|
+
...this.queue.holdWhere(
|
|
11535
|
+
(item) => item.source === "chain",
|
|
11536
|
+
reason === "shutdown" ? reason : void 0
|
|
11537
|
+
),
|
|
11538
|
+
...this.queue.holdWhere((item) => item.source === "user")
|
|
11539
|
+
];
|
|
11395
11540
|
return {
|
|
11396
11541
|
flushed,
|
|
11397
11542
|
held,
|
|
@@ -11403,9 +11548,10 @@ var HeadlessSession = class {
|
|
|
11403
11548
|
* Remove pending queued messages: all user messages (no id), or a single item
|
|
11404
11549
|
* by id. Does not affect the in-flight turn (use `cancel` for that).
|
|
11405
11550
|
*
|
|
11406
|
-
* Held chain items — a paused pipeline — are removable only by explicit id
|
|
11407
|
-
*
|
|
11408
|
-
*
|
|
11551
|
+
* Held chain items — a paused pipeline — are removable only by explicit id:
|
|
11552
|
+
* the queue card's Discard sends one call per step, and its per-step X sends
|
|
11553
|
+
* one. The id-less form is the card's "Clear", which is about the user's own
|
|
11554
|
+
* messages and shouldn't get to decide the pipeline's fate. A DELIVERABLE
|
|
11409
11555
|
* chain item is never removable: that's live pipeline work. (The step
|
|
11410
11556
|
* actually running isn't in the queue at all — drainQueueLoop takes it out
|
|
11411
11557
|
* before running it.)
|
|
@@ -11511,7 +11657,9 @@ var HeadlessSession = class {
|
|
|
11511
11657
|
return;
|
|
11512
11658
|
}
|
|
11513
11659
|
if (action === "cancel") {
|
|
11514
|
-
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
11660
|
+
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
11661
|
+
parsed.reason === "shutdown" ? "shutdown" : void 0
|
|
11662
|
+
);
|
|
11515
11663
|
this.emit(
|
|
11516
11664
|
"completed",
|
|
11517
11665
|
{
|
package/dist/index.js
CHANGED
|
@@ -1481,6 +1481,34 @@ function parseSentinel(text) {
|
|
|
1481
1481
|
}
|
|
1482
1482
|
return { name: match[1], remainder: match[2] };
|
|
1483
1483
|
}
|
|
1484
|
+
function sentinelParams(text) {
|
|
1485
|
+
const parsed = parseSentinel(text);
|
|
1486
|
+
if (!parsed) {
|
|
1487
|
+
return {};
|
|
1488
|
+
}
|
|
1489
|
+
const firstLine = parsed.remainder.split("\n")[0].trim();
|
|
1490
|
+
if (!firstLine) {
|
|
1491
|
+
return {};
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
const value = JSON.parse(firstLine);
|
|
1495
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1496
|
+
} catch {
|
|
1497
|
+
return {};
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
function setSentinelParams(text, patch) {
|
|
1501
|
+
const parsed = parseSentinel(text);
|
|
1502
|
+
if (!parsed) {
|
|
1503
|
+
return text;
|
|
1504
|
+
}
|
|
1505
|
+
const newlineAt = parsed.remainder.indexOf("\n");
|
|
1506
|
+
const body = newlineAt === -1 ? "" : parsed.remainder.slice(newlineAt + 1);
|
|
1507
|
+
const params = { ...sentinelParams(text), ...patch };
|
|
1508
|
+
const line = `${sentinel(parsed.name)}${JSON.stringify(params)}`;
|
|
1509
|
+
return body ? `${line}
|
|
1510
|
+
${body}` : line;
|
|
1511
|
+
}
|
|
1484
1512
|
function stripSentinelLine(text) {
|
|
1485
1513
|
return text.replace(/^@@automated::[^@]*@@[^\n]*\n?/, "");
|
|
1486
1514
|
}
|
|
@@ -2343,6 +2371,20 @@ var init_cleanMessages = __esm({
|
|
|
2343
2371
|
});
|
|
2344
2372
|
|
|
2345
2373
|
// src/recording.ts
|
|
2374
|
+
function collectRecordings(messages) {
|
|
2375
|
+
const recordings = [];
|
|
2376
|
+
for (const msg of messages) {
|
|
2377
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
2378
|
+
continue;
|
|
2379
|
+
}
|
|
2380
|
+
for (const block of msg.content) {
|
|
2381
|
+
if (block.type === "tool" && block.recording) {
|
|
2382
|
+
recordings.push(block.recording);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return recordings;
|
|
2387
|
+
}
|
|
2346
2388
|
function liftRecording(result) {
|
|
2347
2389
|
if (!result.includes('"recording"')) {
|
|
2348
2390
|
return { result };
|
|
@@ -2400,6 +2442,18 @@ function capToolResult(result, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
2400
2442
|
|
|
2401
2443
|
(tool result truncated at ${(maxBytes / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 too large to keep in context. Narrow the call (select fewer fields, paginate, or query a subset) instead of fetching everything.)`;
|
|
2402
2444
|
}
|
|
2445
|
+
function attachSubAgentTranscript(block, messages) {
|
|
2446
|
+
if (!block.recordings) {
|
|
2447
|
+
const recordings = collectRecordings(messages);
|
|
2448
|
+
if (recordings.length > 0) {
|
|
2449
|
+
block.recordings = recordings;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
block.subAgentMessages = capSubAgentTranscript(messages);
|
|
2453
|
+
}
|
|
2454
|
+
function dropToolResultMessages(messages) {
|
|
2455
|
+
return messages.filter((m) => !(m.role === "user" && m.toolCallId));
|
|
2456
|
+
}
|
|
2403
2457
|
function capSubAgentTranscript(messages) {
|
|
2404
2458
|
for (const msg of messages) {
|
|
2405
2459
|
capMessageForHistory(msg, MAX_SUBAGENT_RESULT_BYTES);
|
|
@@ -2444,7 +2498,7 @@ function capMessageForHistory(msg, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
2444
2498
|
);
|
|
2445
2499
|
}
|
|
2446
2500
|
if (Array.isArray(block.subAgentMessages)) {
|
|
2447
|
-
block
|
|
2501
|
+
attachSubAgentTranscript(block, block.subAgentMessages);
|
|
2448
2502
|
}
|
|
2449
2503
|
}
|
|
2450
2504
|
} else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
|
|
@@ -3519,11 +3573,23 @@ var init_bash = __esm({
|
|
|
3519
3573
|
const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
|
|
3520
3574
|
const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
|
|
3521
3575
|
return new Promise((resolve4) => {
|
|
3522
|
-
const child = spawn2("
|
|
3576
|
+
const child = spawn2("bash", ["-c", input.command], {
|
|
3523
3577
|
// Pinned rather than inherited. `undefined` here means "wherever the
|
|
3524
3578
|
// process happens to be", which is the project root only by luck.
|
|
3525
3579
|
cwd: input.cwd ? path7.resolve(PROJECT_ROOT, input.cwd) : PROJECT_ROOT,
|
|
3526
|
-
|
|
3580
|
+
// Output is rendered in a terminal view in the IDE, so ask tools for
|
|
3581
|
+
// color. The devbox image sets none of these — it declares only
|
|
3582
|
+
// WORKSPACE_DIR, LANG, LC_ALL, PIP_BREAK_SYSTEM_PACKAGES,
|
|
3583
|
+
// NPM_CONFIG_PREFIX and PATH — and a container gets no TERM unless
|
|
3584
|
+
// something sets it, so without TERM anything driving terminfo assumes
|
|
3585
|
+
// a dumb terminal. Same trio the sandbox's pty handler sets, for the
|
|
3586
|
+
// same reason; FORCE_COLOR alone only reaches the Node/chalk ecosystem.
|
|
3587
|
+
env: {
|
|
3588
|
+
...process.env,
|
|
3589
|
+
TERM: "xterm-256color",
|
|
3590
|
+
CLICOLOR: "1",
|
|
3591
|
+
FORCE_COLOR: "1"
|
|
3592
|
+
}
|
|
3527
3593
|
});
|
|
3528
3594
|
let output = "";
|
|
3529
3595
|
child.stdout.on("data", (chunk) => {
|
|
@@ -4654,13 +4720,17 @@ var init_browserLock = __esm({
|
|
|
4654
4720
|
});
|
|
4655
4721
|
|
|
4656
4722
|
// src/toolRegistry.ts
|
|
4657
|
-
|
|
4723
|
+
function cancelledToolResult(signal) {
|
|
4724
|
+
return signal?.reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT;
|
|
4725
|
+
}
|
|
4726
|
+
var log7, USER_CANCELLED_RESULT, ENV_INTERRUPTED_RESULT, ToolRegistry;
|
|
4658
4727
|
var init_toolRegistry = __esm({
|
|
4659
4728
|
"src/toolRegistry.ts"() {
|
|
4660
4729
|
"use strict";
|
|
4661
4730
|
init_logger();
|
|
4662
4731
|
log7 = createLogger("tool-registry");
|
|
4663
4732
|
USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
4733
|
+
ENV_INTERRUPTED_RESULT = "[INTERRUPTED] The environment shut down while this tool was running. No user action was involved \u2014 pick up from here.";
|
|
4664
4734
|
ToolRegistry = class {
|
|
4665
4735
|
entries = /* @__PURE__ */ new Map();
|
|
4666
4736
|
onEvent;
|
|
@@ -4916,7 +4986,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4916
4986
|
messages: thisInvocation()
|
|
4917
4987
|
};
|
|
4918
4988
|
}
|
|
4919
|
-
return { text:
|
|
4989
|
+
return { text: cancelledToolResult(signal), messages: thisInvocation() };
|
|
4920
4990
|
}
|
|
4921
4991
|
let lastToolResult = "";
|
|
4922
4992
|
let watchedBlocks = [];
|
|
@@ -5152,7 +5222,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
5152
5222
|
if (signal?.aborted) {
|
|
5153
5223
|
return {
|
|
5154
5224
|
id: tc.id,
|
|
5155
|
-
result:
|
|
5225
|
+
result: cancelledToolResult(signal),
|
|
5156
5226
|
isError: true
|
|
5157
5227
|
};
|
|
5158
5228
|
}
|
|
@@ -5269,7 +5339,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
5269
5339
|
}
|
|
5270
5340
|
const innerMsgs = subAgentMessages.get(r.id);
|
|
5271
5341
|
if (innerMsgs) {
|
|
5272
|
-
block
|
|
5342
|
+
attachSubAgentTranscript(block, innerMsgs);
|
|
5273
5343
|
}
|
|
5274
5344
|
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
5275
5345
|
try {
|
|
@@ -5855,13 +5925,12 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5855
5925
|
},
|
|
5856
5926
|
toolRegistry: context.toolRegistry
|
|
5857
5927
|
});
|
|
5858
|
-
context.subAgentMessages?.set(
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
(m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(
|
|
5862
|
-
(b) => b.type === "tool" && b.name === "browserCommand" && !!b.recording
|
|
5863
|
-
)
|
|
5928
|
+
context.subAgentMessages?.set(
|
|
5929
|
+
context.toolCallId,
|
|
5930
|
+
dropToolResultMessages(result.messages)
|
|
5864
5931
|
);
|
|
5932
|
+
const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
|
|
5933
|
+
const recorded = collectRecordings(result.messages).length > 0;
|
|
5865
5934
|
return {
|
|
5866
5935
|
text: result.text,
|
|
5867
5936
|
recorded,
|
|
@@ -5877,6 +5946,8 @@ var init_browserAutomation = __esm({
|
|
|
5877
5946
|
"use strict";
|
|
5878
5947
|
init_tools10();
|
|
5879
5948
|
init_runner();
|
|
5949
|
+
init_historyLimits();
|
|
5950
|
+
init_recording();
|
|
5880
5951
|
init_tools2();
|
|
5881
5952
|
init_tools();
|
|
5882
5953
|
init_readSpec();
|
|
@@ -6046,6 +6117,88 @@ var init_screenshot2 = __esm({
|
|
|
6046
6117
|
}
|
|
6047
6118
|
});
|
|
6048
6119
|
|
|
6120
|
+
// src/tools/common/scrapeWebUrl.ts
|
|
6121
|
+
async function fetchWebPage(url, opts) {
|
|
6122
|
+
const raw = await runMindstudioCli(
|
|
6123
|
+
[
|
|
6124
|
+
"scrape-url",
|
|
6125
|
+
"--url",
|
|
6126
|
+
url,
|
|
6127
|
+
"--page-options",
|
|
6128
|
+
JSON.stringify({ onlyMainContent: true, screenshot: opts.screenshot })
|
|
6129
|
+
],
|
|
6130
|
+
{
|
|
6131
|
+
onLog: opts.onLog,
|
|
6132
|
+
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
6133
|
+
caller: opts.caller
|
|
6134
|
+
}
|
|
6135
|
+
);
|
|
6136
|
+
return shapeFetchResult(url, raw);
|
|
6137
|
+
}
|
|
6138
|
+
function shapeFetchResult(url, raw) {
|
|
6139
|
+
const body = raw.endsWith(TRUNCATED_SUFFIX) ? raw.slice(0, -TRUNCATED_SUFFIX.length) : raw;
|
|
6140
|
+
let envelope;
|
|
6141
|
+
try {
|
|
6142
|
+
envelope = JSON.parse(body);
|
|
6143
|
+
} catch {
|
|
6144
|
+
return raw;
|
|
6145
|
+
}
|
|
6146
|
+
if (!envelope || typeof envelope !== "object" || typeof envelope.content !== "string") {
|
|
6147
|
+
return raw;
|
|
6148
|
+
}
|
|
6149
|
+
const shot = envelope.screenshot;
|
|
6150
|
+
const screenshot = typeof shot === "string" ? shot : Array.isArray(shot) && typeof shot[0] === "string" ? shot[0] : void 0;
|
|
6151
|
+
return JSON.stringify({
|
|
6152
|
+
url,
|
|
6153
|
+
...screenshot ? { screenshot } : {},
|
|
6154
|
+
content: capContent(envelope.content)
|
|
6155
|
+
});
|
|
6156
|
+
}
|
|
6157
|
+
function capContent(content) {
|
|
6158
|
+
const total = Buffer.byteLength(content, "utf-8");
|
|
6159
|
+
if (total <= CONTENT_BUDGET_BYTES) {
|
|
6160
|
+
return content;
|
|
6161
|
+
}
|
|
6162
|
+
const head = Buffer.from(content, "utf-8").subarray(0, CONTENT_BUDGET_BYTES).toString("utf-8");
|
|
6163
|
+
return head + `
|
|
6164
|
+
|
|
6165
|
+
[page content truncated at ${(CONTENT_BUDGET_BYTES / 1024).toFixed(0)}KB of ${(total / 1024).toFixed(0)}KB \u2014 narrow what you need or fetch a sub-page]`;
|
|
6166
|
+
}
|
|
6167
|
+
var CONTENT_BUDGET_BYTES, TRUNCATED_SUFFIX, scrapeWebUrlTool;
|
|
6168
|
+
var init_scrapeWebUrl = __esm({
|
|
6169
|
+
"src/tools/common/scrapeWebUrl.ts"() {
|
|
6170
|
+
"use strict";
|
|
6171
|
+
init_runMindstudioCli();
|
|
6172
|
+
init_runCli();
|
|
6173
|
+
init_historyLimits();
|
|
6174
|
+
CONTENT_BUDGET_BYTES = MAX_TOOL_RESULT_BYTES - 4 * 1024;
|
|
6175
|
+
TRUNCATED_SUFFIX = "\n\n[output truncated]";
|
|
6176
|
+
scrapeWebUrlTool = {
|
|
6177
|
+
definition: {
|
|
6178
|
+
name: "scrapeWebUrl",
|
|
6179
|
+
description: "Fetch a web page. Returns its main content as markdown plus `screenshot`, a full-page capture the person sees in the editor. Read the markdown; analyze the screenshot with analyzeImage only when the visual design matters. Use this when you need to fetch or analyze content from a website.",
|
|
6180
|
+
inputSchema: {
|
|
6181
|
+
type: "object",
|
|
6182
|
+
properties: {
|
|
6183
|
+
url: {
|
|
6184
|
+
type: "string",
|
|
6185
|
+
description: "The URL to fetch."
|
|
6186
|
+
}
|
|
6187
|
+
},
|
|
6188
|
+
required: ["url"]
|
|
6189
|
+
}
|
|
6190
|
+
},
|
|
6191
|
+
async execute(input, context) {
|
|
6192
|
+
return fetchWebPage(input.url, {
|
|
6193
|
+
screenshot: true,
|
|
6194
|
+
caller: "parent",
|
|
6195
|
+
onLog: context?.onLog
|
|
6196
|
+
});
|
|
6197
|
+
}
|
|
6198
|
+
};
|
|
6199
|
+
}
|
|
6200
|
+
});
|
|
6201
|
+
|
|
6049
6202
|
// src/subagents/research/tools.ts
|
|
6050
6203
|
async function executeSearchGoogle(input, onLog, caller) {
|
|
6051
6204
|
const fetchTopN = Math.max(0, Math.round(Number(input.fetchTopN) || 0));
|
|
@@ -6067,22 +6220,6 @@ async function executeSearchGoogle(input, onLog, caller) {
|
|
|
6067
6220
|
}
|
|
6068
6221
|
);
|
|
6069
6222
|
}
|
|
6070
|
-
async function executeScrapeWebUrl(input, onLog, caller) {
|
|
6071
|
-
return runMindstudioCli(
|
|
6072
|
-
[
|
|
6073
|
-
"scrape-url",
|
|
6074
|
-
"--url",
|
|
6075
|
-
String(input.url),
|
|
6076
|
-
"--page-options",
|
|
6077
|
-
JSON.stringify({ onlyMainContent: true })
|
|
6078
|
-
],
|
|
6079
|
-
{
|
|
6080
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
6081
|
-
onLog,
|
|
6082
|
-
caller
|
|
6083
|
-
}
|
|
6084
|
-
);
|
|
6085
|
-
}
|
|
6086
6223
|
var searchGoogleDefinition, scrapeWebUrlDefinition, RESEARCH_TOOLS;
|
|
6087
6224
|
var init_tools3 = __esm({
|
|
6088
6225
|
"src/subagents/research/tools.ts"() {
|
|
@@ -6155,7 +6292,11 @@ async function runResearch(task, context) {
|
|
|
6155
6292
|
return executeSearchGoogle(toolInput, childCtx.onLog, "research");
|
|
6156
6293
|
}
|
|
6157
6294
|
if (name === "scrapeWebUrl") {
|
|
6158
|
-
return
|
|
6295
|
+
return fetchWebPage(String(toolInput.url), {
|
|
6296
|
+
screenshot: false,
|
|
6297
|
+
caller: "research",
|
|
6298
|
+
onLog: childCtx.onLog
|
|
6299
|
+
});
|
|
6159
6300
|
}
|
|
6160
6301
|
return executeTool(name, toolInput, childCtx);
|
|
6161
6302
|
},
|
|
@@ -6180,6 +6321,7 @@ var init_research = __esm({
|
|
|
6180
6321
|
init_runner();
|
|
6181
6322
|
init_context();
|
|
6182
6323
|
init_tools10();
|
|
6324
|
+
init_scrapeWebUrl();
|
|
6183
6325
|
init_tools3();
|
|
6184
6326
|
init_surfaces();
|
|
6185
6327
|
BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
|
|
@@ -6215,27 +6357,20 @@ __export(scrapeWebUrl_exports, {
|
|
|
6215
6357
|
execute: () => execute
|
|
6216
6358
|
});
|
|
6217
6359
|
async function execute(input, onLog) {
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
input.url,
|
|
6224
|
-
"--page-options",
|
|
6225
|
-
JSON.stringify(pageOptions)
|
|
6226
|
-
],
|
|
6227
|
-
{ onLog, caller: "designExpert", maxBuffer: SCRAPE_MAX_BUFFER }
|
|
6228
|
-
);
|
|
6360
|
+
return fetchWebPage(String(input.url), {
|
|
6361
|
+
screenshot: true,
|
|
6362
|
+
caller: "designExpert",
|
|
6363
|
+
onLog
|
|
6364
|
+
});
|
|
6229
6365
|
}
|
|
6230
6366
|
var definition;
|
|
6231
|
-
var
|
|
6367
|
+
var init_scrapeWebUrl2 = __esm({
|
|
6232
6368
|
"src/subagents/designExpert/tools/scrapeWebUrl.ts"() {
|
|
6233
6369
|
"use strict";
|
|
6234
|
-
|
|
6235
|
-
init_runCli();
|
|
6370
|
+
init_scrapeWebUrl();
|
|
6236
6371
|
definition = {
|
|
6237
6372
|
name: "scrapeWebUrl",
|
|
6238
|
-
description: "Fetch
|
|
6373
|
+
description: "Fetch a web page as markdown, plus `screenshot`, a full-page capture you can analyze. Use for reading a specific URL \u2014 a site the user referenced, a brand to match, a page the researcher cited that you want in full.",
|
|
6239
6374
|
inputSchema: {
|
|
6240
6375
|
type: "object",
|
|
6241
6376
|
properties: {
|
|
@@ -7380,7 +7515,7 @@ var init_tools5 = __esm({
|
|
|
7380
7515
|
init_tools10();
|
|
7381
7516
|
init_tools();
|
|
7382
7517
|
init_research();
|
|
7383
|
-
|
|
7518
|
+
init_scrapeWebUrl2();
|
|
7384
7519
|
init_analyzeDesign();
|
|
7385
7520
|
init_analyzeImage2();
|
|
7386
7521
|
init_generateImages();
|
|
@@ -8817,58 +8952,6 @@ var init_reviewExistingProject = __esm({
|
|
|
8817
8952
|
}
|
|
8818
8953
|
});
|
|
8819
8954
|
|
|
8820
|
-
// src/tools/common/scrapeWebUrl.ts
|
|
8821
|
-
var scrapeWebUrlTool;
|
|
8822
|
-
var init_scrapeWebUrl2 = __esm({
|
|
8823
|
-
"src/tools/common/scrapeWebUrl.ts"() {
|
|
8824
|
-
"use strict";
|
|
8825
|
-
init_runMindstudioCli();
|
|
8826
|
-
init_runCli();
|
|
8827
|
-
scrapeWebUrlTool = {
|
|
8828
|
-
definition: {
|
|
8829
|
-
name: "scrapeWebUrl",
|
|
8830
|
-
description: "Scrape the content of a web page. Returns the HTML of the page as markdown text. Optionally capture a screenshot if you need see the visual design. Use this when you need to fetch or analyze content from a website",
|
|
8831
|
-
inputSchema: {
|
|
8832
|
-
type: "object",
|
|
8833
|
-
properties: {
|
|
8834
|
-
url: {
|
|
8835
|
-
type: "string",
|
|
8836
|
-
description: "The URL to fetch."
|
|
8837
|
-
},
|
|
8838
|
-
screenshot: {
|
|
8839
|
-
type: "boolean",
|
|
8840
|
-
description: "Capture a screenshot of the page in addition to the text content. Adds latency; only use when you need to see the visual design."
|
|
8841
|
-
}
|
|
8842
|
-
},
|
|
8843
|
-
required: ["url"]
|
|
8844
|
-
}
|
|
8845
|
-
},
|
|
8846
|
-
async execute(input, context) {
|
|
8847
|
-
const url = input.url;
|
|
8848
|
-
const screenshot = input.screenshot;
|
|
8849
|
-
const pageOptions = { onlyMainContent: true };
|
|
8850
|
-
if (screenshot) {
|
|
8851
|
-
pageOptions.screenshot = true;
|
|
8852
|
-
}
|
|
8853
|
-
return runMindstudioCli(
|
|
8854
|
-
[
|
|
8855
|
-
"scrape-url",
|
|
8856
|
-
"--url",
|
|
8857
|
-
url,
|
|
8858
|
-
"--page-options",
|
|
8859
|
-
JSON.stringify(pageOptions)
|
|
8860
|
-
],
|
|
8861
|
-
{
|
|
8862
|
-
onLog: context?.onLog,
|
|
8863
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
8864
|
-
caller: "parent"
|
|
8865
|
-
}
|
|
8866
|
-
);
|
|
8867
|
-
}
|
|
8868
|
-
};
|
|
8869
|
-
}
|
|
8870
|
-
});
|
|
8871
|
-
|
|
8872
8955
|
// src/tools/index.ts
|
|
8873
8956
|
function deriveContext(parent, toolCallId, onLog) {
|
|
8874
8957
|
return { ...parent, toolCallId, onLog };
|
|
@@ -8927,7 +9010,7 @@ var init_tools10 = __esm({
|
|
|
8927
9010
|
init_specSync();
|
|
8928
9011
|
init_research();
|
|
8929
9012
|
init_reviewExistingProject();
|
|
8930
|
-
|
|
9013
|
+
init_scrapeWebUrl();
|
|
8931
9014
|
init_writeBuildOverview();
|
|
8932
9015
|
ALL_TOOLS = [
|
|
8933
9016
|
// Common
|
|
@@ -9167,17 +9250,11 @@ function resolveAction(text) {
|
|
|
9167
9250
|
if (!parsed) {
|
|
9168
9251
|
return null;
|
|
9169
9252
|
}
|
|
9170
|
-
const { name: triggerName
|
|
9253
|
+
const { name: triggerName } = parsed;
|
|
9171
9254
|
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
9172
9255
|
return null;
|
|
9173
9256
|
}
|
|
9174
|
-
|
|
9175
|
-
if (remainder) {
|
|
9176
|
-
try {
|
|
9177
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
9178
|
-
} catch {
|
|
9179
|
-
}
|
|
9180
|
-
}
|
|
9257
|
+
const params = sentinelParams(text);
|
|
9181
9258
|
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
9182
9259
|
let next;
|
|
9183
9260
|
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
@@ -9192,8 +9269,16 @@ function resolveAction(text) {
|
|
|
9192
9269
|
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
9193
9270
|
body = body.replaceAll(`{{${key}}}`, str);
|
|
9194
9271
|
}
|
|
9272
|
+
const resumed = params.resumed === true;
|
|
9273
|
+
if (resumed) {
|
|
9274
|
+
body = `${readAsset("automatedActions", "_resumed.md")}
|
|
9275
|
+
|
|
9276
|
+
${body}`;
|
|
9277
|
+
}
|
|
9195
9278
|
return {
|
|
9196
|
-
message: automatedMessage(triggerName, body),
|
|
9279
|
+
message: resumed ? setSentinelParams(automatedMessage(triggerName, body), {
|
|
9280
|
+
resumed: true
|
|
9281
|
+
}) : automatedMessage(triggerName, body),
|
|
9197
9282
|
next
|
|
9198
9283
|
};
|
|
9199
9284
|
}
|
|
@@ -9938,7 +10023,10 @@ async function runTurn(params) {
|
|
|
9938
10023
|
const automated = parseSentinel(entry.text);
|
|
9939
10024
|
if (automated) {
|
|
9940
10025
|
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
9941
|
-
|
|
10026
|
+
const resumed = sentinelParams(entry.text).resumed === true;
|
|
10027
|
+
parts.push(
|
|
10028
|
+
`Automated action: ${automated.name}${resumed ? " (resuming interrupted work)" : ""}`
|
|
10029
|
+
);
|
|
9942
10030
|
hasUserSignal = true;
|
|
9943
10031
|
}
|
|
9944
10032
|
} else if (entry.text) {
|
|
@@ -10327,7 +10415,11 @@ async function runTurn(params) {
|
|
|
10327
10415
|
const results = await Promise.all(
|
|
10328
10416
|
toolCalls.map(async (tc) => {
|
|
10329
10417
|
if (signal?.aborted) {
|
|
10330
|
-
return {
|
|
10418
|
+
return {
|
|
10419
|
+
id: tc.id,
|
|
10420
|
+
result: cancelledToolResult(signal),
|
|
10421
|
+
isError: true
|
|
10422
|
+
};
|
|
10331
10423
|
}
|
|
10332
10424
|
const toolStart = Date.now();
|
|
10333
10425
|
let settle;
|
|
@@ -10346,7 +10438,7 @@ async function runTurn(params) {
|
|
|
10346
10438
|
};
|
|
10347
10439
|
const cascadeAbort = () => {
|
|
10348
10440
|
toolAbort.abort();
|
|
10349
|
-
safeSettle(
|
|
10441
|
+
safeSettle(cancelledToolResult(signal), true);
|
|
10350
10442
|
};
|
|
10351
10443
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
10352
10444
|
const run = async (input) => {
|
|
@@ -10455,7 +10547,7 @@ async function runTurn(params) {
|
|
|
10455
10547
|
block.completedAt = Date.now();
|
|
10456
10548
|
const msgs = subAgentMessages.get(r.id);
|
|
10457
10549
|
if (msgs) {
|
|
10458
|
-
block
|
|
10550
|
+
attachSubAgentTranscript(block, msgs);
|
|
10459
10551
|
}
|
|
10460
10552
|
}
|
|
10461
10553
|
}
|
|
@@ -11019,6 +11111,15 @@ function holdRestoredUserItems(items) {
|
|
|
11019
11111
|
(item) => item.source === "user" ? { ...item, held: true } : item
|
|
11020
11112
|
);
|
|
11021
11113
|
}
|
|
11114
|
+
function releaseShutdownHolds(items) {
|
|
11115
|
+
return items.map((item) => {
|
|
11116
|
+
if (item.heldBy !== "shutdown" || item.source === "user") {
|
|
11117
|
+
return item;
|
|
11118
|
+
}
|
|
11119
|
+
const { held: _held, heldBy: _heldBy, ...released } = item;
|
|
11120
|
+
return released;
|
|
11121
|
+
});
|
|
11122
|
+
}
|
|
11022
11123
|
var MessageQueue;
|
|
11023
11124
|
var init_messageQueue = __esm({
|
|
11024
11125
|
"src/headless/messageQueue.ts"() {
|
|
@@ -11094,16 +11195,27 @@ var init_messageQueue = __esm({
|
|
|
11094
11195
|
/**
|
|
11095
11196
|
* Mark matching items `held` — waiting on the user rather than on the agent.
|
|
11096
11197
|
* Fires onChange only if something changed. Returns the held items.
|
|
11198
|
+
*
|
|
11199
|
+
* `heldBy` is stamped only on items this call actually transitions. An item
|
|
11200
|
+
* that was ALREADY held keeps whatever hold it had: otherwise a shutdown
|
|
11201
|
+
* arriving after a user's Stop — a box reaped while they were away from the
|
|
11202
|
+
* tab — would retag their deliberate pause as environmental and the next
|
|
11203
|
+
* boot would auto-run the very build they stopped.
|
|
11097
11204
|
*/
|
|
11098
|
-
holdWhere(predicate) {
|
|
11205
|
+
holdWhere(predicate, heldBy) {
|
|
11099
11206
|
const held = [];
|
|
11100
11207
|
let changed = false;
|
|
11101
11208
|
for (const item of this.items) {
|
|
11102
11209
|
if (!predicate(item)) {
|
|
11103
11210
|
continue;
|
|
11104
11211
|
}
|
|
11105
|
-
|
|
11106
|
-
|
|
11212
|
+
if (!item.held) {
|
|
11213
|
+
changed = true;
|
|
11214
|
+
item.held = true;
|
|
11215
|
+
if (heldBy) {
|
|
11216
|
+
item.heldBy = heldBy;
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11107
11219
|
held.push(item);
|
|
11108
11220
|
}
|
|
11109
11221
|
if (changed) {
|
|
@@ -11151,6 +11263,7 @@ var init_messageQueue = __esm({
|
|
|
11151
11263
|
const [item] = this.items.splice(idx, 1);
|
|
11152
11264
|
item.delivery = "asap";
|
|
11153
11265
|
delete item.held;
|
|
11266
|
+
delete item.heldBy;
|
|
11154
11267
|
this.items.unshift(item);
|
|
11155
11268
|
this.onChange?.();
|
|
11156
11269
|
return item;
|
|
@@ -11174,6 +11287,7 @@ var init_messageQueue = __esm({
|
|
|
11174
11287
|
continue;
|
|
11175
11288
|
}
|
|
11176
11289
|
delete item.held;
|
|
11290
|
+
delete item.heldBy;
|
|
11177
11291
|
released.push(item);
|
|
11178
11292
|
}
|
|
11179
11293
|
const back = this.items.filter(defer);
|
|
@@ -11331,10 +11445,13 @@ var init_headless = __esm({
|
|
|
11331
11445
|
});
|
|
11332
11446
|
await initOrgContext(this.config);
|
|
11333
11447
|
const resumed = loadSession(this.state);
|
|
11334
|
-
this.queue = new MessageQueue(
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11448
|
+
this.queue = new MessageQueue(
|
|
11449
|
+
releaseShutdownHolds(holdRestoredUserItems(loadQueue())),
|
|
11450
|
+
() => {
|
|
11451
|
+
this.persistStats();
|
|
11452
|
+
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
11453
|
+
}
|
|
11454
|
+
);
|
|
11338
11455
|
this.passivePen = loadPassiveResults();
|
|
11339
11456
|
this.persistStats();
|
|
11340
11457
|
if (resumed) {
|
|
@@ -11456,6 +11573,11 @@ var init_headless = __esm({
|
|
|
11456
11573
|
this.emit("ready");
|
|
11457
11574
|
}
|
|
11458
11575
|
shutdown = () => {
|
|
11576
|
+
try {
|
|
11577
|
+
this.handleCancel("shutdown");
|
|
11578
|
+
} catch (err) {
|
|
11579
|
+
log17.warn("Shutdown cancel failed", { error: err?.message });
|
|
11580
|
+
}
|
|
11459
11581
|
this.emit("stopping");
|
|
11460
11582
|
this.emit("stopped");
|
|
11461
11583
|
process.exit(0);
|
|
@@ -12393,6 +12515,19 @@ var init_headless = __esm({
|
|
|
12393
12515
|
* pipeline where it stopped. Holding only the remainder would resume one step
|
|
12394
12516
|
* PAST the interruption, polishing and finalizing half-built code.
|
|
12395
12517
|
*
|
|
12518
|
+
* That re-queued step is marked `resumed`, which is what stops it being a
|
|
12519
|
+
* verbatim replay. The message the user sends to un-pause a build is usually
|
|
12520
|
+
* the rest of that build ("try again, I fixed the adapter") and Remy does the
|
|
12521
|
+
* work inside that turn — so re-delivering the original "build everything
|
|
12522
|
+
* now" afterwards had it rebuild an app it had just finished.
|
|
12523
|
+
*
|
|
12524
|
+
* `reason: 'shutdown'` is the environment taking the agent down rather than a
|
|
12525
|
+
* person pressing Stop. It pauses identically — nothing may start a turn
|
|
12526
|
+
* while the workspace is being tarred — but tags the chain steps it holds so
|
|
12527
|
+
* the next boot releases them and the pipeline carries on by itself. Called
|
|
12528
|
+
* from the stdin `cancel` (the sandbox's pre-destroy quiesce) and from our own
|
|
12529
|
+
* SIGTERM handler, whichever gets there first.
|
|
12530
|
+
*
|
|
12396
12531
|
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
12397
12532
|
* message and outlives the turn that started it, so leaving it running means
|
|
12398
12533
|
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
@@ -12403,14 +12538,16 @@ var init_headless = __esm({
|
|
|
12403
12538
|
* `{cancelled, absorbed:true}` terminal. Only items still sitting in the
|
|
12404
12539
|
* queue survive.
|
|
12405
12540
|
*/
|
|
12406
|
-
handleCancel() {
|
|
12541
|
+
handleCancel(reason) {
|
|
12407
12542
|
if (this.currentAbort) {
|
|
12408
|
-
this.currentAbort.abort();
|
|
12543
|
+
this.currentAbort.abort(reason);
|
|
12409
12544
|
}
|
|
12410
12545
|
const cancelledCompaction = cancelInflightCompaction();
|
|
12411
12546
|
for (const [id, pending2] of this.pendingTools) {
|
|
12412
12547
|
clearTimeout(pending2.timeout);
|
|
12413
|
-
pending2.resolve(
|
|
12548
|
+
pending2.resolve(
|
|
12549
|
+
reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT
|
|
12550
|
+
);
|
|
12414
12551
|
this.pendingTools.delete(id);
|
|
12415
12552
|
}
|
|
12416
12553
|
const flushed = this.queue.removeWhere(
|
|
@@ -12421,7 +12558,13 @@ var init_headless = __esm({
|
|
|
12421
12558
|
this.queue.unshift({
|
|
12422
12559
|
command: {
|
|
12423
12560
|
action: "message",
|
|
12424
|
-
|
|
12561
|
+
// Marked so it comes back as a resumption rather than a replay: the
|
|
12562
|
+
// step already ran once, and whatever the user does between here and
|
|
12563
|
+
// its re-delivery may well be the rest of it. resolveAction turns
|
|
12564
|
+
// this param into a preamble, and both the chat row and the queue
|
|
12565
|
+
// card label the step off it. Merged, so a step interrupted twice
|
|
12566
|
+
// keeps its own params and re-marking stays idempotent.
|
|
12567
|
+
text: setSentinelParams(step.text, { resumed: true }),
|
|
12425
12568
|
onboardingState: step.onboardingState,
|
|
12426
12569
|
// Fresh id: the original command's terminal has already gone out as
|
|
12427
12570
|
// cancelled, and one command gets exactly one `completed`.
|
|
@@ -12429,13 +12572,18 @@ var init_headless = __esm({
|
|
|
12429
12572
|
},
|
|
12430
12573
|
source: "chain",
|
|
12431
12574
|
enqueuedAt: Date.now(),
|
|
12432
|
-
held: true
|
|
12575
|
+
held: true,
|
|
12576
|
+
...reason === "shutdown" && { heldBy: reason }
|
|
12433
12577
|
});
|
|
12434
12578
|
this.currentChainStep = null;
|
|
12435
12579
|
}
|
|
12436
|
-
const held =
|
|
12437
|
-
|
|
12438
|
-
|
|
12580
|
+
const held = [
|
|
12581
|
+
...this.queue.holdWhere(
|
|
12582
|
+
(item) => item.source === "chain",
|
|
12583
|
+
reason === "shutdown" ? reason : void 0
|
|
12584
|
+
),
|
|
12585
|
+
...this.queue.holdWhere((item) => item.source === "user")
|
|
12586
|
+
];
|
|
12439
12587
|
return {
|
|
12440
12588
|
flushed,
|
|
12441
12589
|
held,
|
|
@@ -12447,9 +12595,10 @@ var init_headless = __esm({
|
|
|
12447
12595
|
* Remove pending queued messages: all user messages (no id), or a single item
|
|
12448
12596
|
* by id. Does not affect the in-flight turn (use `cancel` for that).
|
|
12449
12597
|
*
|
|
12450
|
-
* Held chain items — a paused pipeline — are removable only by explicit id
|
|
12451
|
-
*
|
|
12452
|
-
*
|
|
12598
|
+
* Held chain items — a paused pipeline — are removable only by explicit id:
|
|
12599
|
+
* the queue card's Discard sends one call per step, and its per-step X sends
|
|
12600
|
+
* one. The id-less form is the card's "Clear", which is about the user's own
|
|
12601
|
+
* messages and shouldn't get to decide the pipeline's fate. A DELIVERABLE
|
|
12453
12602
|
* chain item is never removable: that's live pipeline work. (The step
|
|
12454
12603
|
* actually running isn't in the queue at all — drainQueueLoop takes it out
|
|
12455
12604
|
* before running it.)
|
|
@@ -12555,7 +12704,9 @@ var init_headless = __esm({
|
|
|
12555
12704
|
return;
|
|
12556
12705
|
}
|
|
12557
12706
|
if (action === "cancel") {
|
|
12558
|
-
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
12707
|
+
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
12708
|
+
parsed.reason === "shutdown" ? "shutdown" : void 0
|
|
12709
|
+
);
|
|
12559
12710
|
this.emit(
|
|
12560
12711
|
"completed",
|
|
12561
12712
|
{
|