@mindstudio-ai/remy 0.1.323 → 0.1.325
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 +251 -115
- package/dist/index.js +268 -129
- package/dist/prompt/skills/dataSources.md +1 -0
- 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
|
}
|
|
@@ -3386,6 +3414,10 @@ function acquireBrowserLock() {
|
|
|
3386
3414
|
// src/toolRegistry.ts
|
|
3387
3415
|
var log6 = createLogger("tool-registry");
|
|
3388
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
|
+
}
|
|
3389
3421
|
var ToolRegistry = class {
|
|
3390
3422
|
entries = /* @__PURE__ */ new Map();
|
|
3391
3423
|
onEvent;
|
|
@@ -3460,6 +3492,20 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
3460
3492
|
};
|
|
3461
3493
|
|
|
3462
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
|
+
}
|
|
3463
3509
|
function liftRecording(result) {
|
|
3464
3510
|
if (!result.includes('"recording"')) {
|
|
3465
3511
|
return { result };
|
|
@@ -3515,6 +3561,18 @@ function capToolResult(result, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
3515
3561
|
}
|
|
3516
3562
|
var MAX_SUBAGENT_RESULT_BYTES = 32 * 1024;
|
|
3517
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
|
+
}
|
|
3518
3576
|
function capSubAgentTranscript(messages) {
|
|
3519
3577
|
for (const msg of messages) {
|
|
3520
3578
|
capMessageForHistory(msg, MAX_SUBAGENT_RESULT_BYTES);
|
|
@@ -3559,7 +3617,7 @@ function capMessageForHistory(msg, maxBytes = MAX_TOOL_RESULT_BYTES) {
|
|
|
3559
3617
|
);
|
|
3560
3618
|
}
|
|
3561
3619
|
if (Array.isArray(block.subAgentMessages)) {
|
|
3562
|
-
block
|
|
3620
|
+
attachSubAgentTranscript(block, block.subAgentMessages);
|
|
3563
3621
|
}
|
|
3564
3622
|
}
|
|
3565
3623
|
} else if (msg.role === "user" && msg.toolCallId && typeof msg.content === "string") {
|
|
@@ -3904,7 +3962,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3904
3962
|
messages: thisInvocation()
|
|
3905
3963
|
};
|
|
3906
3964
|
}
|
|
3907
|
-
return { text:
|
|
3965
|
+
return { text: cancelledToolResult(signal), messages: thisInvocation() };
|
|
3908
3966
|
}
|
|
3909
3967
|
let lastToolResult = "";
|
|
3910
3968
|
let watchedBlocks = [];
|
|
@@ -4140,7 +4198,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4140
4198
|
if (signal?.aborted) {
|
|
4141
4199
|
return {
|
|
4142
4200
|
id: tc.id,
|
|
4143
|
-
result:
|
|
4201
|
+
result: cancelledToolResult(signal),
|
|
4144
4202
|
isError: true
|
|
4145
4203
|
};
|
|
4146
4204
|
}
|
|
@@ -4257,7 +4315,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4257
4315
|
}
|
|
4258
4316
|
const innerMsgs = subAgentMessages.get(r.id);
|
|
4259
4317
|
if (innerMsgs) {
|
|
4260
|
-
block
|
|
4318
|
+
attachSubAgentTranscript(block, innerMsgs);
|
|
4261
4319
|
}
|
|
4262
4320
|
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4263
4321
|
try {
|
|
@@ -4799,13 +4857,12 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
4799
4857
|
},
|
|
4800
4858
|
toolRegistry: context.toolRegistry
|
|
4801
4859
|
});
|
|
4802
|
-
context.subAgentMessages?.set(
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
(m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(
|
|
4806
|
-
(b) => b.type === "tool" && b.name === "browserCommand" && !!b.recording
|
|
4807
|
-
)
|
|
4860
|
+
context.subAgentMessages?.set(
|
|
4861
|
+
context.toolCallId,
|
|
4862
|
+
dropToolResultMessages(result.messages)
|
|
4808
4863
|
);
|
|
4864
|
+
const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
|
|
4865
|
+
const recorded = collectRecordings(result.messages).length > 0;
|
|
4809
4866
|
return {
|
|
4810
4867
|
text: result.text,
|
|
4811
4868
|
recorded,
|
|
@@ -4960,6 +5017,79 @@ var screenshotTool = {
|
|
|
4960
5017
|
execute: (input, context) => executeScreenshot(input, context?.onLog, context)
|
|
4961
5018
|
};
|
|
4962
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
|
+
|
|
4963
5093
|
// src/subagents/research/tools.ts
|
|
4964
5094
|
var searchGoogleDefinition = {
|
|
4965
5095
|
name: "searchGoogle",
|
|
@@ -5019,22 +5149,6 @@ async function executeSearchGoogle(input, onLog, caller) {
|
|
|
5019
5149
|
}
|
|
5020
5150
|
);
|
|
5021
5151
|
}
|
|
5022
|
-
async function executeScrapeWebUrl(input, onLog, caller) {
|
|
5023
|
-
return runMindstudioCli(
|
|
5024
|
-
[
|
|
5025
|
-
"scrape-url",
|
|
5026
|
-
"--url",
|
|
5027
|
-
String(input.url),
|
|
5028
|
-
"--page-options",
|
|
5029
|
-
JSON.stringify({ onlyMainContent: true })
|
|
5030
|
-
],
|
|
5031
|
-
{
|
|
5032
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
5033
|
-
onLog,
|
|
5034
|
-
caller
|
|
5035
|
-
}
|
|
5036
|
-
);
|
|
5037
|
-
}
|
|
5038
5152
|
|
|
5039
5153
|
// src/subagents/research/index.ts
|
|
5040
5154
|
var BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
|
|
@@ -5060,7 +5174,11 @@ async function runResearch(task, context) {
|
|
|
5060
5174
|
return executeSearchGoogle(toolInput, childCtx.onLog, "research");
|
|
5061
5175
|
}
|
|
5062
5176
|
if (name === "scrapeWebUrl") {
|
|
5063
|
-
return
|
|
5177
|
+
return fetchWebPage(String(toolInput.url), {
|
|
5178
|
+
screenshot: false,
|
|
5179
|
+
caller: "research",
|
|
5180
|
+
onLog: childCtx.onLog
|
|
5181
|
+
});
|
|
5064
5182
|
}
|
|
5065
5183
|
return executeTool(name, toolInput, childCtx);
|
|
5066
5184
|
},
|
|
@@ -5108,7 +5226,7 @@ __export(scrapeWebUrl_exports, {
|
|
|
5108
5226
|
});
|
|
5109
5227
|
var definition = {
|
|
5110
5228
|
name: "scrapeWebUrl",
|
|
5111
|
-
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.",
|
|
5112
5230
|
inputSchema: {
|
|
5113
5231
|
type: "object",
|
|
5114
5232
|
properties: {
|
|
@@ -5121,17 +5239,11 @@ var definition = {
|
|
|
5121
5239
|
}
|
|
5122
5240
|
};
|
|
5123
5241
|
async function execute(input, onLog) {
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
input.url,
|
|
5130
|
-
"--page-options",
|
|
5131
|
-
JSON.stringify(pageOptions)
|
|
5132
|
-
],
|
|
5133
|
-
{ onLog, caller: "designExpert", maxBuffer: SCRAPE_MAX_BUFFER }
|
|
5134
|
-
);
|
|
5242
|
+
return fetchWebPage(String(input.url), {
|
|
5243
|
+
screenshot: true,
|
|
5244
|
+
caller: "designExpert",
|
|
5245
|
+
onLog
|
|
5246
|
+
});
|
|
5135
5247
|
}
|
|
5136
5248
|
|
|
5137
5249
|
// src/subagents/designExpert/tools/analyzeDesign.ts
|
|
@@ -7312,50 +7424,6 @@ var reviewExistingProjectTool = {
|
|
|
7312
7424
|
}
|
|
7313
7425
|
};
|
|
7314
7426
|
|
|
7315
|
-
// src/tools/common/scrapeWebUrl.ts
|
|
7316
|
-
var scrapeWebUrlTool = {
|
|
7317
|
-
definition: {
|
|
7318
|
-
name: "scrapeWebUrl",
|
|
7319
|
-
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",
|
|
7320
|
-
inputSchema: {
|
|
7321
|
-
type: "object",
|
|
7322
|
-
properties: {
|
|
7323
|
-
url: {
|
|
7324
|
-
type: "string",
|
|
7325
|
-
description: "The URL to fetch."
|
|
7326
|
-
},
|
|
7327
|
-
screenshot: {
|
|
7328
|
-
type: "boolean",
|
|
7329
|
-
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."
|
|
7330
|
-
}
|
|
7331
|
-
},
|
|
7332
|
-
required: ["url"]
|
|
7333
|
-
}
|
|
7334
|
-
},
|
|
7335
|
-
async execute(input, context) {
|
|
7336
|
-
const url = input.url;
|
|
7337
|
-
const screenshot = input.screenshot;
|
|
7338
|
-
const pageOptions = { onlyMainContent: true };
|
|
7339
|
-
if (screenshot) {
|
|
7340
|
-
pageOptions.screenshot = true;
|
|
7341
|
-
}
|
|
7342
|
-
return runMindstudioCli(
|
|
7343
|
-
[
|
|
7344
|
-
"scrape-url",
|
|
7345
|
-
"--url",
|
|
7346
|
-
url,
|
|
7347
|
-
"--page-options",
|
|
7348
|
-
JSON.stringify(pageOptions)
|
|
7349
|
-
],
|
|
7350
|
-
{
|
|
7351
|
-
onLog: context?.onLog,
|
|
7352
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
7353
|
-
caller: "parent"
|
|
7354
|
-
}
|
|
7355
|
-
);
|
|
7356
|
-
}
|
|
7357
|
-
};
|
|
7358
|
-
|
|
7359
7427
|
// src/tools/index.ts
|
|
7360
7428
|
function deriveContext(parent, toolCallId, onLog) {
|
|
7361
7429
|
return { ...parent, toolCallId, onLog };
|
|
@@ -8841,17 +8909,11 @@ function resolveAction(text) {
|
|
|
8841
8909
|
if (!parsed) {
|
|
8842
8910
|
return null;
|
|
8843
8911
|
}
|
|
8844
|
-
const { name: triggerName
|
|
8912
|
+
const { name: triggerName } = parsed;
|
|
8845
8913
|
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
8846
8914
|
return null;
|
|
8847
8915
|
}
|
|
8848
|
-
|
|
8849
|
-
if (remainder) {
|
|
8850
|
-
try {
|
|
8851
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
8852
|
-
} catch {
|
|
8853
|
-
}
|
|
8854
|
-
}
|
|
8916
|
+
const params = sentinelParams(text);
|
|
8855
8917
|
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
8856
8918
|
let next;
|
|
8857
8919
|
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
@@ -8866,8 +8928,16 @@ function resolveAction(text) {
|
|
|
8866
8928
|
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
8867
8929
|
body = body.replaceAll(`{{${key}}}`, str);
|
|
8868
8930
|
}
|
|
8931
|
+
const resumed = params.resumed === true;
|
|
8932
|
+
if (resumed) {
|
|
8933
|
+
body = `${readAsset("automatedActions", "_resumed.md")}
|
|
8934
|
+
|
|
8935
|
+
${body}`;
|
|
8936
|
+
}
|
|
8869
8937
|
return {
|
|
8870
|
-
message: automatedMessage(triggerName, body),
|
|
8938
|
+
message: resumed ? setSentinelParams(automatedMessage(triggerName, body), {
|
|
8939
|
+
resumed: true
|
|
8940
|
+
}) : automatedMessage(triggerName, body),
|
|
8871
8941
|
next
|
|
8872
8942
|
};
|
|
8873
8943
|
}
|
|
@@ -9250,7 +9320,10 @@ async function runTurn(params) {
|
|
|
9250
9320
|
const automated = parseSentinel(entry.text);
|
|
9251
9321
|
if (automated) {
|
|
9252
9322
|
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
9253
|
-
|
|
9323
|
+
const resumed = sentinelParams(entry.text).resumed === true;
|
|
9324
|
+
parts.push(
|
|
9325
|
+
`Automated action: ${automated.name}${resumed ? " (resuming interrupted work)" : ""}`
|
|
9326
|
+
);
|
|
9254
9327
|
hasUserSignal = true;
|
|
9255
9328
|
}
|
|
9256
9329
|
} else if (entry.text) {
|
|
@@ -9639,7 +9712,11 @@ async function runTurn(params) {
|
|
|
9639
9712
|
const results = await Promise.all(
|
|
9640
9713
|
toolCalls.map(async (tc) => {
|
|
9641
9714
|
if (signal?.aborted) {
|
|
9642
|
-
return {
|
|
9715
|
+
return {
|
|
9716
|
+
id: tc.id,
|
|
9717
|
+
result: cancelledToolResult(signal),
|
|
9718
|
+
isError: true
|
|
9719
|
+
};
|
|
9643
9720
|
}
|
|
9644
9721
|
const toolStart = Date.now();
|
|
9645
9722
|
let settle;
|
|
@@ -9658,7 +9735,7 @@ async function runTurn(params) {
|
|
|
9658
9735
|
};
|
|
9659
9736
|
const cascadeAbort = () => {
|
|
9660
9737
|
toolAbort.abort();
|
|
9661
|
-
safeSettle(
|
|
9738
|
+
safeSettle(cancelledToolResult(signal), true);
|
|
9662
9739
|
};
|
|
9663
9740
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
9664
9741
|
const run = async (input) => {
|
|
@@ -9767,7 +9844,7 @@ async function runTurn(params) {
|
|
|
9767
9844
|
block.completedAt = Date.now();
|
|
9768
9845
|
const msgs = subAgentMessages.get(r.id);
|
|
9769
9846
|
if (msgs) {
|
|
9770
|
-
block
|
|
9847
|
+
attachSubAgentTranscript(block, msgs);
|
|
9771
9848
|
}
|
|
9772
9849
|
}
|
|
9773
9850
|
}
|
|
@@ -10019,6 +10096,15 @@ function holdRestoredUserItems(items) {
|
|
|
10019
10096
|
(item) => item.source === "user" ? { ...item, held: true } : item
|
|
10020
10097
|
);
|
|
10021
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
|
+
}
|
|
10022
10108
|
var MessageQueue = class {
|
|
10023
10109
|
items = [];
|
|
10024
10110
|
onChange;
|
|
@@ -10090,16 +10176,27 @@ var MessageQueue = class {
|
|
|
10090
10176
|
/**
|
|
10091
10177
|
* Mark matching items `held` — waiting on the user rather than on the agent.
|
|
10092
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.
|
|
10093
10185
|
*/
|
|
10094
|
-
holdWhere(predicate) {
|
|
10186
|
+
holdWhere(predicate, heldBy) {
|
|
10095
10187
|
const held = [];
|
|
10096
10188
|
let changed = false;
|
|
10097
10189
|
for (const item of this.items) {
|
|
10098
10190
|
if (!predicate(item)) {
|
|
10099
10191
|
continue;
|
|
10100
10192
|
}
|
|
10101
|
-
|
|
10102
|
-
|
|
10193
|
+
if (!item.held) {
|
|
10194
|
+
changed = true;
|
|
10195
|
+
item.held = true;
|
|
10196
|
+
if (heldBy) {
|
|
10197
|
+
item.heldBy = heldBy;
|
|
10198
|
+
}
|
|
10199
|
+
}
|
|
10103
10200
|
held.push(item);
|
|
10104
10201
|
}
|
|
10105
10202
|
if (changed) {
|
|
@@ -10147,6 +10244,7 @@ var MessageQueue = class {
|
|
|
10147
10244
|
const [item] = this.items.splice(idx, 1);
|
|
10148
10245
|
item.delivery = "asap";
|
|
10149
10246
|
delete item.held;
|
|
10247
|
+
delete item.heldBy;
|
|
10150
10248
|
this.items.unshift(item);
|
|
10151
10249
|
this.onChange?.();
|
|
10152
10250
|
return item;
|
|
@@ -10170,6 +10268,7 @@ var MessageQueue = class {
|
|
|
10170
10268
|
continue;
|
|
10171
10269
|
}
|
|
10172
10270
|
delete item.held;
|
|
10271
|
+
delete item.heldBy;
|
|
10173
10272
|
released.push(item);
|
|
10174
10273
|
}
|
|
10175
10274
|
const back = this.items.filter(defer);
|
|
@@ -10299,10 +10398,13 @@ var HeadlessSession = class {
|
|
|
10299
10398
|
});
|
|
10300
10399
|
await initOrgContext(this.config);
|
|
10301
10400
|
const resumed = loadSession(this.state);
|
|
10302
|
-
this.queue = new MessageQueue(
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10401
|
+
this.queue = new MessageQueue(
|
|
10402
|
+
releaseShutdownHolds(holdRestoredUserItems(loadQueue())),
|
|
10403
|
+
() => {
|
|
10404
|
+
this.persistStats();
|
|
10405
|
+
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
10406
|
+
}
|
|
10407
|
+
);
|
|
10306
10408
|
this.passivePen = loadPassiveResults();
|
|
10307
10409
|
this.persistStats();
|
|
10308
10410
|
if (resumed) {
|
|
@@ -10424,6 +10526,11 @@ var HeadlessSession = class {
|
|
|
10424
10526
|
this.emit("ready");
|
|
10425
10527
|
}
|
|
10426
10528
|
shutdown = () => {
|
|
10529
|
+
try {
|
|
10530
|
+
this.handleCancel("shutdown");
|
|
10531
|
+
} catch (err) {
|
|
10532
|
+
log17.warn("Shutdown cancel failed", { error: err?.message });
|
|
10533
|
+
}
|
|
10427
10534
|
this.emit("stopping");
|
|
10428
10535
|
this.emit("stopped");
|
|
10429
10536
|
process.exit(0);
|
|
@@ -11361,6 +11468,19 @@ var HeadlessSession = class {
|
|
|
11361
11468
|
* pipeline where it stopped. Holding only the remainder would resume one step
|
|
11362
11469
|
* PAST the interruption, polishing and finalizing half-built code.
|
|
11363
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
|
+
*
|
|
11364
11484
|
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
11365
11485
|
* message and outlives the turn that started it, so leaving it running means
|
|
11366
11486
|
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
@@ -11371,14 +11491,16 @@ var HeadlessSession = class {
|
|
|
11371
11491
|
* `{cancelled, absorbed:true}` terminal. Only items still sitting in the
|
|
11372
11492
|
* queue survive.
|
|
11373
11493
|
*/
|
|
11374
|
-
handleCancel() {
|
|
11494
|
+
handleCancel(reason) {
|
|
11375
11495
|
if (this.currentAbort) {
|
|
11376
|
-
this.currentAbort.abort();
|
|
11496
|
+
this.currentAbort.abort(reason);
|
|
11377
11497
|
}
|
|
11378
11498
|
const cancelledCompaction = cancelInflightCompaction();
|
|
11379
11499
|
for (const [id, pending2] of this.pendingTools) {
|
|
11380
11500
|
clearTimeout(pending2.timeout);
|
|
11381
|
-
pending2.resolve(
|
|
11501
|
+
pending2.resolve(
|
|
11502
|
+
reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT
|
|
11503
|
+
);
|
|
11382
11504
|
this.pendingTools.delete(id);
|
|
11383
11505
|
}
|
|
11384
11506
|
const flushed = this.queue.removeWhere(
|
|
@@ -11389,7 +11511,13 @@ var HeadlessSession = class {
|
|
|
11389
11511
|
this.queue.unshift({
|
|
11390
11512
|
command: {
|
|
11391
11513
|
action: "message",
|
|
11392
|
-
|
|
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 }),
|
|
11393
11521
|
onboardingState: step.onboardingState,
|
|
11394
11522
|
// Fresh id: the original command's terminal has already gone out as
|
|
11395
11523
|
// cancelled, and one command gets exactly one `completed`.
|
|
@@ -11397,13 +11525,18 @@ var HeadlessSession = class {
|
|
|
11397
11525
|
},
|
|
11398
11526
|
source: "chain",
|
|
11399
11527
|
enqueuedAt: Date.now(),
|
|
11400
|
-
held: true
|
|
11528
|
+
held: true,
|
|
11529
|
+
...reason === "shutdown" && { heldBy: reason }
|
|
11401
11530
|
});
|
|
11402
11531
|
this.currentChainStep = null;
|
|
11403
11532
|
}
|
|
11404
|
-
const held =
|
|
11405
|
-
|
|
11406
|
-
|
|
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
|
+
];
|
|
11407
11540
|
return {
|
|
11408
11541
|
flushed,
|
|
11409
11542
|
held,
|
|
@@ -11415,9 +11548,10 @@ var HeadlessSession = class {
|
|
|
11415
11548
|
* Remove pending queued messages: all user messages (no id), or a single item
|
|
11416
11549
|
* by id. Does not affect the in-flight turn (use `cancel` for that).
|
|
11417
11550
|
*
|
|
11418
|
-
* Held chain items — a paused pipeline — are removable only by explicit id
|
|
11419
|
-
*
|
|
11420
|
-
*
|
|
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
|
|
11421
11555
|
* chain item is never removable: that's live pipeline work. (The step
|
|
11422
11556
|
* actually running isn't in the queue at all — drainQueueLoop takes it out
|
|
11423
11557
|
* before running it.)
|
|
@@ -11523,7 +11657,9 @@ var HeadlessSession = class {
|
|
|
11523
11657
|
return;
|
|
11524
11658
|
}
|
|
11525
11659
|
if (action === "cancel") {
|
|
11526
|
-
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
11660
|
+
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
11661
|
+
parsed.reason === "shutdown" ? "shutdown" : void 0
|
|
11662
|
+
);
|
|
11527
11663
|
this.emit(
|
|
11528
11664
|
"completed",
|
|
11529
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") {
|
|
@@ -4666,13 +4720,17 @@ var init_browserLock = __esm({
|
|
|
4666
4720
|
});
|
|
4667
4721
|
|
|
4668
4722
|
// src/toolRegistry.ts
|
|
4669
|
-
|
|
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;
|
|
4670
4727
|
var init_toolRegistry = __esm({
|
|
4671
4728
|
"src/toolRegistry.ts"() {
|
|
4672
4729
|
"use strict";
|
|
4673
4730
|
init_logger();
|
|
4674
4731
|
log7 = createLogger("tool-registry");
|
|
4675
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.";
|
|
4676
4734
|
ToolRegistry = class {
|
|
4677
4735
|
entries = /* @__PURE__ */ new Map();
|
|
4678
4736
|
onEvent;
|
|
@@ -4928,7 +4986,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4928
4986
|
messages: thisInvocation()
|
|
4929
4987
|
};
|
|
4930
4988
|
}
|
|
4931
|
-
return { text:
|
|
4989
|
+
return { text: cancelledToolResult(signal), messages: thisInvocation() };
|
|
4932
4990
|
}
|
|
4933
4991
|
let lastToolResult = "";
|
|
4934
4992
|
let watchedBlocks = [];
|
|
@@ -5164,7 +5222,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
5164
5222
|
if (signal?.aborted) {
|
|
5165
5223
|
return {
|
|
5166
5224
|
id: tc.id,
|
|
5167
|
-
result:
|
|
5225
|
+
result: cancelledToolResult(signal),
|
|
5168
5226
|
isError: true
|
|
5169
5227
|
};
|
|
5170
5228
|
}
|
|
@@ -5281,7 +5339,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
5281
5339
|
}
|
|
5282
5340
|
const innerMsgs = subAgentMessages.get(r.id);
|
|
5283
5341
|
if (innerMsgs) {
|
|
5284
|
-
block
|
|
5342
|
+
attachSubAgentTranscript(block, innerMsgs);
|
|
5285
5343
|
}
|
|
5286
5344
|
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
5287
5345
|
try {
|
|
@@ -5867,13 +5925,12 @@ async function runBrowserAutomation(task, context, opts) {
|
|
|
5867
5925
|
},
|
|
5868
5926
|
toolRegistry: context.toolRegistry
|
|
5869
5927
|
});
|
|
5870
|
-
context.subAgentMessages?.set(
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
(m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some(
|
|
5874
|
-
(b) => b.type === "tool" && b.name === "browserCommand" && !!b.recording
|
|
5875
|
-
)
|
|
5928
|
+
context.subAgentMessages?.set(
|
|
5929
|
+
context.toolCallId,
|
|
5930
|
+
dropToolResultMessages(result.messages)
|
|
5876
5931
|
);
|
|
5932
|
+
const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
|
|
5933
|
+
const recorded = collectRecordings(result.messages).length > 0;
|
|
5877
5934
|
return {
|
|
5878
5935
|
text: result.text,
|
|
5879
5936
|
recorded,
|
|
@@ -5889,6 +5946,8 @@ var init_browserAutomation = __esm({
|
|
|
5889
5946
|
"use strict";
|
|
5890
5947
|
init_tools10();
|
|
5891
5948
|
init_runner();
|
|
5949
|
+
init_historyLimits();
|
|
5950
|
+
init_recording();
|
|
5892
5951
|
init_tools2();
|
|
5893
5952
|
init_tools();
|
|
5894
5953
|
init_readSpec();
|
|
@@ -6058,6 +6117,88 @@ var init_screenshot2 = __esm({
|
|
|
6058
6117
|
}
|
|
6059
6118
|
});
|
|
6060
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
|
+
|
|
6061
6202
|
// src/subagents/research/tools.ts
|
|
6062
6203
|
async function executeSearchGoogle(input, onLog, caller) {
|
|
6063
6204
|
const fetchTopN = Math.max(0, Math.round(Number(input.fetchTopN) || 0));
|
|
@@ -6079,22 +6220,6 @@ async function executeSearchGoogle(input, onLog, caller) {
|
|
|
6079
6220
|
}
|
|
6080
6221
|
);
|
|
6081
6222
|
}
|
|
6082
|
-
async function executeScrapeWebUrl(input, onLog, caller) {
|
|
6083
|
-
return runMindstudioCli(
|
|
6084
|
-
[
|
|
6085
|
-
"scrape-url",
|
|
6086
|
-
"--url",
|
|
6087
|
-
String(input.url),
|
|
6088
|
-
"--page-options",
|
|
6089
|
-
JSON.stringify({ onlyMainContent: true })
|
|
6090
|
-
],
|
|
6091
|
-
{
|
|
6092
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
6093
|
-
onLog,
|
|
6094
|
-
caller
|
|
6095
|
-
}
|
|
6096
|
-
);
|
|
6097
|
-
}
|
|
6098
6223
|
var searchGoogleDefinition, scrapeWebUrlDefinition, RESEARCH_TOOLS;
|
|
6099
6224
|
var init_tools3 = __esm({
|
|
6100
6225
|
"src/subagents/research/tools.ts"() {
|
|
@@ -6167,7 +6292,11 @@ async function runResearch(task, context) {
|
|
|
6167
6292
|
return executeSearchGoogle(toolInput, childCtx.onLog, "research");
|
|
6168
6293
|
}
|
|
6169
6294
|
if (name === "scrapeWebUrl") {
|
|
6170
|
-
return
|
|
6295
|
+
return fetchWebPage(String(toolInput.url), {
|
|
6296
|
+
screenshot: false,
|
|
6297
|
+
caller: "research",
|
|
6298
|
+
onLog: childCtx.onLog
|
|
6299
|
+
});
|
|
6171
6300
|
}
|
|
6172
6301
|
return executeTool(name, toolInput, childCtx);
|
|
6173
6302
|
},
|
|
@@ -6192,6 +6321,7 @@ var init_research = __esm({
|
|
|
6192
6321
|
init_runner();
|
|
6193
6322
|
init_context();
|
|
6194
6323
|
init_tools10();
|
|
6324
|
+
init_scrapeWebUrl();
|
|
6195
6325
|
init_tools3();
|
|
6196
6326
|
init_surfaces();
|
|
6197
6327
|
BASE_PROMPT2 = readAsset("subagents/research", "prompt.md");
|
|
@@ -6227,27 +6357,20 @@ __export(scrapeWebUrl_exports, {
|
|
|
6227
6357
|
execute: () => execute
|
|
6228
6358
|
});
|
|
6229
6359
|
async function execute(input, onLog) {
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
input.url,
|
|
6236
|
-
"--page-options",
|
|
6237
|
-
JSON.stringify(pageOptions)
|
|
6238
|
-
],
|
|
6239
|
-
{ onLog, caller: "designExpert", maxBuffer: SCRAPE_MAX_BUFFER }
|
|
6240
|
-
);
|
|
6360
|
+
return fetchWebPage(String(input.url), {
|
|
6361
|
+
screenshot: true,
|
|
6362
|
+
caller: "designExpert",
|
|
6363
|
+
onLog
|
|
6364
|
+
});
|
|
6241
6365
|
}
|
|
6242
6366
|
var definition;
|
|
6243
|
-
var
|
|
6367
|
+
var init_scrapeWebUrl2 = __esm({
|
|
6244
6368
|
"src/subagents/designExpert/tools/scrapeWebUrl.ts"() {
|
|
6245
6369
|
"use strict";
|
|
6246
|
-
|
|
6247
|
-
init_runCli();
|
|
6370
|
+
init_scrapeWebUrl();
|
|
6248
6371
|
definition = {
|
|
6249
6372
|
name: "scrapeWebUrl",
|
|
6250
|
-
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.",
|
|
6251
6374
|
inputSchema: {
|
|
6252
6375
|
type: "object",
|
|
6253
6376
|
properties: {
|
|
@@ -7392,7 +7515,7 @@ var init_tools5 = __esm({
|
|
|
7392
7515
|
init_tools10();
|
|
7393
7516
|
init_tools();
|
|
7394
7517
|
init_research();
|
|
7395
|
-
|
|
7518
|
+
init_scrapeWebUrl2();
|
|
7396
7519
|
init_analyzeDesign();
|
|
7397
7520
|
init_analyzeImage2();
|
|
7398
7521
|
init_generateImages();
|
|
@@ -8829,58 +8952,6 @@ var init_reviewExistingProject = __esm({
|
|
|
8829
8952
|
}
|
|
8830
8953
|
});
|
|
8831
8954
|
|
|
8832
|
-
// src/tools/common/scrapeWebUrl.ts
|
|
8833
|
-
var scrapeWebUrlTool;
|
|
8834
|
-
var init_scrapeWebUrl2 = __esm({
|
|
8835
|
-
"src/tools/common/scrapeWebUrl.ts"() {
|
|
8836
|
-
"use strict";
|
|
8837
|
-
init_runMindstudioCli();
|
|
8838
|
-
init_runCli();
|
|
8839
|
-
scrapeWebUrlTool = {
|
|
8840
|
-
definition: {
|
|
8841
|
-
name: "scrapeWebUrl",
|
|
8842
|
-
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",
|
|
8843
|
-
inputSchema: {
|
|
8844
|
-
type: "object",
|
|
8845
|
-
properties: {
|
|
8846
|
-
url: {
|
|
8847
|
-
type: "string",
|
|
8848
|
-
description: "The URL to fetch."
|
|
8849
|
-
},
|
|
8850
|
-
screenshot: {
|
|
8851
|
-
type: "boolean",
|
|
8852
|
-
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."
|
|
8853
|
-
}
|
|
8854
|
-
},
|
|
8855
|
-
required: ["url"]
|
|
8856
|
-
}
|
|
8857
|
-
},
|
|
8858
|
-
async execute(input, context) {
|
|
8859
|
-
const url = input.url;
|
|
8860
|
-
const screenshot = input.screenshot;
|
|
8861
|
-
const pageOptions = { onlyMainContent: true };
|
|
8862
|
-
if (screenshot) {
|
|
8863
|
-
pageOptions.screenshot = true;
|
|
8864
|
-
}
|
|
8865
|
-
return runMindstudioCli(
|
|
8866
|
-
[
|
|
8867
|
-
"scrape-url",
|
|
8868
|
-
"--url",
|
|
8869
|
-
url,
|
|
8870
|
-
"--page-options",
|
|
8871
|
-
JSON.stringify(pageOptions)
|
|
8872
|
-
],
|
|
8873
|
-
{
|
|
8874
|
-
onLog: context?.onLog,
|
|
8875
|
-
maxBuffer: SCRAPE_MAX_BUFFER,
|
|
8876
|
-
caller: "parent"
|
|
8877
|
-
}
|
|
8878
|
-
);
|
|
8879
|
-
}
|
|
8880
|
-
};
|
|
8881
|
-
}
|
|
8882
|
-
});
|
|
8883
|
-
|
|
8884
8955
|
// src/tools/index.ts
|
|
8885
8956
|
function deriveContext(parent, toolCallId, onLog) {
|
|
8886
8957
|
return { ...parent, toolCallId, onLog };
|
|
@@ -8939,7 +9010,7 @@ var init_tools10 = __esm({
|
|
|
8939
9010
|
init_specSync();
|
|
8940
9011
|
init_research();
|
|
8941
9012
|
init_reviewExistingProject();
|
|
8942
|
-
|
|
9013
|
+
init_scrapeWebUrl();
|
|
8943
9014
|
init_writeBuildOverview();
|
|
8944
9015
|
ALL_TOOLS = [
|
|
8945
9016
|
// Common
|
|
@@ -9179,17 +9250,11 @@ function resolveAction(text) {
|
|
|
9179
9250
|
if (!parsed) {
|
|
9180
9251
|
return null;
|
|
9181
9252
|
}
|
|
9182
|
-
const { name: triggerName
|
|
9253
|
+
const { name: triggerName } = parsed;
|
|
9183
9254
|
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
9184
9255
|
return null;
|
|
9185
9256
|
}
|
|
9186
|
-
|
|
9187
|
-
if (remainder) {
|
|
9188
|
-
try {
|
|
9189
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
9190
|
-
} catch {
|
|
9191
|
-
}
|
|
9192
|
-
}
|
|
9257
|
+
const params = sentinelParams(text);
|
|
9193
9258
|
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
9194
9259
|
let next;
|
|
9195
9260
|
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
@@ -9204,8 +9269,16 @@ function resolveAction(text) {
|
|
|
9204
9269
|
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
9205
9270
|
body = body.replaceAll(`{{${key}}}`, str);
|
|
9206
9271
|
}
|
|
9272
|
+
const resumed = params.resumed === true;
|
|
9273
|
+
if (resumed) {
|
|
9274
|
+
body = `${readAsset("automatedActions", "_resumed.md")}
|
|
9275
|
+
|
|
9276
|
+
${body}`;
|
|
9277
|
+
}
|
|
9207
9278
|
return {
|
|
9208
|
-
message: automatedMessage(triggerName, body),
|
|
9279
|
+
message: resumed ? setSentinelParams(automatedMessage(triggerName, body), {
|
|
9280
|
+
resumed: true
|
|
9281
|
+
}) : automatedMessage(triggerName, body),
|
|
9209
9282
|
next
|
|
9210
9283
|
};
|
|
9211
9284
|
}
|
|
@@ -9950,7 +10023,10 @@ async function runTurn(params) {
|
|
|
9950
10023
|
const automated = parseSentinel(entry.text);
|
|
9951
10024
|
if (automated) {
|
|
9952
10025
|
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
9953
|
-
|
|
10026
|
+
const resumed = sentinelParams(entry.text).resumed === true;
|
|
10027
|
+
parts.push(
|
|
10028
|
+
`Automated action: ${automated.name}${resumed ? " (resuming interrupted work)" : ""}`
|
|
10029
|
+
);
|
|
9954
10030
|
hasUserSignal = true;
|
|
9955
10031
|
}
|
|
9956
10032
|
} else if (entry.text) {
|
|
@@ -10339,7 +10415,11 @@ async function runTurn(params) {
|
|
|
10339
10415
|
const results = await Promise.all(
|
|
10340
10416
|
toolCalls.map(async (tc) => {
|
|
10341
10417
|
if (signal?.aborted) {
|
|
10342
|
-
return {
|
|
10418
|
+
return {
|
|
10419
|
+
id: tc.id,
|
|
10420
|
+
result: cancelledToolResult(signal),
|
|
10421
|
+
isError: true
|
|
10422
|
+
};
|
|
10343
10423
|
}
|
|
10344
10424
|
const toolStart = Date.now();
|
|
10345
10425
|
let settle;
|
|
@@ -10358,7 +10438,7 @@ async function runTurn(params) {
|
|
|
10358
10438
|
};
|
|
10359
10439
|
const cascadeAbort = () => {
|
|
10360
10440
|
toolAbort.abort();
|
|
10361
|
-
safeSettle(
|
|
10441
|
+
safeSettle(cancelledToolResult(signal), true);
|
|
10362
10442
|
};
|
|
10363
10443
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
10364
10444
|
const run = async (input) => {
|
|
@@ -10467,7 +10547,7 @@ async function runTurn(params) {
|
|
|
10467
10547
|
block.completedAt = Date.now();
|
|
10468
10548
|
const msgs = subAgentMessages.get(r.id);
|
|
10469
10549
|
if (msgs) {
|
|
10470
|
-
block
|
|
10550
|
+
attachSubAgentTranscript(block, msgs);
|
|
10471
10551
|
}
|
|
10472
10552
|
}
|
|
10473
10553
|
}
|
|
@@ -11031,6 +11111,15 @@ function holdRestoredUserItems(items) {
|
|
|
11031
11111
|
(item) => item.source === "user" ? { ...item, held: true } : item
|
|
11032
11112
|
);
|
|
11033
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
|
+
}
|
|
11034
11123
|
var MessageQueue;
|
|
11035
11124
|
var init_messageQueue = __esm({
|
|
11036
11125
|
"src/headless/messageQueue.ts"() {
|
|
@@ -11106,16 +11195,27 @@ var init_messageQueue = __esm({
|
|
|
11106
11195
|
/**
|
|
11107
11196
|
* Mark matching items `held` — waiting on the user rather than on the agent.
|
|
11108
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.
|
|
11109
11204
|
*/
|
|
11110
|
-
holdWhere(predicate) {
|
|
11205
|
+
holdWhere(predicate, heldBy) {
|
|
11111
11206
|
const held = [];
|
|
11112
11207
|
let changed = false;
|
|
11113
11208
|
for (const item of this.items) {
|
|
11114
11209
|
if (!predicate(item)) {
|
|
11115
11210
|
continue;
|
|
11116
11211
|
}
|
|
11117
|
-
|
|
11118
|
-
|
|
11212
|
+
if (!item.held) {
|
|
11213
|
+
changed = true;
|
|
11214
|
+
item.held = true;
|
|
11215
|
+
if (heldBy) {
|
|
11216
|
+
item.heldBy = heldBy;
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11119
11219
|
held.push(item);
|
|
11120
11220
|
}
|
|
11121
11221
|
if (changed) {
|
|
@@ -11163,6 +11263,7 @@ var init_messageQueue = __esm({
|
|
|
11163
11263
|
const [item] = this.items.splice(idx, 1);
|
|
11164
11264
|
item.delivery = "asap";
|
|
11165
11265
|
delete item.held;
|
|
11266
|
+
delete item.heldBy;
|
|
11166
11267
|
this.items.unshift(item);
|
|
11167
11268
|
this.onChange?.();
|
|
11168
11269
|
return item;
|
|
@@ -11186,6 +11287,7 @@ var init_messageQueue = __esm({
|
|
|
11186
11287
|
continue;
|
|
11187
11288
|
}
|
|
11188
11289
|
delete item.held;
|
|
11290
|
+
delete item.heldBy;
|
|
11189
11291
|
released.push(item);
|
|
11190
11292
|
}
|
|
11191
11293
|
const back = this.items.filter(defer);
|
|
@@ -11343,10 +11445,13 @@ var init_headless = __esm({
|
|
|
11343
11445
|
});
|
|
11344
11446
|
await initOrgContext(this.config);
|
|
11345
11447
|
const resumed = loadSession(this.state);
|
|
11346
|
-
this.queue = new MessageQueue(
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11448
|
+
this.queue = new MessageQueue(
|
|
11449
|
+
releaseShutdownHolds(holdRestoredUserItems(loadQueue())),
|
|
11450
|
+
() => {
|
|
11451
|
+
this.persistStats();
|
|
11452
|
+
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
11453
|
+
}
|
|
11454
|
+
);
|
|
11350
11455
|
this.passivePen = loadPassiveResults();
|
|
11351
11456
|
this.persistStats();
|
|
11352
11457
|
if (resumed) {
|
|
@@ -11468,6 +11573,11 @@ var init_headless = __esm({
|
|
|
11468
11573
|
this.emit("ready");
|
|
11469
11574
|
}
|
|
11470
11575
|
shutdown = () => {
|
|
11576
|
+
try {
|
|
11577
|
+
this.handleCancel("shutdown");
|
|
11578
|
+
} catch (err) {
|
|
11579
|
+
log17.warn("Shutdown cancel failed", { error: err?.message });
|
|
11580
|
+
}
|
|
11471
11581
|
this.emit("stopping");
|
|
11472
11582
|
this.emit("stopped");
|
|
11473
11583
|
process.exit(0);
|
|
@@ -12405,6 +12515,19 @@ var init_headless = __esm({
|
|
|
12405
12515
|
* pipeline where it stopped. Holding only the remainder would resume one step
|
|
12406
12516
|
* PAST the interruption, polishing and finalizing half-built code.
|
|
12407
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
|
+
*
|
|
12408
12531
|
* A compaction is cancelled here too, unconditionally. It gates every queued
|
|
12409
12532
|
* message and outlives the turn that started it, so leaving it running means
|
|
12410
12533
|
* Stop can't reach idle. The cost is the summary work in flight; the forced
|
|
@@ -12415,14 +12538,16 @@ var init_headless = __esm({
|
|
|
12415
12538
|
* `{cancelled, absorbed:true}` terminal. Only items still sitting in the
|
|
12416
12539
|
* queue survive.
|
|
12417
12540
|
*/
|
|
12418
|
-
handleCancel() {
|
|
12541
|
+
handleCancel(reason) {
|
|
12419
12542
|
if (this.currentAbort) {
|
|
12420
|
-
this.currentAbort.abort();
|
|
12543
|
+
this.currentAbort.abort(reason);
|
|
12421
12544
|
}
|
|
12422
12545
|
const cancelledCompaction = cancelInflightCompaction();
|
|
12423
12546
|
for (const [id, pending2] of this.pendingTools) {
|
|
12424
12547
|
clearTimeout(pending2.timeout);
|
|
12425
|
-
pending2.resolve(
|
|
12548
|
+
pending2.resolve(
|
|
12549
|
+
reason === "shutdown" ? ENV_INTERRUPTED_RESULT : USER_CANCELLED_RESULT
|
|
12550
|
+
);
|
|
12426
12551
|
this.pendingTools.delete(id);
|
|
12427
12552
|
}
|
|
12428
12553
|
const flushed = this.queue.removeWhere(
|
|
@@ -12433,7 +12558,13 @@ var init_headless = __esm({
|
|
|
12433
12558
|
this.queue.unshift({
|
|
12434
12559
|
command: {
|
|
12435
12560
|
action: "message",
|
|
12436
|
-
|
|
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 }),
|
|
12437
12568
|
onboardingState: step.onboardingState,
|
|
12438
12569
|
// Fresh id: the original command's terminal has already gone out as
|
|
12439
12570
|
// cancelled, and one command gets exactly one `completed`.
|
|
@@ -12441,13 +12572,18 @@ var init_headless = __esm({
|
|
|
12441
12572
|
},
|
|
12442
12573
|
source: "chain",
|
|
12443
12574
|
enqueuedAt: Date.now(),
|
|
12444
|
-
held: true
|
|
12575
|
+
held: true,
|
|
12576
|
+
...reason === "shutdown" && { heldBy: reason }
|
|
12445
12577
|
});
|
|
12446
12578
|
this.currentChainStep = null;
|
|
12447
12579
|
}
|
|
12448
|
-
const held =
|
|
12449
|
-
|
|
12450
|
-
|
|
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
|
+
];
|
|
12451
12587
|
return {
|
|
12452
12588
|
flushed,
|
|
12453
12589
|
held,
|
|
@@ -12459,9 +12595,10 @@ var init_headless = __esm({
|
|
|
12459
12595
|
* Remove pending queued messages: all user messages (no id), or a single item
|
|
12460
12596
|
* by id. Does not affect the in-flight turn (use `cancel` for that).
|
|
12461
12597
|
*
|
|
12462
|
-
* Held chain items — a paused pipeline — are removable only by explicit id
|
|
12463
|
-
*
|
|
12464
|
-
*
|
|
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
|
|
12465
12602
|
* chain item is never removable: that's live pipeline work. (The step
|
|
12466
12603
|
* actually running isn't in the queue at all — drainQueueLoop takes it out
|
|
12467
12604
|
* before running it.)
|
|
@@ -12567,7 +12704,9 @@ var init_headless = __esm({
|
|
|
12567
12704
|
return;
|
|
12568
12705
|
}
|
|
12569
12706
|
if (action === "cancel") {
|
|
12570
|
-
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
12707
|
+
const { flushed, held, pausedPipeline, cancelledCompaction } = this.handleCancel(
|
|
12708
|
+
parsed.reason === "shutdown" ? "shutdown" : void 0
|
|
12709
|
+
);
|
|
12571
12710
|
this.emit(
|
|
12572
12711
|
"completed",
|
|
12573
12712
|
{
|
|
@@ -132,6 +132,7 @@ remy-admin secrets set ARCHIVE_S3_KEY --prod <value> # the user sets thes
|
|
|
132
132
|
remy-admin secrets set ARCHIVE_S3_SECRET --prod <value>
|
|
133
133
|
remy-admin datasources connect --source archive --bucket acme-docs --region us-east-1 --prefix contracts/ --access-key-secret ARCHIVE_S3_KEY --secret-key-secret ARCHIVE_S3_SECRET --budget-per-sync 5
|
|
134
134
|
remy-admin datasources sync --source archive --wait # first sync: plans the whole bucket, stops for approval if over the budget
|
|
135
|
+
remy-admin datasources sync --source archive --limit 20000 --concurrency 64 # a slice, run hard: how a big backfill is measured before it is approved
|
|
135
136
|
remy-admin datasources connector --source archive # what it follows, last sync, object counts
|
|
136
137
|
```
|
|
137
138
|
|