@mindstudio-ai/remy 0.1.265 → 0.1.267
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/README.md +13 -2
- package/dist/automatedActions/publish.md +1 -0
- package/dist/headless.d.ts +17 -1
- package/dist/headless.js +1397 -954
- package/dist/index.js +1424 -977
- package/dist/prompt/compiled/methods.md +1 -1
- package/dist/prompt/skills/agentInterfaces.md +24 -0
- package/dist/prompt/skills/taskAgents.md +1 -1
- package/dist/prompt/skills/voiceInterfaces.md +130 -40
- package/dist/prompt/static/instructions.md +1 -1
- package/dist/prompt/static/team.md +3 -1
- package/dist/subagents/browserAutomation/prompt.md +34 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1759,7 +1759,11 @@ function getConversationMessagesForSummary(messages, endIndex) {
|
|
|
1759
1759
|
break;
|
|
1760
1760
|
}
|
|
1761
1761
|
}
|
|
1762
|
-
return messages.slice(startIdx, endIndex)
|
|
1762
|
+
return messages.slice(startIdx, endIndex).filter(
|
|
1763
|
+
(msg) => !(Array.isArray(msg.content) && msg.content.some(
|
|
1764
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
1765
|
+
))
|
|
1766
|
+
);
|
|
1763
1767
|
}
|
|
1764
1768
|
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
1765
1769
|
let checkpointIdx = -1;
|
|
@@ -2289,7 +2293,12 @@ ${summaryBlock.text}
|
|
|
2289
2293
|
}
|
|
2290
2294
|
startIdx = checkpointIdx + 1;
|
|
2291
2295
|
}
|
|
2292
|
-
const
|
|
2296
|
+
const apiMessages = messages.slice(startIdx).filter(
|
|
2297
|
+
(msg) => !(msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some(
|
|
2298
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
2299
|
+
))
|
|
2300
|
+
);
|
|
2301
|
+
const messagesToProcess = fixOrphanedToolCalls(apiMessages);
|
|
2293
2302
|
const toolUseIds = /* @__PURE__ */ new Set();
|
|
2294
2303
|
for (const msg of messagesToProcess) {
|
|
2295
2304
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
@@ -2393,6 +2402,11 @@ var init_toolResultCap = __esm({
|
|
|
2393
2402
|
// src/session.ts
|
|
2394
2403
|
import fs10 from "fs";
|
|
2395
2404
|
import path4 from "path";
|
|
2405
|
+
function writeFileAtomicSync(file, data) {
|
|
2406
|
+
const tmp = `${file}.tmp`;
|
|
2407
|
+
fs10.writeFileSync(tmp, data, "utf-8");
|
|
2408
|
+
fs10.renameSync(tmp, file);
|
|
2409
|
+
}
|
|
2396
2410
|
function loadSession(state) {
|
|
2397
2411
|
pruneArchives();
|
|
2398
2412
|
try {
|
|
@@ -2481,7 +2495,7 @@ function archiveMessages(messages, label, models) {
|
|
|
2481
2495
|
if (models && Object.keys(models).length > 0) {
|
|
2482
2496
|
payload.models = models;
|
|
2483
2497
|
}
|
|
2484
|
-
|
|
2498
|
+
writeFileAtomicSync(dest, JSON.stringify(payload));
|
|
2485
2499
|
archiveCountCache.set(path4.basename(dest), count);
|
|
2486
2500
|
log3.info("Session archived", { label, dest, messageCount: count });
|
|
2487
2501
|
pruneArchives();
|
|
@@ -2686,7 +2700,7 @@ function saveSession(state) {
|
|
|
2686
2700
|
if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
|
|
2687
2701
|
serialized = JSON.stringify(buildPayload(state));
|
|
2688
2702
|
}
|
|
2689
|
-
|
|
2703
|
+
writeFileAtomicSync(SESSION_FILE, serialized);
|
|
2690
2704
|
log3.info("Session saved", { messageCount: state.messages.length });
|
|
2691
2705
|
} catch (err) {
|
|
2692
2706
|
log3.warn("Session save failed", { error: err.message });
|
|
@@ -2737,6 +2751,28 @@ var init_session = __esm({
|
|
|
2737
2751
|
});
|
|
2738
2752
|
|
|
2739
2753
|
// src/compaction/trigger.ts
|
|
2754
|
+
function getInflightCompaction() {
|
|
2755
|
+
return inflightCompaction;
|
|
2756
|
+
}
|
|
2757
|
+
function summariesOf(result) {
|
|
2758
|
+
const out = [];
|
|
2759
|
+
for (const msg of result.checkpoints) {
|
|
2760
|
+
if (!Array.isArray(msg.content)) {
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
for (const block of msg.content) {
|
|
2764
|
+
if (block.type === "summary") {
|
|
2765
|
+
out.push({
|
|
2766
|
+
name: block.name,
|
|
2767
|
+
text: block.text,
|
|
2768
|
+
...block.recent && { recent: block.recent },
|
|
2769
|
+
startedAt: block.startedAt
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return out;
|
|
2775
|
+
}
|
|
2740
2776
|
function applyPendingSummaries(state) {
|
|
2741
2777
|
const drained = pending;
|
|
2742
2778
|
pending = null;
|
|
@@ -2757,6 +2793,20 @@ function applyPendingSummaries(state) {
|
|
|
2757
2793
|
});
|
|
2758
2794
|
saveSession(state);
|
|
2759
2795
|
}
|
|
2796
|
+
function formatSummariesResult(summaries) {
|
|
2797
|
+
if (summaries === null) {
|
|
2798
|
+
return "A compaction checkpoint was already pending \u2014 no new compaction was needed.";
|
|
2799
|
+
}
|
|
2800
|
+
if (summaries.length === 0) {
|
|
2801
|
+
return "Nothing to compact \u2014 the conversation is already fully summarized.";
|
|
2802
|
+
}
|
|
2803
|
+
if (summaries.length === 1 && summaries[0].name === "conversation") {
|
|
2804
|
+
return summaries[0].text;
|
|
2805
|
+
}
|
|
2806
|
+
return summaries.map((s) => `## ${SUMMARY_SECTION_LABELS[s.name] ?? s.name}
|
|
2807
|
+
|
|
2808
|
+
${s.text}`).join("\n\n");
|
|
2809
|
+
}
|
|
2760
2810
|
function setCompactionListener(l) {
|
|
2761
2811
|
listener = l;
|
|
2762
2812
|
}
|
|
@@ -2766,18 +2816,24 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2766
2816
|
}
|
|
2767
2817
|
if (pending) {
|
|
2768
2818
|
log4.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
2769
|
-
return Promise.resolve();
|
|
2819
|
+
return Promise.resolve(null);
|
|
2770
2820
|
}
|
|
2771
|
-
const { blocking = false, requestId, model } = opts;
|
|
2772
|
-
listener?.({ type: "started", blocking, requestId });
|
|
2821
|
+
const { blocking = false, requestId, model, origin, toolCallId } = opts;
|
|
2822
|
+
listener?.({ type: "started", blocking, requestId, origin, toolCallId });
|
|
2773
2823
|
inflightCompaction = compactConversation(
|
|
2774
2824
|
state.messages,
|
|
2775
2825
|
apiConfig,
|
|
2776
2826
|
resolveModel("conversationSummarizer", state.models, model)
|
|
2777
2827
|
).then((result) => {
|
|
2778
2828
|
pending = result;
|
|
2779
|
-
|
|
2829
|
+
const summaries = summariesOf(result);
|
|
2830
|
+
listener?.({
|
|
2831
|
+
type: "complete",
|
|
2832
|
+
requestId,
|
|
2833
|
+
...summaries.length > 0 && { summaries }
|
|
2834
|
+
});
|
|
2780
2835
|
log4.info("Compaction complete");
|
|
2836
|
+
return summaries;
|
|
2781
2837
|
}).catch((err) => {
|
|
2782
2838
|
const message = err.message || "Compaction failed";
|
|
2783
2839
|
listener?.({ type: "complete", error: message, requestId });
|
|
@@ -2788,7 +2844,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
2788
2844
|
});
|
|
2789
2845
|
return inflightCompaction;
|
|
2790
2846
|
}
|
|
2791
|
-
var log4, pending, inflightCompaction, listener;
|
|
2847
|
+
var log4, pending, inflightCompaction, SUMMARY_SECTION_LABELS, listener;
|
|
2792
2848
|
var init_trigger = __esm({
|
|
2793
2849
|
"src/compaction/trigger.ts"() {
|
|
2794
2850
|
"use strict";
|
|
@@ -2799,6 +2855,11 @@ var init_trigger = __esm({
|
|
|
2799
2855
|
log4 = createLogger("compaction:trigger");
|
|
2800
2856
|
pending = null;
|
|
2801
2857
|
inflightCompaction = null;
|
|
2858
|
+
SUMMARY_SECTION_LABELS = {
|
|
2859
|
+
conversation: "Conversation",
|
|
2860
|
+
visualDesignExpert: "Design Agent Thread",
|
|
2861
|
+
productVision: "Roadmap Agent Thread"
|
|
2862
|
+
};
|
|
2802
2863
|
listener = null;
|
|
2803
2864
|
}
|
|
2804
2865
|
});
|
|
@@ -2810,6 +2871,12 @@ var init_compactConversation = __esm({
|
|
|
2810
2871
|
"use strict";
|
|
2811
2872
|
init_trigger();
|
|
2812
2873
|
compactConversationTool = {
|
|
2874
|
+
backgroundOnly: true,
|
|
2875
|
+
// Silent: the completed summary is written onto the tool block for the UI
|
|
2876
|
+
// detail view only. The model must never receive it as a message — it gets
|
|
2877
|
+
// the summary the correct way, as the checkpoint prefix in every later API
|
|
2878
|
+
// call (cleanMessagesForApi).
|
|
2879
|
+
backgroundNotify: "silent",
|
|
2813
2880
|
definition: {
|
|
2814
2881
|
clearable: false,
|
|
2815
2882
|
name: "compactConversation",
|
|
@@ -2823,11 +2890,32 @@ var init_compactConversation = __esm({
|
|
|
2823
2890
|
if (!context?.conversationMessages || !context.apiConfig) {
|
|
2824
2891
|
return "Error: compaction requires execution context.";
|
|
2825
2892
|
}
|
|
2893
|
+
const { toolCallId, onBackgroundComplete, toolRegistry } = context;
|
|
2826
2894
|
triggerCompaction(
|
|
2827
2895
|
{ messages: context.conversationMessages },
|
|
2828
2896
|
context.apiConfig,
|
|
2829
|
-
|
|
2830
|
-
|
|
2897
|
+
// origin/toolCallId tell the headless listener a real block exists —
|
|
2898
|
+
// it must not synthesize a UI-only one for this compaction.
|
|
2899
|
+
{
|
|
2900
|
+
blocking: false,
|
|
2901
|
+
requestId: context.requestId,
|
|
2902
|
+
origin: "tool",
|
|
2903
|
+
toolCallId
|
|
2904
|
+
}
|
|
2905
|
+
).then((summaries) => {
|
|
2906
|
+
onBackgroundComplete?.(
|
|
2907
|
+
toolCallId,
|
|
2908
|
+
"compactConversation",
|
|
2909
|
+
formatSummariesResult(summaries)
|
|
2910
|
+
);
|
|
2911
|
+
}).catch((err) => {
|
|
2912
|
+
onBackgroundComplete?.(
|
|
2913
|
+
toolCallId,
|
|
2914
|
+
"compactConversation",
|
|
2915
|
+
`Error: ${err.message || "Compaction failed"}`
|
|
2916
|
+
);
|
|
2917
|
+
}).finally(() => {
|
|
2918
|
+
toolRegistry?.unregister(toolCallId);
|
|
2831
2919
|
});
|
|
2832
2920
|
return "Compaction started in the background.";
|
|
2833
2921
|
}
|
|
@@ -4337,16 +4425,48 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
4337
4425
|
});
|
|
4338
4426
|
|
|
4339
4427
|
// src/statusWatcher.ts
|
|
4428
|
+
function sanitizeStatusText(text) {
|
|
4429
|
+
if (!text) {
|
|
4430
|
+
return "";
|
|
4431
|
+
}
|
|
4432
|
+
for (const marker of INTERNAL_PAYLOAD_MARKERS) {
|
|
4433
|
+
if (text.includes(marker)) {
|
|
4434
|
+
return "";
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
const truncIdx = text.indexOf("(tool result truncated at");
|
|
4438
|
+
return truncIdx === -1 ? text : text.slice(0, truncIdx);
|
|
4439
|
+
}
|
|
4340
4440
|
function startStatusWatcher(config) {
|
|
4341
4441
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
4342
4442
|
let inflight2 = false;
|
|
4343
4443
|
let stopped = false;
|
|
4344
4444
|
let pauseCount = 0;
|
|
4345
4445
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
4446
|
+
let consecutiveFailures = 0;
|
|
4447
|
+
let backoffMs = 0;
|
|
4448
|
+
let nextAllowedAt = 0;
|
|
4449
|
+
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
4450
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
4451
|
+
function recordFailure() {
|
|
4452
|
+
consecutiveFailures++;
|
|
4453
|
+
backoffMs = Math.min(
|
|
4454
|
+
backoffMs === 0 ? interval : backoffMs * 2,
|
|
4455
|
+
MAX_BACKOFF_MS
|
|
4456
|
+
);
|
|
4457
|
+
nextAllowedAt = Date.now() + backoffMs;
|
|
4458
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !stopped) {
|
|
4459
|
+
stopped = true;
|
|
4460
|
+
clearInterval(timer);
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4346
4463
|
async function tick() {
|
|
4347
4464
|
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
4348
4465
|
return;
|
|
4349
4466
|
}
|
|
4467
|
+
if (Date.now() < nextAllowedAt) {
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4350
4470
|
inflight2 = true;
|
|
4351
4471
|
try {
|
|
4352
4472
|
const context = getContext();
|
|
@@ -4362,18 +4482,29 @@ function startStatusWatcher(config) {
|
|
|
4362
4482
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
4363
4483
|
signal
|
|
4364
4484
|
});
|
|
4365
|
-
if (
|
|
4485
|
+
if (stopped) {
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
if (!res.ok) {
|
|
4489
|
+
recordFailure();
|
|
4366
4490
|
return;
|
|
4367
4491
|
}
|
|
4368
4492
|
const data = await res.json();
|
|
4369
4493
|
if (!data.label) {
|
|
4494
|
+
recordFailure();
|
|
4370
4495
|
return;
|
|
4371
4496
|
}
|
|
4497
|
+
consecutiveFailures = 0;
|
|
4498
|
+
backoffMs = 0;
|
|
4499
|
+
nextAllowedAt = 0;
|
|
4372
4500
|
if (pauseCount > 0 || stopped || signal?.aborted) {
|
|
4373
4501
|
return;
|
|
4374
4502
|
}
|
|
4375
4503
|
onStatus(data.label);
|
|
4376
4504
|
} catch {
|
|
4505
|
+
if (!stopped && !signal?.aborted) {
|
|
4506
|
+
recordFailure();
|
|
4507
|
+
}
|
|
4377
4508
|
} finally {
|
|
4378
4509
|
inflight2 = false;
|
|
4379
4510
|
}
|
|
@@ -4394,9 +4525,17 @@ function startStatusWatcher(config) {
|
|
|
4394
4525
|
}
|
|
4395
4526
|
};
|
|
4396
4527
|
}
|
|
4528
|
+
var INTERNAL_PAYLOAD_MARKERS;
|
|
4397
4529
|
var init_statusWatcher = __esm({
|
|
4398
4530
|
"src/statusWatcher.ts"() {
|
|
4399
4531
|
"use strict";
|
|
4532
|
+
INTERNAL_PAYLOAD_MARKERS = [
|
|
4533
|
+
"[USER CANCELLED]",
|
|
4534
|
+
"[INTERRUPTED]",
|
|
4535
|
+
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
4536
|
+
"<background_results>",
|
|
4537
|
+
"<tool_result"
|
|
4538
|
+
];
|
|
4400
4539
|
}
|
|
4401
4540
|
});
|
|
4402
4541
|
|
|
@@ -4465,329 +4604,341 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4465
4604
|
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
4466
4605
|
}
|
|
4467
4606
|
let lastToolResult = "";
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
}
|
|
4607
|
+
let watchedBlocks = [];
|
|
4608
|
+
let watchedToolNames = "";
|
|
4609
|
+
const statusWatcher = startStatusWatcher({
|
|
4610
|
+
apiConfig,
|
|
4611
|
+
getContext: () => {
|
|
4612
|
+
const parts = [];
|
|
4613
|
+
if (task) {
|
|
4614
|
+
parts.push(`Task: ${sanitizeStatusText(task).slice(-200)}`);
|
|
4615
|
+
}
|
|
4616
|
+
const text = getPartialText(watchedBlocks);
|
|
4617
|
+
if (text) {
|
|
4618
|
+
parts.push(`Assistant text: ${text.slice(-500)}`);
|
|
4619
|
+
}
|
|
4620
|
+
if (watchedToolNames) {
|
|
4621
|
+
parts.push(`Tool: ${watchedToolNames}`);
|
|
4622
|
+
}
|
|
4623
|
+
const toolResult = sanitizeStatusText(lastToolResult);
|
|
4624
|
+
if (toolResult) {
|
|
4625
|
+
parts.push(`Tool result: ${toolResult.slice(-200)}`);
|
|
4626
|
+
}
|
|
4627
|
+
return parts.join("\n");
|
|
4628
|
+
},
|
|
4629
|
+
onStatus: (label) => emit({ type: "status", message: label }),
|
|
4630
|
+
signal
|
|
4631
|
+
});
|
|
4632
|
+
try {
|
|
4633
|
+
while (true) {
|
|
4634
|
+
turns++;
|
|
4635
|
+
if (signal?.aborted) {
|
|
4636
|
+
return abortResult([]);
|
|
4637
|
+
}
|
|
4638
|
+
const iterStart = Date.now();
|
|
4639
|
+
const contentBlocks = [];
|
|
4640
|
+
let thinkingStartedAt = 0;
|
|
4641
|
+
let lastThinkingRelatedStartedAt;
|
|
4642
|
+
let stopReason = "end_turn";
|
|
4643
|
+
let lastUsage;
|
|
4644
|
+
let lastProviderMetadata;
|
|
4645
|
+
let lastModelId;
|
|
4646
|
+
watchedBlocks = contentBlocks;
|
|
4647
|
+
watchedToolNames = "";
|
|
4648
|
+
try {
|
|
4649
|
+
for await (const event of streamChatWithRetry(
|
|
4650
|
+
{
|
|
4651
|
+
...apiConfig,
|
|
4652
|
+
model,
|
|
4653
|
+
requestId,
|
|
4654
|
+
subAgentId,
|
|
4655
|
+
system: fullSystem,
|
|
4656
|
+
messages: cleanMessagesForApi(messages),
|
|
4657
|
+
tools: tools2,
|
|
4658
|
+
signal
|
|
4659
|
+
},
|
|
4660
|
+
{
|
|
4661
|
+
onRetry: (attempt) => emit({
|
|
4662
|
+
type: "status",
|
|
4663
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
4664
|
+
})
|
|
4665
|
+
}
|
|
4666
|
+
)) {
|
|
4667
|
+
if (signal?.aborted) {
|
|
4668
|
+
break;
|
|
4669
|
+
}
|
|
4670
|
+
switch (event.type) {
|
|
4671
|
+
case "text": {
|
|
4672
|
+
const lastBlock = contentBlocks.at(-1);
|
|
4673
|
+
if (lastBlock?.type === "text") {
|
|
4674
|
+
lastBlock.text += event.text;
|
|
4675
|
+
} else {
|
|
4676
|
+
contentBlocks.push({
|
|
4677
|
+
type: "text",
|
|
4678
|
+
text: event.text,
|
|
4679
|
+
startedAt: event.ts
|
|
4680
|
+
});
|
|
4681
|
+
}
|
|
4682
|
+
emit({ type: "text", text: event.text });
|
|
4683
|
+
break;
|
|
4684
|
+
}
|
|
4685
|
+
case "thinking":
|
|
4686
|
+
if (!thinkingStartedAt) {
|
|
4687
|
+
thinkingStartedAt = event.ts;
|
|
4688
|
+
}
|
|
4689
|
+
emit({ type: "thinking", text: event.text });
|
|
4690
|
+
break;
|
|
4691
|
+
case "thinking_complete":
|
|
4692
|
+
contentBlocks.push({
|
|
4693
|
+
type: "thinking",
|
|
4694
|
+
thinking: event.thinking,
|
|
4695
|
+
signature: event.signature,
|
|
4696
|
+
startedAt: thinkingStartedAt,
|
|
4697
|
+
completedAt: event.ts
|
|
4698
|
+
});
|
|
4699
|
+
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
4700
|
+
thinkingStartedAt = 0;
|
|
4701
|
+
break;
|
|
4702
|
+
case "redacted_thinking_complete": {
|
|
4703
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
4704
|
+
contentBlocks.push({
|
|
4705
|
+
type: "redacted_thinking",
|
|
4706
|
+
data: event.data,
|
|
4707
|
+
startedAt,
|
|
4708
|
+
completedAt: event.ts
|
|
4709
|
+
});
|
|
4710
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
4711
|
+
break;
|
|
4712
|
+
}
|
|
4713
|
+
case "tool_use":
|
|
4714
|
+
contentBlocks.push({
|
|
4715
|
+
type: "tool",
|
|
4716
|
+
id: event.id,
|
|
4717
|
+
name: event.name,
|
|
4718
|
+
input: event.input,
|
|
4719
|
+
startedAt: Date.now()
|
|
4720
|
+
});
|
|
4721
|
+
emit({
|
|
4722
|
+
type: "tool_start",
|
|
4723
|
+
id: event.id,
|
|
4724
|
+
name: event.name,
|
|
4725
|
+
input: event.input
|
|
4726
|
+
});
|
|
4727
|
+
break;
|
|
4728
|
+
case "done":
|
|
4729
|
+
stopReason = event.stopReason;
|
|
4730
|
+
lastUsage = {
|
|
4731
|
+
inputTokens: event.usage.inputTokens,
|
|
4732
|
+
outputTokens: event.usage.outputTokens,
|
|
4733
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4734
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4735
|
+
llmCalls: 1
|
|
4736
|
+
};
|
|
4737
|
+
lastProviderMetadata = event.providerMetadata;
|
|
4738
|
+
lastModelId = event.modelId;
|
|
4739
|
+
recordUsage({
|
|
4740
|
+
ts: Date.now(),
|
|
4741
|
+
requestId,
|
|
4742
|
+
agentName: subAgentId || "sub-agent",
|
|
4743
|
+
parentToolId,
|
|
4744
|
+
modelId: event.modelId,
|
|
4745
|
+
inputTokens: event.usage.inputTokens,
|
|
4746
|
+
outputTokens: event.usage.outputTokens,
|
|
4747
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4748
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4749
|
+
cost: nanoToDollars(event.cost),
|
|
4750
|
+
billingEvents: event.billingEvents,
|
|
4751
|
+
durationMs: Date.now() - iterStart,
|
|
4752
|
+
toolNames: contentBlocks.filter(
|
|
4753
|
+
(b) => b.type === "tool"
|
|
4754
|
+
).map((b) => b.name)
|
|
4755
|
+
});
|
|
4756
|
+
break;
|
|
4757
|
+
case "error":
|
|
4758
|
+
statusWatcher.stop();
|
|
4759
|
+
return {
|
|
4760
|
+
text: `Error: ${event.error}`,
|
|
4761
|
+
messages: thisInvocation()
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4521
4764
|
}
|
|
4522
|
-
)
|
|
4523
|
-
if (signal?.aborted) {
|
|
4524
|
-
|
|
4765
|
+
} catch (err) {
|
|
4766
|
+
if (!signal?.aborted) {
|
|
4767
|
+
throw err;
|
|
4525
4768
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4769
|
+
}
|
|
4770
|
+
if (signal?.aborted) {
|
|
4771
|
+
statusWatcher.stop();
|
|
4772
|
+
return abortResult(contentBlocks);
|
|
4773
|
+
}
|
|
4774
|
+
messages.push({
|
|
4775
|
+
role: "assistant",
|
|
4776
|
+
content: contentBlocks,
|
|
4777
|
+
...lastUsage ? { usage: lastUsage } : {},
|
|
4778
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
4779
|
+
model: lastModelId ?? model
|
|
4780
|
+
});
|
|
4781
|
+
const toolCalls = contentBlocks.filter(
|
|
4782
|
+
(b) => b.type === "tool"
|
|
4783
|
+
);
|
|
4784
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4785
|
+
statusWatcher.stop();
|
|
4786
|
+
const text = getPartialText(contentBlocks);
|
|
4787
|
+
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
4788
|
+
return {
|
|
4789
|
+
text,
|
|
4790
|
+
messages: thisInvocation(),
|
|
4791
|
+
...hasArtifacts ? { artifacts } : {}
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
log8.info("Tools executing", {
|
|
4795
|
+
requestId,
|
|
4796
|
+
parentToolId,
|
|
4797
|
+
count: toolCalls.length,
|
|
4798
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
4799
|
+
});
|
|
4800
|
+
watchedToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
4801
|
+
const results = await Promise.all(
|
|
4802
|
+
toolCalls.map(async (tc) => {
|
|
4803
|
+
if (signal?.aborted) {
|
|
4804
|
+
return {
|
|
4805
|
+
id: tc.id,
|
|
4806
|
+
result: USER_CANCELLED_RESULT,
|
|
4807
|
+
isError: true
|
|
4808
|
+
};
|
|
4809
|
+
}
|
|
4810
|
+
let settle;
|
|
4811
|
+
const resultPromise = new Promise((res) => {
|
|
4812
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4813
|
+
});
|
|
4814
|
+
let toolAbort = new AbortController();
|
|
4815
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
4816
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4817
|
+
let settled = false;
|
|
4818
|
+
const safeSettle = (result, isError) => {
|
|
4819
|
+
if (settled) {
|
|
4820
|
+
return;
|
|
4821
|
+
}
|
|
4822
|
+
settled = true;
|
|
4823
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
4824
|
+
settle(result, isError);
|
|
4825
|
+
};
|
|
4826
|
+
const run2 = async (input) => {
|
|
4827
|
+
try {
|
|
4828
|
+
let result;
|
|
4829
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
4830
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4831
|
+
} else {
|
|
4832
|
+
const onLog = (line) => emit({
|
|
4833
|
+
type: "tool_input_delta",
|
|
4834
|
+
id: tc.id,
|
|
4835
|
+
name: tc.name,
|
|
4836
|
+
result: line
|
|
4837
|
+
});
|
|
4838
|
+
result = await executeTool2(
|
|
4839
|
+
tc.name,
|
|
4840
|
+
input,
|
|
4841
|
+
tc.id,
|
|
4842
|
+
onLog,
|
|
4843
|
+
subAgentMessages
|
|
4844
|
+
);
|
|
4845
|
+
}
|
|
4846
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
4847
|
+
} catch (err) {
|
|
4848
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
4849
|
+
}
|
|
4850
|
+
};
|
|
4851
|
+
const entry = {
|
|
4852
|
+
id: tc.id,
|
|
4853
|
+
name: tc.name,
|
|
4854
|
+
input: tc.input,
|
|
4855
|
+
parentToolId,
|
|
4856
|
+
abortController: toolAbort,
|
|
4857
|
+
startedAt: Date.now(),
|
|
4858
|
+
settle: safeSettle,
|
|
4859
|
+
rerun: (newInput) => {
|
|
4860
|
+
settled = false;
|
|
4861
|
+
toolAbort = new AbortController();
|
|
4862
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4863
|
+
once: true
|
|
4536
4864
|
});
|
|
4865
|
+
entry.abortController = toolAbort;
|
|
4866
|
+
entry.input = newInput;
|
|
4867
|
+
run2(newInput);
|
|
4537
4868
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4869
|
+
};
|
|
4870
|
+
toolRegistry?.register(entry);
|
|
4871
|
+
const toolStart = Date.now();
|
|
4872
|
+
run2(tc.input);
|
|
4873
|
+
const r = await resultPromise;
|
|
4874
|
+
toolRegistry?.unregister(tc.id);
|
|
4875
|
+
log8.info("Tool completed", {
|
|
4876
|
+
requestId,
|
|
4877
|
+
parentToolId,
|
|
4878
|
+
toolCallId: tc.id,
|
|
4879
|
+
name: tc.name,
|
|
4880
|
+
durationMs: Date.now() - toolStart,
|
|
4881
|
+
isError: r.isError
|
|
4882
|
+
});
|
|
4883
|
+
emit({
|
|
4884
|
+
type: "tool_done",
|
|
4885
|
+
id: tc.id,
|
|
4886
|
+
name: tc.name,
|
|
4887
|
+
result: r.result,
|
|
4888
|
+
isError: r.isError
|
|
4889
|
+
});
|
|
4890
|
+
return r;
|
|
4891
|
+
})
|
|
4892
|
+
);
|
|
4893
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
4894
|
+
for (const r of results) {
|
|
4895
|
+
const block = contentBlocks.find(
|
|
4896
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
4897
|
+
);
|
|
4898
|
+
if (block?.type === "tool") {
|
|
4899
|
+
block.result = r.result;
|
|
4900
|
+
block.isError = r.isError;
|
|
4901
|
+
block.completedAt = Date.now();
|
|
4902
|
+
const innerMsgs = subAgentMessages.get(r.id);
|
|
4903
|
+
if (innerMsgs) {
|
|
4904
|
+
block.subAgentMessages = innerMsgs;
|
|
4540
4905
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4906
|
+
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4907
|
+
try {
|
|
4908
|
+
artifacts[block.name] = JSON.parse(r.result);
|
|
4909
|
+
} catch {
|
|
4544
4910
|
}
|
|
4545
|
-
emit({ type: "thinking", text: event.text });
|
|
4546
|
-
break;
|
|
4547
|
-
case "thinking_complete":
|
|
4548
|
-
contentBlocks.push({
|
|
4549
|
-
type: "thinking",
|
|
4550
|
-
thinking: event.thinking,
|
|
4551
|
-
signature: event.signature,
|
|
4552
|
-
startedAt: thinkingStartedAt,
|
|
4553
|
-
completedAt: event.ts
|
|
4554
|
-
});
|
|
4555
|
-
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
4556
|
-
thinkingStartedAt = 0;
|
|
4557
|
-
break;
|
|
4558
|
-
case "redacted_thinking_complete": {
|
|
4559
|
-
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
4560
|
-
contentBlocks.push({
|
|
4561
|
-
type: "redacted_thinking",
|
|
4562
|
-
data: event.data,
|
|
4563
|
-
startedAt,
|
|
4564
|
-
completedAt: event.ts
|
|
4565
|
-
});
|
|
4566
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
4567
|
-
break;
|
|
4568
4911
|
}
|
|
4569
|
-
case "tool_use":
|
|
4570
|
-
contentBlocks.push({
|
|
4571
|
-
type: "tool",
|
|
4572
|
-
id: event.id,
|
|
4573
|
-
name: event.name,
|
|
4574
|
-
input: event.input,
|
|
4575
|
-
startedAt: Date.now()
|
|
4576
|
-
});
|
|
4577
|
-
emit({
|
|
4578
|
-
type: "tool_start",
|
|
4579
|
-
id: event.id,
|
|
4580
|
-
name: event.name,
|
|
4581
|
-
input: event.input
|
|
4582
|
-
});
|
|
4583
|
-
break;
|
|
4584
|
-
case "done":
|
|
4585
|
-
stopReason = event.stopReason;
|
|
4586
|
-
lastUsage = {
|
|
4587
|
-
inputTokens: event.usage.inputTokens,
|
|
4588
|
-
outputTokens: event.usage.outputTokens,
|
|
4589
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4590
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4591
|
-
llmCalls: 1
|
|
4592
|
-
};
|
|
4593
|
-
lastProviderMetadata = event.providerMetadata;
|
|
4594
|
-
lastModelId = event.modelId;
|
|
4595
|
-
recordUsage({
|
|
4596
|
-
ts: Date.now(),
|
|
4597
|
-
requestId,
|
|
4598
|
-
agentName: subAgentId || "sub-agent",
|
|
4599
|
-
parentToolId,
|
|
4600
|
-
modelId: event.modelId,
|
|
4601
|
-
inputTokens: event.usage.inputTokens,
|
|
4602
|
-
outputTokens: event.usage.outputTokens,
|
|
4603
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
4604
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
4605
|
-
cost: nanoToDollars(event.cost),
|
|
4606
|
-
billingEvents: event.billingEvents,
|
|
4607
|
-
durationMs: Date.now() - iterStart,
|
|
4608
|
-
toolNames: contentBlocks.filter(
|
|
4609
|
-
(b) => b.type === "tool"
|
|
4610
|
-
).map((b) => b.name)
|
|
4611
|
-
});
|
|
4612
|
-
break;
|
|
4613
|
-
case "error":
|
|
4614
|
-
return {
|
|
4615
|
-
text: `Error: ${event.error}`,
|
|
4616
|
-
messages: thisInvocation()
|
|
4617
|
-
};
|
|
4618
4912
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4913
|
+
messages.push({
|
|
4914
|
+
role: "user",
|
|
4915
|
+
content: r.result,
|
|
4916
|
+
toolCallId: r.id,
|
|
4917
|
+
isToolError: r.isError
|
|
4918
|
+
});
|
|
4623
4919
|
}
|
|
4624
4920
|
}
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4921
|
+
} finally {
|
|
4922
|
+
statusWatcher.stop();
|
|
4923
|
+
}
|
|
4924
|
+
};
|
|
4925
|
+
const wrapRun = async () => {
|
|
4926
|
+
try {
|
|
4927
|
+
const result = await run();
|
|
4928
|
+
log8.info("Sub-agent complete", {
|
|
4929
|
+
requestId,
|
|
4930
|
+
parentToolId,
|
|
4931
|
+
agentName,
|
|
4932
|
+
durationMs: Date.now() - runStart,
|
|
4933
|
+
turns
|
|
4635
4934
|
});
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
4640
|
-
statusWatcher.stop();
|
|
4641
|
-
const text = getPartialText(contentBlocks);
|
|
4642
|
-
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
4643
|
-
return {
|
|
4644
|
-
text,
|
|
4645
|
-
messages: thisInvocation(),
|
|
4646
|
-
...hasArtifacts ? { artifacts } : {}
|
|
4647
|
-
};
|
|
4648
|
-
}
|
|
4649
|
-
log8.info("Tools executing", {
|
|
4935
|
+
return result;
|
|
4936
|
+
} catch (err) {
|
|
4937
|
+
log8.warn("Sub-agent error", {
|
|
4650
4938
|
requestId,
|
|
4651
4939
|
parentToolId,
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
});
|
|
4655
|
-
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
4656
|
-
const results = await Promise.all(
|
|
4657
|
-
toolCalls.map(async (tc) => {
|
|
4658
|
-
if (signal?.aborted) {
|
|
4659
|
-
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
4660
|
-
}
|
|
4661
|
-
let settle;
|
|
4662
|
-
const resultPromise = new Promise((res) => {
|
|
4663
|
-
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
4664
|
-
});
|
|
4665
|
-
let toolAbort = new AbortController();
|
|
4666
|
-
const cascadeAbort = () => toolAbort.abort();
|
|
4667
|
-
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
4668
|
-
let settled = false;
|
|
4669
|
-
const safeSettle = (result, isError) => {
|
|
4670
|
-
if (settled) {
|
|
4671
|
-
return;
|
|
4672
|
-
}
|
|
4673
|
-
settled = true;
|
|
4674
|
-
signal?.removeEventListener("abort", cascadeAbort);
|
|
4675
|
-
settle(result, isError);
|
|
4676
|
-
};
|
|
4677
|
-
const run2 = async (input) => {
|
|
4678
|
-
try {
|
|
4679
|
-
let result;
|
|
4680
|
-
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
4681
|
-
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
4682
|
-
} else {
|
|
4683
|
-
const onLog = (line) => emit({
|
|
4684
|
-
type: "tool_input_delta",
|
|
4685
|
-
id: tc.id,
|
|
4686
|
-
name: tc.name,
|
|
4687
|
-
result: line
|
|
4688
|
-
});
|
|
4689
|
-
result = await executeTool2(
|
|
4690
|
-
tc.name,
|
|
4691
|
-
input,
|
|
4692
|
-
tc.id,
|
|
4693
|
-
onLog,
|
|
4694
|
-
subAgentMessages
|
|
4695
|
-
);
|
|
4696
|
-
}
|
|
4697
|
-
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
4698
|
-
} catch (err) {
|
|
4699
|
-
safeSettle(`Error: ${err.message}`, true);
|
|
4700
|
-
}
|
|
4701
|
-
};
|
|
4702
|
-
const entry = {
|
|
4703
|
-
id: tc.id,
|
|
4704
|
-
name: tc.name,
|
|
4705
|
-
input: tc.input,
|
|
4706
|
-
parentToolId,
|
|
4707
|
-
abortController: toolAbort,
|
|
4708
|
-
startedAt: Date.now(),
|
|
4709
|
-
settle: safeSettle,
|
|
4710
|
-
rerun: (newInput) => {
|
|
4711
|
-
settled = false;
|
|
4712
|
-
toolAbort = new AbortController();
|
|
4713
|
-
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4714
|
-
once: true
|
|
4715
|
-
});
|
|
4716
|
-
entry.abortController = toolAbort;
|
|
4717
|
-
entry.input = newInput;
|
|
4718
|
-
run2(newInput);
|
|
4719
|
-
}
|
|
4720
|
-
};
|
|
4721
|
-
toolRegistry?.register(entry);
|
|
4722
|
-
const toolStart = Date.now();
|
|
4723
|
-
run2(tc.input);
|
|
4724
|
-
const r = await resultPromise;
|
|
4725
|
-
toolRegistry?.unregister(tc.id);
|
|
4726
|
-
log8.info("Tool completed", {
|
|
4727
|
-
requestId,
|
|
4728
|
-
parentToolId,
|
|
4729
|
-
toolCallId: tc.id,
|
|
4730
|
-
name: tc.name,
|
|
4731
|
-
durationMs: Date.now() - toolStart,
|
|
4732
|
-
isError: r.isError
|
|
4733
|
-
});
|
|
4734
|
-
emit({
|
|
4735
|
-
type: "tool_done",
|
|
4736
|
-
id: tc.id,
|
|
4737
|
-
name: tc.name,
|
|
4738
|
-
result: r.result,
|
|
4739
|
-
isError: r.isError
|
|
4740
|
-
});
|
|
4741
|
-
return r;
|
|
4742
|
-
})
|
|
4743
|
-
);
|
|
4744
|
-
statusWatcher.stop();
|
|
4745
|
-
lastToolResult = results.at(-1)?.result ?? "";
|
|
4746
|
-
for (const r of results) {
|
|
4747
|
-
const block = contentBlocks.find(
|
|
4748
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
4749
|
-
);
|
|
4750
|
-
if (block?.type === "tool") {
|
|
4751
|
-
block.result = r.result;
|
|
4752
|
-
block.isError = r.isError;
|
|
4753
|
-
block.completedAt = Date.now();
|
|
4754
|
-
const innerMsgs = subAgentMessages.get(r.id);
|
|
4755
|
-
if (innerMsgs) {
|
|
4756
|
-
block.subAgentMessages = innerMsgs;
|
|
4757
|
-
}
|
|
4758
|
-
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4759
|
-
try {
|
|
4760
|
-
artifacts[block.name] = JSON.parse(r.result);
|
|
4761
|
-
} catch {
|
|
4762
|
-
}
|
|
4763
|
-
}
|
|
4764
|
-
}
|
|
4765
|
-
messages.push({
|
|
4766
|
-
role: "user",
|
|
4767
|
-
content: r.result,
|
|
4768
|
-
toolCallId: r.id,
|
|
4769
|
-
isToolError: r.isError
|
|
4770
|
-
});
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
4773
|
-
};
|
|
4774
|
-
const wrapRun = async () => {
|
|
4775
|
-
try {
|
|
4776
|
-
const result = await run();
|
|
4777
|
-
log8.info("Sub-agent complete", {
|
|
4778
|
-
requestId,
|
|
4779
|
-
parentToolId,
|
|
4780
|
-
agentName,
|
|
4781
|
-
durationMs: Date.now() - runStart,
|
|
4782
|
-
turns
|
|
4783
|
-
});
|
|
4784
|
-
return result;
|
|
4785
|
-
} catch (err) {
|
|
4786
|
-
log8.warn("Sub-agent error", {
|
|
4787
|
-
requestId,
|
|
4788
|
-
parentToolId,
|
|
4789
|
-
agentName,
|
|
4790
|
-
error: err.message
|
|
4940
|
+
agentName,
|
|
4941
|
+
error: err.message
|
|
4791
4942
|
});
|
|
4792
4943
|
throw err;
|
|
4793
4944
|
}
|
|
@@ -4934,7 +5085,7 @@ var init_tools2 = __esm({
|
|
|
4934
5085
|
"screenshotViewport",
|
|
4935
5086
|
"setViewport"
|
|
4936
5087
|
],
|
|
4937
|
-
description: 'snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for
|
|
5088
|
+
description: 'snapshot: accessibility tree of the page (waits for network to settle). click: click an element (animated cursor, full event sequence). type: type text into input (one char at a time, works with React/Vue/Svelte). select: select a dropdown option by text. wait: wait for an element to appear (polls 100ms, waits for network). navigate: navigate to a URL within the app (waits for the route, subsequent steps run on the new page; soft in-app route change by default \u2014 pass `fresh: true` for a real full page load; the result reports the URL actually landed on, so app redirects are visible). evaluate: run JS in the page. styles: read computed CSS styles from elements (pass properties array with camelCase names, or omit for defaults). screenshotFullPage: screenshot of the whole page top-to-bottom (returns a CDN url with dimensions and a written analysis). screenshotViewport: screenshot of just the visible viewport \u2014 pass `scrollToSelector` (or `scrollY`) on this step to scroll a section into view and capture it in one atomic step (no separate scroll needed). setViewport: switch the browser between desktop and mobile rendering (pass `mode`: "desktop" or "mobile"). Reloads the page so responsive layouts, media queries, and matchMedia re-evaluate \u2014 use it to QA mobile/responsive views.'
|
|
4938
5089
|
},
|
|
4939
5090
|
ref: {
|
|
4940
5091
|
type: "string",
|
|
@@ -4976,6 +5127,10 @@ var init_tools2 = __esm({
|
|
|
4976
5127
|
type: "string",
|
|
4977
5128
|
description: 'For navigate: the URL to navigate to (e.g., "/quiz", "/settings").'
|
|
4978
5129
|
},
|
|
5130
|
+
fresh: {
|
|
5131
|
+
type: "boolean",
|
|
5132
|
+
description: "For navigate: force a real full page load (fresh document) instead of a soft in-app route change. Use when testing what a user sees on entry \u2014 landing pages, join/invite links, signed-out views \u2014 where reusing the SPA\u2019s in-memory state would test the wrong thing."
|
|
5133
|
+
},
|
|
4979
5134
|
properties: {
|
|
4980
5135
|
type: "array",
|
|
4981
5136
|
items: { type: "string" },
|
|
@@ -7092,6 +7247,129 @@ var init_codeSanityCheck = __esm({
|
|
|
7092
7247
|
}
|
|
7093
7248
|
});
|
|
7094
7249
|
|
|
7250
|
+
// src/tools/spec/writeBuildOverview.ts
|
|
7251
|
+
import fs19 from "fs";
|
|
7252
|
+
function initialDelivery() {
|
|
7253
|
+
return `### Your deliverable
|
|
7254
|
+
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
7255
|
+
|
|
7256
|
+
<overview_shell>
|
|
7257
|
+
${OVERVIEW_SHELL}
|
|
7258
|
+
</overview_shell>
|
|
7259
|
+
|
|
7260
|
+
Then reply with a one-line summary of what you wrote.`;
|
|
7261
|
+
}
|
|
7262
|
+
function refreshDelivery() {
|
|
7263
|
+
return `### Your deliverable
|
|
7264
|
+
The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
|
|
7265
|
+
|
|
7266
|
+
Then reply with a one-line summary of what you changed.`;
|
|
7267
|
+
}
|
|
7268
|
+
async function renderBuildOverview(content, context, opts) {
|
|
7269
|
+
if (!content) {
|
|
7270
|
+
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7271
|
+
}
|
|
7272
|
+
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7273
|
+
const task = `<overview_copy>${content}</overview_copy>
|
|
7274
|
+
|
|
7275
|
+
${DESIGN_BRIEF}
|
|
7276
|
+
|
|
7277
|
+
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
7278
|
+
try {
|
|
7279
|
+
if (opts.background) {
|
|
7280
|
+
const result2 = await runDesignExpertRender(
|
|
7281
|
+
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
7282
|
+
context
|
|
7283
|
+
);
|
|
7284
|
+
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
7285
|
+
return result2.text;
|
|
7286
|
+
}
|
|
7287
|
+
const result = await runDesignExpertRender({ task }, context);
|
|
7288
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7289
|
+
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7290
|
+
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7291
|
+
${result.text}`;
|
|
7292
|
+
}
|
|
7293
|
+
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
7294
|
+
} catch (err) {
|
|
7295
|
+
return `Error generating build overview: ${err.message}`;
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
var OVERVIEW_FILE, DESIGN_BRIEF, OVERVIEW_SHELL, buildOverviewTool;
|
|
7299
|
+
var init_writeBuildOverview = __esm({
|
|
7300
|
+
"src/tools/spec/writeBuildOverview.ts"() {
|
|
7301
|
+
"use strict";
|
|
7302
|
+
init_designExpert();
|
|
7303
|
+
OVERVIEW_FILE = "src/overview.html";
|
|
7304
|
+
DESIGN_BRIEF = `We are building the Build Overview for this app \u2014 the home page of its Spec tab. It is a calm, dense, one-page reference of everything the app actually contains, including the parts the user can't see. It renders flush inside the Spec tab's content panel (the IDE supplies the surrounding nav).
|
|
7305
|
+
|
|
7306
|
+
Take the plain-language copy in <overview_copy> and lay it out and skin it into a single, beautiful, self-contained HTML document in the app's own brand.
|
|
7307
|
+
|
|
7308
|
+
### The single hard rule
|
|
7309
|
+
The copy in <overview_copy> is final \u2014 it was authored and edited before it reached you. Treat it as locked content to typeset, not a draft to improve. Reproduce the words exactly: do not rewrite, rephrase, shorten, expand, reorder, or "polish" them, and do not run them through any copy tool. This is the opposite of your usual role \u2014 here you own layout, typography, and visual design only, and the words (every number, name, label, claim, and sentence) are fixed. A single changed word or wrong number breaks this document's purpose.
|
|
7310
|
+
|
|
7311
|
+
### What it is (and is not)
|
|
7312
|
+
- A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
|
|
7313
|
+
- Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
|
|
7314
|
+
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
7315
|
+
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
7316
|
+
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
7317
|
+
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
7318
|
+
|
|
7319
|
+
### Constraints
|
|
7320
|
+
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
7321
|
+
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
7322
|
+
OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
7323
|
+
<html lang="en">
|
|
7324
|
+
<head>
|
|
7325
|
+
<meta charset="utf-8">
|
|
7326
|
+
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
7327
|
+
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
7328
|
+
The user-scalable=no viewport is intentional; leave it. -->
|
|
7329
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
7330
|
+
<title>Build Overview</title>
|
|
7331
|
+
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
7332
|
+
<style>
|
|
7333
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7334
|
+
</style>
|
|
7335
|
+
</head>
|
|
7336
|
+
<body>
|
|
7337
|
+
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
7338
|
+
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
7339
|
+
app's spec. -->
|
|
7340
|
+
</body>
|
|
7341
|
+
</html>`;
|
|
7342
|
+
buildOverviewTool = {
|
|
7343
|
+
definition: {
|
|
7344
|
+
clearable: false,
|
|
7345
|
+
name: "writeBuildOverview",
|
|
7346
|
+
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build; routine refreshes ride specSync's `refreshBuildOverview` flag instead, so call this directly only for the initial generation or when the user explicitly asks.",
|
|
7347
|
+
inputSchema: {
|
|
7348
|
+
type: "object",
|
|
7349
|
+
properties: {
|
|
7350
|
+
content: {
|
|
7351
|
+
type: "string",
|
|
7352
|
+
description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
|
|
7353
|
+
}
|
|
7354
|
+
},
|
|
7355
|
+
required: ["content"]
|
|
7356
|
+
}
|
|
7357
|
+
},
|
|
7358
|
+
async execute(input, context) {
|
|
7359
|
+
if (!context) {
|
|
7360
|
+
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
7361
|
+
}
|
|
7362
|
+
const content = (input.content ?? "").trim();
|
|
7363
|
+
const background = Boolean(content) && fs19.existsSync(OVERVIEW_FILE);
|
|
7364
|
+
if (background) {
|
|
7365
|
+
input.background = true;
|
|
7366
|
+
}
|
|
7367
|
+
return renderBuildOverview(content, context, { background });
|
|
7368
|
+
}
|
|
7369
|
+
};
|
|
7370
|
+
}
|
|
7371
|
+
});
|
|
7372
|
+
|
|
7095
7373
|
// src/subagents/specSync/tools.ts
|
|
7096
7374
|
var SPEC_SYNC_TOOLS;
|
|
7097
7375
|
var init_tools7 = __esm({
|
|
@@ -7137,6 +7415,7 @@ var init_specSync = __esm({
|
|
|
7137
7415
|
init_runner();
|
|
7138
7416
|
init_context();
|
|
7139
7417
|
init_tools8();
|
|
7418
|
+
init_writeBuildOverview();
|
|
7140
7419
|
init_tools7();
|
|
7141
7420
|
init_lock();
|
|
7142
7421
|
init_surfaces();
|
|
@@ -7149,16 +7428,23 @@ ${readAsset(
|
|
|
7149
7428
|
</mindstudio_flavored_markdown_spec_docs>`;
|
|
7150
7429
|
specSyncTool = {
|
|
7151
7430
|
backgroundOnly: true,
|
|
7431
|
+
// Fire-and-forget: completion never wakes the agent. The outcome rides the
|
|
7432
|
+
// next real turn as a hidden background_results note instead.
|
|
7433
|
+
backgroundNotify: "passive",
|
|
7152
7434
|
definition: {
|
|
7153
7435
|
clearable: false,
|
|
7154
7436
|
name: "specSync",
|
|
7155
|
-
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Always runs in the background \u2014 it
|
|
7437
|
+
description: "Reconcile the spec to bring it in line with code changes you have made. Provide a brief, bulleted list of what changed and why; it finds the affected spec sections and updates them to match. Set `refreshBuildOverview` after a deploy or a large milestone to also re-author the Build Overview from the updated spec. Always runs in the background and completes silently \u2014 do not wait for it; its outcome appears as an automated note at the start of a later turn.",
|
|
7156
7438
|
inputSchema: {
|
|
7157
7439
|
type: "object",
|
|
7158
7440
|
properties: {
|
|
7159
7441
|
task: {
|
|
7160
7442
|
type: "string",
|
|
7161
7443
|
description: "What you changed in the code and why, in plain language \u2014 the way you would explain it to a teammate. The specialist reads the spec itself and decides which sections to update; you do not need to name files or spec locations."
|
|
7444
|
+
},
|
|
7445
|
+
refreshBuildOverview: {
|
|
7446
|
+
type: "boolean",
|
|
7447
|
+
description: "Also refresh the Build Overview (src/overview.html) after reconciling \u2014 the specialist authors fresh copy from the updated spec and re-renders the page. Set this after a deploy or a large milestone; leave it off for routine syncs."
|
|
7162
7448
|
}
|
|
7163
7449
|
},
|
|
7164
7450
|
required: ["task"]
|
|
@@ -7182,12 +7468,27 @@ ${readAsset(
|
|
|
7182
7468
|
parts.push(specIndex);
|
|
7183
7469
|
}
|
|
7184
7470
|
const system = parts.join("\n\n");
|
|
7471
|
+
const refreshOverview = input.refreshBuildOverview === true;
|
|
7472
|
+
const tools2 = refreshOverview ? [...SPEC_SYNC_TOOLS, buildOverviewTool.definition] : SPEC_SYNC_TOOLS;
|
|
7473
|
+
const task = refreshOverview ? `${input.task}
|
|
7474
|
+
|
|
7475
|
+
After reconciling the spec, refresh the Build Overview: author the complete updated copy from the freshly-reconciled spec (follow the writeBuildOverview tool description) and call \`writeBuildOverview\` once with the final copy.` : input.task;
|
|
7185
7476
|
const result = await runSubAgent({
|
|
7186
7477
|
system,
|
|
7187
|
-
task
|
|
7188
|
-
tools:
|
|
7478
|
+
task,
|
|
7479
|
+
tools: tools2,
|
|
7189
7480
|
externalTools: /* @__PURE__ */ new Set(),
|
|
7190
|
-
executeTool: (name, toolInput
|
|
7481
|
+
executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
|
|
7482
|
+
if (name === "writeBuildOverview") {
|
|
7483
|
+
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
7484
|
+
return renderBuildOverview(
|
|
7485
|
+
String(toolInput.content ?? "").trim(),
|
|
7486
|
+
childCtx,
|
|
7487
|
+
{ background: false }
|
|
7488
|
+
);
|
|
7489
|
+
}
|
|
7490
|
+
return executeTool(name, toolInput, context);
|
|
7491
|
+
},
|
|
7191
7492
|
apiConfig: context.apiConfig,
|
|
7192
7493
|
model: resolveModel("specSync", context.models, context.model),
|
|
7193
7494
|
subAgentId: "specSync",
|
|
@@ -7270,143 +7571,26 @@ var init_scrapeWebUrl2 = __esm({
|
|
|
7270
7571
|
}
|
|
7271
7572
|
});
|
|
7272
7573
|
|
|
7273
|
-
// src/tools/
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
return `### Your deliverable
|
|
7277
|
-
Write the complete Build Overview to \`${OVERVIEW_FILE}\`. The file does not exist yet \u2014 start from this scaffold, which carries the technical hygiene for the iframed render context (keep the viewport meta and the reset). Layout, sections, type, and styling are yours to compose.
|
|
7278
|
-
|
|
7279
|
-
<overview_shell>
|
|
7280
|
-
${OVERVIEW_SHELL}
|
|
7281
|
-
</overview_shell>
|
|
7282
|
-
|
|
7283
|
-
Then reply with a one-line summary of what you wrote.`;
|
|
7574
|
+
// src/tools/index.ts
|
|
7575
|
+
function deriveContext(parent, toolCallId) {
|
|
7576
|
+
return { ...parent, toolCallId };
|
|
7284
7577
|
}
|
|
7285
|
-
function
|
|
7286
|
-
return
|
|
7287
|
-
The Build Overview already exists at \`${OVERVIEW_FILE}\`. Read it, then update it to reflect <overview_copy>, preserving its established skin \u2014 change only what the copy changed.
|
|
7288
|
-
|
|
7289
|
-
Then reply with a one-line summary of what you changed.`;
|
|
7578
|
+
function getToolDefinitions(_onboardingState) {
|
|
7579
|
+
return ALL_TOOLS.map((t) => t.definition);
|
|
7290
7580
|
}
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
### What it is (and is not)
|
|
7305
|
-
- A typeset reference dossier: composed, dense, a little cool \u2014 substantial at a glance, then readable. Density communicates substance; sparse and airy reads as "not much here."
|
|
7306
|
-
- Brand-skinned: pull the palette, type system, and a single accent from the app's spec. The accent leads labels and figures; it is a mark, not a fill.
|
|
7307
|
-
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
7308
|
-
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
7309
|
-
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
7310
|
-
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
7311
|
-
|
|
7312
|
-
### Constraints
|
|
7313
|
-
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
7314
|
-
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
7315
|
-
OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
7316
|
-
<html lang="en">
|
|
7317
|
-
<head>
|
|
7318
|
-
<meta charset="utf-8">
|
|
7319
|
-
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
7320
|
-
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
7321
|
-
The user-scalable=no viewport is intentional; leave it. -->
|
|
7322
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
7323
|
-
<title>Build Overview</title>
|
|
7324
|
-
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
7325
|
-
<style>
|
|
7326
|
-
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7327
|
-
</style>
|
|
7328
|
-
</head>
|
|
7329
|
-
<body>
|
|
7330
|
-
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
7331
|
-
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
7332
|
-
app's spec. -->
|
|
7333
|
-
</body>
|
|
7334
|
-
</html>`;
|
|
7335
|
-
buildOverviewTool = {
|
|
7336
|
-
definition: {
|
|
7337
|
-
clearable: false,
|
|
7338
|
-
name: "writeBuildOverview",
|
|
7339
|
-
description: "Generate or refresh the Build Overview \u2014 the project's home page in the Spec tab: a single-page, plain-language reference of everything the app actually contains, including the parts the user can't see (data stores, backend operations, access and roles, background jobs, seeded scenarios, the design system). You author the full copy: read the manifest and spec and state, plainly and exactly, what genuinely exists \u2014 real names and accurate counts \u2014 in calm, declarative, present-tense outcome language, with no persuasion or hype. Describe only what exists. Pass the complete copy as `content`; the design expert lays it out and skins it to the app's brand using your copy verbatim \u2014 it typesets your words, it does not rewrite them, so polish the copy before you pass it. Generate it at the end of a build and refresh it after meaningful work.",
|
|
7340
|
-
inputSchema: {
|
|
7341
|
-
type: "object",
|
|
7342
|
-
properties: {
|
|
7343
|
-
content: {
|
|
7344
|
-
type: "string",
|
|
7345
|
-
description: "The full Build Overview copy you authored: everything the app contains, in plain present-tense outcome language, with real names and exact counts. The design expert lays this out and skins it to the brand verbatim \u2014 it styles your words, it does not rewrite them \u2014 so this should be the final copy."
|
|
7346
|
-
}
|
|
7347
|
-
},
|
|
7348
|
-
required: ["content"]
|
|
7349
|
-
}
|
|
7350
|
-
},
|
|
7351
|
-
async execute(input, context) {
|
|
7352
|
-
if (!context) {
|
|
7353
|
-
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
7354
|
-
}
|
|
7355
|
-
const content = (input.content ?? "").trim();
|
|
7356
|
-
if (!content) {
|
|
7357
|
-
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
7358
|
-
}
|
|
7359
|
-
const exists = fs19.existsSync(OVERVIEW_FILE);
|
|
7360
|
-
const task = `<overview_copy>${content}</overview_copy>
|
|
7361
|
-
|
|
7362
|
-
${DESIGN_BRIEF}
|
|
7363
|
-
|
|
7364
|
-
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
7365
|
-
try {
|
|
7366
|
-
if (exists) {
|
|
7367
|
-
input.background = true;
|
|
7368
|
-
const result2 = await runDesignExpertRender(
|
|
7369
|
-
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
7370
|
-
context
|
|
7371
|
-
);
|
|
7372
|
-
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
7373
|
-
return result2.text;
|
|
7374
|
-
}
|
|
7375
|
-
const result = await runDesignExpertRender({ task }, context);
|
|
7376
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
7377
|
-
if (!fs19.existsSync(OVERVIEW_FILE)) {
|
|
7378
|
-
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
7379
|
-
${result.text}`;
|
|
7380
|
-
}
|
|
7381
|
-
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
7382
|
-
} catch (err) {
|
|
7383
|
-
return `Error generating build overview: ${err.message}`;
|
|
7384
|
-
}
|
|
7385
|
-
}
|
|
7386
|
-
};
|
|
7387
|
-
}
|
|
7388
|
-
});
|
|
7389
|
-
|
|
7390
|
-
// src/tools/index.ts
|
|
7391
|
-
function deriveContext(parent, toolCallId) {
|
|
7392
|
-
return { ...parent, toolCallId };
|
|
7393
|
-
}
|
|
7394
|
-
function getToolDefinitions(_onboardingState) {
|
|
7395
|
-
return ALL_TOOLS.map((t) => t.definition);
|
|
7396
|
-
}
|
|
7397
|
-
function getToolByName(name) {
|
|
7398
|
-
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
7399
|
-
}
|
|
7400
|
-
function executeTool(name, input, context) {
|
|
7401
|
-
const tool = getToolByName(name);
|
|
7402
|
-
if (!tool) {
|
|
7403
|
-
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
7404
|
-
}
|
|
7405
|
-
return tool.execute(input, context);
|
|
7406
|
-
}
|
|
7407
|
-
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7408
|
-
var init_tools8 = __esm({
|
|
7409
|
-
"src/tools/index.ts"() {
|
|
7581
|
+
function getToolByName(name) {
|
|
7582
|
+
return ALL_TOOLS.find((t) => t.definition.name === name);
|
|
7583
|
+
}
|
|
7584
|
+
function executeTool(name, input, context) {
|
|
7585
|
+
const tool = getToolByName(name);
|
|
7586
|
+
if (!tool) {
|
|
7587
|
+
return Promise.resolve(`Error: Unknown tool "${name}"`);
|
|
7588
|
+
}
|
|
7589
|
+
return tool.execute(input, context);
|
|
7590
|
+
}
|
|
7591
|
+
var ALL_TOOLS, SUBAGENT_TOOL_NAMES;
|
|
7592
|
+
var init_tools8 = __esm({
|
|
7593
|
+
"src/tools/index.ts"() {
|
|
7410
7594
|
"use strict";
|
|
7411
7595
|
init_readSpec();
|
|
7412
7596
|
init_writeSpec();
|
|
@@ -7673,6 +7857,70 @@ var init_parsePartialJson = __esm({
|
|
|
7673
7857
|
}
|
|
7674
7858
|
});
|
|
7675
7859
|
|
|
7860
|
+
// src/automatedActions/resolve.ts
|
|
7861
|
+
function resolveAction(text) {
|
|
7862
|
+
const parsed = parseSentinel(text);
|
|
7863
|
+
if (!parsed) {
|
|
7864
|
+
return null;
|
|
7865
|
+
}
|
|
7866
|
+
const { name: triggerName, remainder } = parsed;
|
|
7867
|
+
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
7868
|
+
return null;
|
|
7869
|
+
}
|
|
7870
|
+
let params = {};
|
|
7871
|
+
if (remainder) {
|
|
7872
|
+
try {
|
|
7873
|
+
params = JSON.parse(remainder.split("\n")[0]);
|
|
7874
|
+
} catch {
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7877
|
+
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
7878
|
+
let next;
|
|
7879
|
+
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7880
|
+
if (fmMatch) {
|
|
7881
|
+
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
7882
|
+
if (nextMatch) {
|
|
7883
|
+
next = nextMatch[1];
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
7887
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7888
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
7889
|
+
body = body.replaceAll(`{{${key}}}`, str);
|
|
7890
|
+
}
|
|
7891
|
+
return {
|
|
7892
|
+
message: automatedMessage(triggerName, body),
|
|
7893
|
+
next
|
|
7894
|
+
};
|
|
7895
|
+
}
|
|
7896
|
+
function getActionChain(startName) {
|
|
7897
|
+
const chain = [];
|
|
7898
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7899
|
+
let name = startName;
|
|
7900
|
+
while (name && !seen.has(name)) {
|
|
7901
|
+
seen.add(name);
|
|
7902
|
+
chain.push(name);
|
|
7903
|
+
let body;
|
|
7904
|
+
try {
|
|
7905
|
+
body = readAsset("automatedActions", `${name}.md`);
|
|
7906
|
+
} catch {
|
|
7907
|
+
break;
|
|
7908
|
+
}
|
|
7909
|
+
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7910
|
+
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
7911
|
+
}
|
|
7912
|
+
return chain;
|
|
7913
|
+
}
|
|
7914
|
+
var NON_ACTION_SENTINELS;
|
|
7915
|
+
var init_resolve = __esm({
|
|
7916
|
+
"src/automatedActions/resolve.ts"() {
|
|
7917
|
+
"use strict";
|
|
7918
|
+
init_assets();
|
|
7919
|
+
init_sentinel();
|
|
7920
|
+
NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
7921
|
+
}
|
|
7922
|
+
});
|
|
7923
|
+
|
|
7676
7924
|
// src/errors.ts
|
|
7677
7925
|
function friendlyError(raw) {
|
|
7678
7926
|
for (const [pattern, message] of patterns) {
|
|
@@ -8104,6 +8352,7 @@ async function runTurn(params) {
|
|
|
8104
8352
|
onboardingState,
|
|
8105
8353
|
signal,
|
|
8106
8354
|
onEvent,
|
|
8355
|
+
takeSteering,
|
|
8107
8356
|
resolveExternalTool,
|
|
8108
8357
|
requestId,
|
|
8109
8358
|
toolRegistry,
|
|
@@ -8138,7 +8387,7 @@ async function runTurn(params) {
|
|
|
8138
8387
|
onEvent({ type: "error", error: "Empty message" });
|
|
8139
8388
|
return;
|
|
8140
8389
|
}
|
|
8141
|
-
|
|
8390
|
+
const appendEntry = (entry) => {
|
|
8142
8391
|
const hasAttachments = (entry.attachments?.length ?? 0) > 0;
|
|
8143
8392
|
const userMsg = { role: "user", content: entry.text };
|
|
8144
8393
|
if (entry.hidden) {
|
|
@@ -8161,6 +8410,9 @@ async function runTurn(params) {
|
|
|
8161
8410
|
...entry.requestId && { requestId: entry.requestId },
|
|
8162
8411
|
...entry.queued && { queued: true }
|
|
8163
8412
|
});
|
|
8413
|
+
};
|
|
8414
|
+
for (const entry of keptEntries) {
|
|
8415
|
+
appendEntry(entry);
|
|
8164
8416
|
}
|
|
8165
8417
|
const isFirstMessage = state.messages.filter((m) => m.role === "user").length === 1;
|
|
8166
8418
|
const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -8171,6 +8423,9 @@ async function runTurn(params) {
|
|
|
8171
8423
|
let lastCompletedTools = "";
|
|
8172
8424
|
let lastCompletedInput = "";
|
|
8173
8425
|
let lastCompletedResult = "";
|
|
8426
|
+
let subAgentText = "";
|
|
8427
|
+
let currentToolNames = "";
|
|
8428
|
+
let contentBlocksRef = [];
|
|
8174
8429
|
let turnInputTokens = 0;
|
|
8175
8430
|
let turnOutputTokens = 0;
|
|
8176
8431
|
let turnCacheCreation = 0;
|
|
@@ -8179,281 +8434,328 @@ async function runTurn(params) {
|
|
|
8179
8434
|
let lastCallInputTokens = 0;
|
|
8180
8435
|
let lastCallCacheCreation = 0;
|
|
8181
8436
|
let lastCallCacheRead = 0;
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
} } : startStatusWatcher({
|
|
8213
|
-
apiConfig,
|
|
8214
|
-
getContext: () => {
|
|
8215
|
-
const parts = [];
|
|
8216
|
-
const toolName = currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
8217
|
-
if (toolName) {
|
|
8218
|
-
parts.push(`Tool: ${toolName}`);
|
|
8219
|
-
}
|
|
8220
|
-
if (lastCompletedInput) {
|
|
8221
|
-
parts.push(`Tool input: ${lastCompletedInput.slice(-1500)}`);
|
|
8222
|
-
}
|
|
8223
|
-
if (lastCompletedResult) {
|
|
8224
|
-
parts.push(`Tool result: ${lastCompletedResult.slice(-1500)}`);
|
|
8225
|
-
}
|
|
8226
|
-
const text = subAgentText || getTextContent(contentBlocks).slice(-2e3);
|
|
8227
|
-
if (text) {
|
|
8228
|
-
parts.push(`Assistant text: ${text}`);
|
|
8229
|
-
}
|
|
8230
|
-
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
8231
|
-
parts.push(`Build phase: ${onboardingState}`);
|
|
8437
|
+
const statusWatcher = isFirstMessage ? { stop() {
|
|
8438
|
+
}, pause() {
|
|
8439
|
+
}, resume() {
|
|
8440
|
+
} } : startStatusWatcher({
|
|
8441
|
+
apiConfig,
|
|
8442
|
+
getContext: () => {
|
|
8443
|
+
const parts = [];
|
|
8444
|
+
const toolName = currentToolNames || getToolCalls(contentBlocksRef).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
8445
|
+
if (toolName) {
|
|
8446
|
+
parts.push(`Tool: ${toolName}`);
|
|
8447
|
+
}
|
|
8448
|
+
const toolInput = sanitizeStatusText(lastCompletedInput);
|
|
8449
|
+
if (toolInput) {
|
|
8450
|
+
parts.push(`Tool input: ${toolInput.slice(-1500)}`);
|
|
8451
|
+
}
|
|
8452
|
+
const toolResult = sanitizeStatusText(lastCompletedResult);
|
|
8453
|
+
if (toolResult) {
|
|
8454
|
+
parts.push(`Tool result: ${toolResult.slice(-1500)}`);
|
|
8455
|
+
}
|
|
8456
|
+
const text = subAgentText || getTextContent(contentBlocksRef).slice(-2e3);
|
|
8457
|
+
if (text) {
|
|
8458
|
+
parts.push(`Assistant text: ${text}`);
|
|
8459
|
+
}
|
|
8460
|
+
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
8461
|
+
parts.push(`Build phase: ${onboardingState}`);
|
|
8462
|
+
}
|
|
8463
|
+
let hasUserSignal = false;
|
|
8464
|
+
for (const entry of keptEntries) {
|
|
8465
|
+
if (entry.hidden) {
|
|
8466
|
+
continue;
|
|
8232
8467
|
}
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
if (automated) {
|
|
8468
|
+
const automated = parseSentinel(entry.text);
|
|
8469
|
+
if (automated) {
|
|
8470
|
+
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
8236
8471
|
parts.push(`Automated action: ${automated.name}`);
|
|
8237
|
-
|
|
8238
|
-
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
8472
|
+
hasUserSignal = true;
|
|
8239
8473
|
}
|
|
8474
|
+
} else if (entry.text) {
|
|
8475
|
+
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
8476
|
+
hasUserSignal = true;
|
|
8240
8477
|
}
|
|
8241
|
-
return parts.join("\n");
|
|
8242
|
-
},
|
|
8243
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
8244
|
-
signal
|
|
8245
|
-
});
|
|
8246
|
-
async function handlePartialInput(acc, id, name, partial) {
|
|
8247
|
-
const tool = getToolByName(name);
|
|
8248
|
-
if (!tool?.streaming) {
|
|
8249
|
-
return;
|
|
8250
8478
|
}
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
transform,
|
|
8254
|
-
partialInput
|
|
8255
|
-
} = tool.streaming;
|
|
8256
|
-
if (partialInput) {
|
|
8257
|
-
const result = partialInput(partial, acc.lastEmittedCount);
|
|
8258
|
-
if (!result) {
|
|
8259
|
-
return;
|
|
8260
|
-
}
|
|
8261
|
-
acc.lastEmittedCount = result.emittedCount;
|
|
8262
|
-
acc.started = true;
|
|
8263
|
-
onEvent({
|
|
8264
|
-
type: "tool_start",
|
|
8265
|
-
id,
|
|
8266
|
-
name,
|
|
8267
|
-
input: result.input,
|
|
8268
|
-
partial: true
|
|
8269
|
-
});
|
|
8270
|
-
return;
|
|
8479
|
+
if (!hasUserSignal && !toolName && !text) {
|
|
8480
|
+
return "";
|
|
8271
8481
|
}
|
|
8272
|
-
|
|
8273
|
-
|
|
8482
|
+
return parts.join("\n");
|
|
8483
|
+
},
|
|
8484
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
8485
|
+
signal
|
|
8486
|
+
});
|
|
8487
|
+
try {
|
|
8488
|
+
while (true) {
|
|
8489
|
+
let getOrCreateAccumulator2 = function(id, name) {
|
|
8490
|
+
let acc = toolInputAccumulators.get(id);
|
|
8491
|
+
if (!acc) {
|
|
8492
|
+
acc = { name, json: "", started: false, lastEmittedCount: 0 };
|
|
8493
|
+
toolInputAccumulators.set(id, acc);
|
|
8494
|
+
}
|
|
8495
|
+
return acc;
|
|
8496
|
+
};
|
|
8497
|
+
var getOrCreateAccumulator = getOrCreateAccumulator2;
|
|
8498
|
+
if (signal?.aborted) {
|
|
8499
|
+
onEvent({ type: "turn_cancelled" });
|
|
8500
|
+
saveSession(state);
|
|
8274
8501
|
return;
|
|
8275
8502
|
}
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8503
|
+
const iterStart = Date.now();
|
|
8504
|
+
const contentBlocks = [];
|
|
8505
|
+
const thinkingBlockStartTimes = [];
|
|
8506
|
+
let thinkingCompleteCount = 0;
|
|
8507
|
+
let lastThinkingRelatedStartedAt;
|
|
8508
|
+
let textBlockOpen = false;
|
|
8509
|
+
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
8510
|
+
let stopReason = "end_turn";
|
|
8511
|
+
let turnProviderMetadata;
|
|
8512
|
+
let turnModelId;
|
|
8513
|
+
contentBlocksRef = contentBlocks;
|
|
8514
|
+
subAgentText = "";
|
|
8515
|
+
currentToolNames = "";
|
|
8516
|
+
async function handlePartialInput(acc, id, name, partial) {
|
|
8517
|
+
const tool = getToolByName(name);
|
|
8518
|
+
if (!tool?.streaming) {
|
|
8283
8519
|
return;
|
|
8284
8520
|
}
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
model: parentModel,
|
|
8295
|
-
requestId,
|
|
8296
|
-
system,
|
|
8297
|
-
messages: cleanMessagesForApi(state.messages),
|
|
8298
|
-
tools: tools2,
|
|
8299
|
-
signal
|
|
8300
|
-
},
|
|
8301
|
-
{
|
|
8302
|
-
onRetry: (attempt) => {
|
|
8303
|
-
onEvent({
|
|
8304
|
-
type: "status",
|
|
8305
|
-
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8306
|
-
});
|
|
8521
|
+
const {
|
|
8522
|
+
contentField = "content",
|
|
8523
|
+
transform,
|
|
8524
|
+
partialInput
|
|
8525
|
+
} = tool.streaming;
|
|
8526
|
+
if (partialInput) {
|
|
8527
|
+
const result = partialInput(partial, acc.lastEmittedCount);
|
|
8528
|
+
if (!result) {
|
|
8529
|
+
return;
|
|
8307
8530
|
}
|
|
8531
|
+
acc.lastEmittedCount = result.emittedCount;
|
|
8532
|
+
acc.started = true;
|
|
8533
|
+
onEvent({
|
|
8534
|
+
type: "tool_start",
|
|
8535
|
+
id,
|
|
8536
|
+
name,
|
|
8537
|
+
input: result.input,
|
|
8538
|
+
partial: true
|
|
8539
|
+
});
|
|
8540
|
+
return;
|
|
8308
8541
|
}
|
|
8309
|
-
|
|
8310
|
-
if (
|
|
8311
|
-
|
|
8542
|
+
const content = partial[contentField];
|
|
8543
|
+
if (typeof content !== "string") {
|
|
8544
|
+
return;
|
|
8312
8545
|
}
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
text: event.text,
|
|
8322
|
-
startedAt: event.ts
|
|
8323
|
-
});
|
|
8324
|
-
}
|
|
8325
|
-
textBlockOpen = true;
|
|
8326
|
-
onEvent({ type: "text", text: event.text });
|
|
8327
|
-
break;
|
|
8546
|
+
if (!acc.started) {
|
|
8547
|
+
acc.started = true;
|
|
8548
|
+
onEvent({ type: "tool_start", id, name, input: partial });
|
|
8549
|
+
}
|
|
8550
|
+
if (transform) {
|
|
8551
|
+
const result = await transform(partial);
|
|
8552
|
+
if (result === null) {
|
|
8553
|
+
return;
|
|
8328
8554
|
}
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8555
|
+
onEvent({ type: "tool_input_delta", id, name, result });
|
|
8556
|
+
} else {
|
|
8557
|
+
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
8558
|
+
}
|
|
8559
|
+
}
|
|
8560
|
+
try {
|
|
8561
|
+
for await (const event of streamChatWithRetry(
|
|
8562
|
+
{
|
|
8563
|
+
...apiConfig,
|
|
8564
|
+
model: parentModel,
|
|
8565
|
+
requestId,
|
|
8566
|
+
system,
|
|
8567
|
+
messages: cleanMessagesForApi(state.messages),
|
|
8568
|
+
tools: tools2,
|
|
8569
|
+
signal
|
|
8570
|
+
},
|
|
8571
|
+
{
|
|
8572
|
+
onRetry: (attempt) => {
|
|
8573
|
+
onEvent({
|
|
8574
|
+
type: "status",
|
|
8575
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8576
|
+
});
|
|
8333
8577
|
}
|
|
8334
|
-
onEvent({ type: "thinking", text: event.text });
|
|
8335
|
-
break;
|
|
8336
|
-
case "thinking_complete": {
|
|
8337
|
-
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8338
|
-
contentBlocks.push({
|
|
8339
|
-
type: "thinking",
|
|
8340
|
-
thinking: event.thinking,
|
|
8341
|
-
signature: event.signature,
|
|
8342
|
-
startedAt,
|
|
8343
|
-
completedAt: event.ts
|
|
8344
|
-
});
|
|
8345
|
-
thinkingCompleteCount++;
|
|
8346
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8347
|
-
break;
|
|
8348
8578
|
}
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
contentBlocks.push({
|
|
8352
|
-
type: "redacted_thinking",
|
|
8353
|
-
data: event.data,
|
|
8354
|
-
startedAt,
|
|
8355
|
-
completedAt: event.ts
|
|
8356
|
-
});
|
|
8357
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8579
|
+
)) {
|
|
8580
|
+
if (signal?.aborted) {
|
|
8358
8581
|
break;
|
|
8359
8582
|
}
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8583
|
+
switch (event.type) {
|
|
8584
|
+
case "text": {
|
|
8585
|
+
const lastBlock = contentBlocks.at(-1);
|
|
8586
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
8587
|
+
lastBlock.text += event.text;
|
|
8588
|
+
} else {
|
|
8589
|
+
contentBlocks.push({
|
|
8590
|
+
type: "text",
|
|
8591
|
+
text: event.text,
|
|
8592
|
+
startedAt: event.ts
|
|
8593
|
+
});
|
|
8594
|
+
}
|
|
8595
|
+
textBlockOpen = true;
|
|
8596
|
+
onEvent({ type: "text", text: event.text });
|
|
8597
|
+
break;
|
|
8367
8598
|
}
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8373
|
-
break;
|
|
8374
|
-
}
|
|
8375
|
-
case "tool_use": {
|
|
8376
|
-
const tool = getToolByName(event.name);
|
|
8377
|
-
contentBlocks.push({
|
|
8378
|
-
type: "tool",
|
|
8379
|
-
id: event.id,
|
|
8380
|
-
name: event.name,
|
|
8381
|
-
input: event.input,
|
|
8382
|
-
startedAt: event.ts,
|
|
8383
|
-
...(event.input.background || tool?.backgroundOnly) && {
|
|
8384
|
-
background: true
|
|
8599
|
+
case "thinking":
|
|
8600
|
+
if (event.text === "") {
|
|
8601
|
+
thinkingBlockStartTimes.push(event.ts);
|
|
8602
|
+
textBlockOpen = false;
|
|
8385
8603
|
}
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8604
|
+
onEvent({ type: "thinking", text: event.text });
|
|
8605
|
+
break;
|
|
8606
|
+
case "thinking_complete": {
|
|
8607
|
+
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8608
|
+
contentBlocks.push({
|
|
8609
|
+
type: "thinking",
|
|
8610
|
+
thinking: event.thinking,
|
|
8611
|
+
signature: event.signature,
|
|
8612
|
+
startedAt,
|
|
8613
|
+
completedAt: event.ts
|
|
8614
|
+
});
|
|
8615
|
+
thinkingCompleteCount++;
|
|
8616
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8617
|
+
break;
|
|
8618
|
+
}
|
|
8619
|
+
case "redacted_thinking_complete": {
|
|
8620
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
8621
|
+
contentBlocks.push({
|
|
8622
|
+
type: "redacted_thinking",
|
|
8623
|
+
data: event.data,
|
|
8624
|
+
startedAt,
|
|
8625
|
+
completedAt: event.ts
|
|
8626
|
+
});
|
|
8627
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8628
|
+
break;
|
|
8629
|
+
}
|
|
8630
|
+
case "tool_input_delta": {
|
|
8631
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8632
|
+
acc.json += event.delta;
|
|
8633
|
+
try {
|
|
8634
|
+
const partial = parsePartialJson(acc.json);
|
|
8635
|
+
await handlePartialInput(acc, event.id, event.name, partial);
|
|
8636
|
+
} catch {
|
|
8637
|
+
}
|
|
8638
|
+
break;
|
|
8639
|
+
}
|
|
8640
|
+
case "tool_input_args": {
|
|
8641
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8642
|
+
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8643
|
+
break;
|
|
8644
|
+
}
|
|
8645
|
+
case "tool_use": {
|
|
8646
|
+
const tool = getToolByName(event.name);
|
|
8647
|
+
contentBlocks.push({
|
|
8648
|
+
type: "tool",
|
|
8398
8649
|
id: event.id,
|
|
8399
8650
|
name: event.name,
|
|
8400
|
-
input: event.input
|
|
8651
|
+
input: event.input,
|
|
8652
|
+
startedAt: event.ts,
|
|
8653
|
+
...(event.input.background || tool?.backgroundOnly) && {
|
|
8654
|
+
background: true
|
|
8655
|
+
}
|
|
8656
|
+
});
|
|
8657
|
+
const acc = toolInputAccumulators.get(event.id);
|
|
8658
|
+
const wasStreamed = acc?.started ?? false;
|
|
8659
|
+
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8660
|
+
log13.info("Tool received", {
|
|
8661
|
+
requestId,
|
|
8662
|
+
toolCallId: event.id,
|
|
8663
|
+
name: event.name
|
|
8401
8664
|
});
|
|
8665
|
+
if (!wasStreamed || isInputStreaming) {
|
|
8666
|
+
onEvent({
|
|
8667
|
+
type: "tool_start",
|
|
8668
|
+
id: event.id,
|
|
8669
|
+
name: event.name,
|
|
8670
|
+
input: event.input,
|
|
8671
|
+
// Mirror the content-block stamp above so the live event
|
|
8672
|
+
// matches what a history reload shows — without it the
|
|
8673
|
+
// frontend renders background rows as already complete.
|
|
8674
|
+
...isBackgroundCall({
|
|
8675
|
+
name: event.name,
|
|
8676
|
+
input: event.input
|
|
8677
|
+
}) && { background: true }
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
break;
|
|
8402
8681
|
}
|
|
8403
|
-
|
|
8682
|
+
case "done":
|
|
8683
|
+
stopReason = event.stopReason;
|
|
8684
|
+
turnProviderMetadata = event.providerMetadata;
|
|
8685
|
+
turnModelId = event.modelId;
|
|
8686
|
+
turnLlmCalls++;
|
|
8687
|
+
lastCallInputTokens = event.usage.inputTokens;
|
|
8688
|
+
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8689
|
+
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8690
|
+
turnInputTokens += lastCallInputTokens;
|
|
8691
|
+
turnOutputTokens += event.usage.outputTokens;
|
|
8692
|
+
turnCacheCreation += lastCallCacheCreation;
|
|
8693
|
+
turnCacheRead += lastCallCacheRead;
|
|
8694
|
+
recordUsage({
|
|
8695
|
+
ts: Date.now(),
|
|
8696
|
+
requestId,
|
|
8697
|
+
agentName: "parent",
|
|
8698
|
+
modelId: event.modelId,
|
|
8699
|
+
inputTokens: event.usage.inputTokens,
|
|
8700
|
+
outputTokens: event.usage.outputTokens,
|
|
8701
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8702
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8703
|
+
cost: nanoToDollars(event.cost),
|
|
8704
|
+
billingEvents: event.billingEvents,
|
|
8705
|
+
durationMs: Date.now() - iterStart,
|
|
8706
|
+
toolNames: contentBlocks.filter(
|
|
8707
|
+
(b) => b.type === "tool"
|
|
8708
|
+
).map((b) => b.name)
|
|
8709
|
+
});
|
|
8710
|
+
break;
|
|
8711
|
+
case "error":
|
|
8712
|
+
statusWatcher.stop();
|
|
8713
|
+
onEvent({
|
|
8714
|
+
type: "error",
|
|
8715
|
+
error: friendlyError(event.error),
|
|
8716
|
+
...event.code ? { code: event.code } : {}
|
|
8717
|
+
});
|
|
8718
|
+
return;
|
|
8404
8719
|
}
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
lastCallInputTokens = event.usage.inputTokens;
|
|
8411
|
-
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8412
|
-
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8413
|
-
turnInputTokens += lastCallInputTokens;
|
|
8414
|
-
turnOutputTokens += event.usage.outputTokens;
|
|
8415
|
-
turnCacheCreation += lastCallCacheCreation;
|
|
8416
|
-
turnCacheRead += lastCallCacheRead;
|
|
8417
|
-
recordUsage({
|
|
8418
|
-
ts: Date.now(),
|
|
8419
|
-
requestId,
|
|
8420
|
-
agentName: "parent",
|
|
8421
|
-
modelId: event.modelId,
|
|
8422
|
-
inputTokens: event.usage.inputTokens,
|
|
8423
|
-
outputTokens: event.usage.outputTokens,
|
|
8424
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8425
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8426
|
-
cost: nanoToDollars(event.cost),
|
|
8427
|
-
billingEvents: event.billingEvents,
|
|
8428
|
-
durationMs: Date.now() - iterStart,
|
|
8429
|
-
toolNames: contentBlocks.filter(
|
|
8430
|
-
(b) => b.type === "tool"
|
|
8431
|
-
).map((b) => b.name)
|
|
8432
|
-
});
|
|
8433
|
-
break;
|
|
8434
|
-
case "error":
|
|
8435
|
-
onEvent({
|
|
8436
|
-
type: "error",
|
|
8437
|
-
error: friendlyError(event.error),
|
|
8438
|
-
...event.code ? { code: event.code } : {}
|
|
8439
|
-
});
|
|
8440
|
-
return;
|
|
8720
|
+
}
|
|
8721
|
+
} catch (err) {
|
|
8722
|
+
if (signal?.aborted) {
|
|
8723
|
+
} else {
|
|
8724
|
+
throw err;
|
|
8441
8725
|
}
|
|
8442
8726
|
}
|
|
8443
|
-
} catch (err) {
|
|
8444
8727
|
if (signal?.aborted) {
|
|
8445
|
-
|
|
8446
|
-
|
|
8728
|
+
statusWatcher.stop();
|
|
8729
|
+
if (contentBlocks.length > 0) {
|
|
8730
|
+
contentBlocks.push({
|
|
8731
|
+
type: "text",
|
|
8732
|
+
text: "\n\n(cancelled)",
|
|
8733
|
+
startedAt: Date.now()
|
|
8734
|
+
});
|
|
8735
|
+
state.messages.push({
|
|
8736
|
+
role: "assistant",
|
|
8737
|
+
content: [...contentBlocks].sort(
|
|
8738
|
+
(a, b) => a.startedAt - b.startedAt
|
|
8739
|
+
),
|
|
8740
|
+
usage: {
|
|
8741
|
+
inputTokens: turnInputTokens,
|
|
8742
|
+
outputTokens: turnOutputTokens,
|
|
8743
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8744
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8745
|
+
llmCalls: turnLlmCalls
|
|
8746
|
+
},
|
|
8747
|
+
...turnProviderMetadata && {
|
|
8748
|
+
providerMetadata: turnProviderMetadata
|
|
8749
|
+
},
|
|
8750
|
+
model: turnModelId ?? parentModel,
|
|
8751
|
+
...modelOverride && { modelOverride }
|
|
8752
|
+
});
|
|
8753
|
+
}
|
|
8754
|
+
onEvent({ type: "turn_cancelled" });
|
|
8755
|
+
saveSession(state);
|
|
8756
|
+
return;
|
|
8447
8757
|
}
|
|
8448
|
-
}
|
|
8449
|
-
if (signal?.aborted) {
|
|
8450
|
-
statusWatcher.stop();
|
|
8451
8758
|
if (contentBlocks.length > 0) {
|
|
8452
|
-
contentBlocks.push({
|
|
8453
|
-
type: "text",
|
|
8454
|
-
text: "\n\n(cancelled)",
|
|
8455
|
-
startedAt: Date.now()
|
|
8456
|
-
});
|
|
8457
8759
|
state.messages.push({
|
|
8458
8760
|
role: "assistant",
|
|
8459
8761
|
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
@@ -8471,217 +8773,212 @@ async function runTurn(params) {
|
|
|
8471
8773
|
...modelOverride && { modelOverride }
|
|
8472
8774
|
});
|
|
8473
8775
|
}
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
});
|
|
8493
|
-
}
|
|
8494
|
-
const toolCalls = getToolCalls(contentBlocks);
|
|
8495
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8496
|
-
statusWatcher.stop();
|
|
8497
|
-
saveSession(state);
|
|
8498
|
-
onEvent({
|
|
8499
|
-
type: "turn_done",
|
|
8500
|
-
stats: {
|
|
8501
|
-
inputTokens: turnInputTokens,
|
|
8502
|
-
outputTokens: turnOutputTokens,
|
|
8503
|
-
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8504
|
-
cacheReadTokens: turnCacheRead || void 0,
|
|
8505
|
-
llmCalls: turnLlmCalls,
|
|
8506
|
-
lastCallInputTokens,
|
|
8507
|
-
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8508
|
-
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8509
|
-
}
|
|
8510
|
-
});
|
|
8511
|
-
return;
|
|
8512
|
-
}
|
|
8513
|
-
log13.info("Tools executing", {
|
|
8514
|
-
requestId,
|
|
8515
|
-
count: toolCalls.length,
|
|
8516
|
-
tools: toolCalls.map((tc) => tc.name)
|
|
8517
|
-
});
|
|
8518
|
-
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8519
|
-
const wrappedOnEvent = (e) => {
|
|
8520
|
-
if ("parentToolId" in e && e.parentToolId) {
|
|
8521
|
-
if (e.type === "text") {
|
|
8522
|
-
subAgentText = e.text;
|
|
8523
|
-
} else if (e.type === "tool_start") {
|
|
8524
|
-
subAgentText = `Using ${e.name}`;
|
|
8525
|
-
}
|
|
8776
|
+
const toolCalls = getToolCalls(contentBlocks);
|
|
8777
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8778
|
+
statusWatcher.stop();
|
|
8779
|
+
saveSession(state);
|
|
8780
|
+
onEvent({
|
|
8781
|
+
type: "turn_done",
|
|
8782
|
+
stats: {
|
|
8783
|
+
inputTokens: turnInputTokens,
|
|
8784
|
+
outputTokens: turnOutputTokens,
|
|
8785
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8786
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8787
|
+
llmCalls: turnLlmCalls,
|
|
8788
|
+
lastCallInputTokens,
|
|
8789
|
+
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8790
|
+
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8791
|
+
}
|
|
8792
|
+
});
|
|
8793
|
+
return;
|
|
8526
8794
|
}
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8795
|
+
log13.info("Tools executing", {
|
|
8796
|
+
requestId,
|
|
8797
|
+
count: toolCalls.length,
|
|
8798
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
8799
|
+
});
|
|
8800
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8801
|
+
const wrappedOnEvent = (e) => {
|
|
8802
|
+
if ("parentToolId" in e && e.parentToolId) {
|
|
8803
|
+
if (e.type === "text") {
|
|
8804
|
+
subAgentText = e.text;
|
|
8805
|
+
} else if (e.type === "tool_start") {
|
|
8806
|
+
subAgentText = `Using ${e.name}`;
|
|
8807
|
+
}
|
|
8534
8808
|
}
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
const safeSettle = (result, isError) => {
|
|
8543
|
-
if (settled) {
|
|
8544
|
-
return;
|
|
8809
|
+
onEvent(e);
|
|
8810
|
+
};
|
|
8811
|
+
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
8812
|
+
const results = await Promise.all(
|
|
8813
|
+
toolCalls.map(async (tc) => {
|
|
8814
|
+
if (signal?.aborted) {
|
|
8815
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
8545
8816
|
}
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
toolAbort
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8817
|
+
const toolStart = Date.now();
|
|
8818
|
+
let settle;
|
|
8819
|
+
const resultPromise = new Promise((res) => {
|
|
8820
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
8821
|
+
});
|
|
8822
|
+
let toolAbort = new AbortController();
|
|
8823
|
+
let settled = false;
|
|
8824
|
+
const safeSettle = (result, isError) => {
|
|
8825
|
+
if (settled) {
|
|
8826
|
+
return;
|
|
8827
|
+
}
|
|
8828
|
+
settled = true;
|
|
8829
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
8830
|
+
settle(result, isError);
|
|
8831
|
+
};
|
|
8832
|
+
const cascadeAbort = () => {
|
|
8833
|
+
toolAbort.abort();
|
|
8834
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
8835
|
+
};
|
|
8836
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
8837
|
+
const run = async (input) => {
|
|
8838
|
+
try {
|
|
8839
|
+
let result;
|
|
8840
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8841
|
+
saveSession(state);
|
|
8842
|
+
log13.info("Waiting for external tool result", {
|
|
8843
|
+
requestId,
|
|
8844
|
+
toolCallId: tc.id,
|
|
8845
|
+
name: tc.name
|
|
8846
|
+
});
|
|
8847
|
+
const blocksUser = USER_BLOCKING_EXTERNAL_TOOLS.has(tc.name);
|
|
8572
8848
|
if (blocksUser) {
|
|
8573
|
-
statusWatcher.
|
|
8849
|
+
statusWatcher.pause();
|
|
8850
|
+
}
|
|
8851
|
+
try {
|
|
8852
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
8853
|
+
} finally {
|
|
8854
|
+
if (blocksUser) {
|
|
8855
|
+
statusWatcher.resume();
|
|
8856
|
+
}
|
|
8574
8857
|
}
|
|
8858
|
+
} else {
|
|
8859
|
+
result = await executeTool(tc.name, input, {
|
|
8860
|
+
apiConfig,
|
|
8861
|
+
model,
|
|
8862
|
+
models: state.models,
|
|
8863
|
+
signal: toolAbort.signal,
|
|
8864
|
+
onEvent: wrappedOnEvent,
|
|
8865
|
+
resolveExternalTool,
|
|
8866
|
+
toolCallId: tc.id,
|
|
8867
|
+
requestId,
|
|
8868
|
+
onboardingState,
|
|
8869
|
+
subAgentMessages,
|
|
8870
|
+
conversationMessages: state.messages,
|
|
8871
|
+
toolRegistry,
|
|
8872
|
+
onBackgroundComplete,
|
|
8873
|
+
onLog: (line) => wrappedOnEvent({
|
|
8874
|
+
type: "tool_input_delta",
|
|
8875
|
+
id: tc.id,
|
|
8876
|
+
name: tc.name,
|
|
8877
|
+
result: line
|
|
8878
|
+
})
|
|
8879
|
+
});
|
|
8575
8880
|
}
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
id: tc.id,
|
|
8594
|
-
name: tc.name,
|
|
8595
|
-
result: line
|
|
8596
|
-
})
|
|
8881
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
8882
|
+
} catch (err) {
|
|
8883
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
8884
|
+
}
|
|
8885
|
+
};
|
|
8886
|
+
const entry = {
|
|
8887
|
+
id: tc.id,
|
|
8888
|
+
name: tc.name,
|
|
8889
|
+
input: tc.input,
|
|
8890
|
+
abortController: toolAbort,
|
|
8891
|
+
startedAt: toolStart,
|
|
8892
|
+
settle: safeSettle,
|
|
8893
|
+
rerun: (newInput) => {
|
|
8894
|
+
settled = false;
|
|
8895
|
+
toolAbort = new AbortController();
|
|
8896
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
8897
|
+
once: true
|
|
8597
8898
|
});
|
|
8899
|
+
entry.abortController = toolAbort;
|
|
8900
|
+
entry.input = newInput;
|
|
8901
|
+
run(newInput);
|
|
8598
8902
|
}
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8903
|
+
};
|
|
8904
|
+
toolRegistry?.register(entry);
|
|
8905
|
+
run(tc.input);
|
|
8906
|
+
const r = await resultPromise;
|
|
8907
|
+
if (!isBackgroundCall(tc)) {
|
|
8908
|
+
toolRegistry?.unregister(tc.id);
|
|
8602
8909
|
}
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8910
|
+
log13.info("Tool completed", {
|
|
8911
|
+
requestId,
|
|
8912
|
+
toolCallId: tc.id,
|
|
8913
|
+
name: tc.name,
|
|
8914
|
+
durationMs: Date.now() - toolStart,
|
|
8915
|
+
isError: r.isError
|
|
8916
|
+
});
|
|
8917
|
+
onEvent({
|
|
8918
|
+
type: "tool_done",
|
|
8919
|
+
id: tc.id,
|
|
8920
|
+
name: tc.name,
|
|
8921
|
+
result: r.result,
|
|
8922
|
+
isError: r.isError
|
|
8923
|
+
});
|
|
8924
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8925
|
+
triggerBrandExtraction(
|
|
8926
|
+
apiConfig,
|
|
8927
|
+
resolveModel("brandExtractor", state.models, model)
|
|
8928
|
+
);
|
|
8929
|
+
}
|
|
8930
|
+
return r;
|
|
8931
|
+
})
|
|
8932
|
+
);
|
|
8933
|
+
for (const r of results) {
|
|
8934
|
+
const block = contentBlocks.find(
|
|
8935
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
8936
|
+
);
|
|
8937
|
+
if (block?.type === "tool") {
|
|
8938
|
+
block.result = r.result;
|
|
8939
|
+
block.isError = r.isError;
|
|
8940
|
+
block.completedAt = Date.now();
|
|
8941
|
+
const msgs = subAgentMessages.get(r.id);
|
|
8942
|
+
if (msgs) {
|
|
8943
|
+
block.subAgentMessages = msgs;
|
|
8620
8944
|
}
|
|
8621
|
-
};
|
|
8622
|
-
toolRegistry?.register(entry);
|
|
8623
|
-
run(tc.input);
|
|
8624
|
-
const r = await resultPromise;
|
|
8625
|
-
if (!isBackgroundCall(tc)) {
|
|
8626
|
-
toolRegistry?.unregister(tc.id);
|
|
8627
|
-
}
|
|
8628
|
-
log13.info("Tool completed", {
|
|
8629
|
-
requestId,
|
|
8630
|
-
toolCallId: tc.id,
|
|
8631
|
-
name: tc.name,
|
|
8632
|
-
durationMs: Date.now() - toolStart,
|
|
8633
|
-
isError: r.isError
|
|
8634
|
-
});
|
|
8635
|
-
onEvent({
|
|
8636
|
-
type: "tool_done",
|
|
8637
|
-
id: tc.id,
|
|
8638
|
-
name: tc.name,
|
|
8639
|
-
result: r.result,
|
|
8640
|
-
isError: r.isError
|
|
8641
|
-
});
|
|
8642
|
-
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8643
|
-
triggerBrandExtraction(
|
|
8644
|
-
apiConfig,
|
|
8645
|
-
resolveModel("brandExtractor", state.models, model)
|
|
8646
|
-
);
|
|
8647
8945
|
}
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
statusWatcher.stop();
|
|
8652
|
-
for (const r of results) {
|
|
8653
|
-
const block = contentBlocks.find(
|
|
8654
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
8946
|
+
}
|
|
8947
|
+
const lastNonExcluded = toolCalls.filter(
|
|
8948
|
+
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8655
8949
|
);
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8950
|
+
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8951
|
+
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8952
|
+
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8953
|
+
for (const r of results) {
|
|
8954
|
+
state.messages.push({
|
|
8955
|
+
role: "user",
|
|
8956
|
+
content: r.result,
|
|
8957
|
+
toolCallId: r.id,
|
|
8958
|
+
isToolError: r.isError
|
|
8959
|
+
});
|
|
8960
|
+
}
|
|
8961
|
+
if (takeSteering && !signal?.aborted) {
|
|
8962
|
+
const injected = (await takeSteering()).filter(
|
|
8963
|
+
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8964
|
+
);
|
|
8965
|
+
if (injected.length > 0) {
|
|
8966
|
+
for (const entry of injected) {
|
|
8967
|
+
appendEntry(entry);
|
|
8968
|
+
keptEntries.push(entry);
|
|
8969
|
+
}
|
|
8970
|
+
saveSession(state);
|
|
8663
8971
|
}
|
|
8664
8972
|
}
|
|
8973
|
+
if (signal?.aborted) {
|
|
8974
|
+
statusWatcher.stop();
|
|
8975
|
+
onEvent({ type: "turn_cancelled" });
|
|
8976
|
+
saveSession(state);
|
|
8977
|
+
return;
|
|
8978
|
+
}
|
|
8665
8979
|
}
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
);
|
|
8669
|
-
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8670
|
-
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8671
|
-
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8672
|
-
for (const r of results) {
|
|
8673
|
-
state.messages.push({
|
|
8674
|
-
role: "user",
|
|
8675
|
-
content: r.result,
|
|
8676
|
-
toolCallId: r.id,
|
|
8677
|
-
isToolError: r.isError
|
|
8678
|
-
});
|
|
8679
|
-
}
|
|
8680
|
-
if (signal?.aborted) {
|
|
8681
|
-
onEvent({ type: "turn_cancelled" });
|
|
8682
|
-
saveSession(state);
|
|
8683
|
-
return;
|
|
8684
|
-
}
|
|
8980
|
+
} finally {
|
|
8981
|
+
statusWatcher.stop();
|
|
8685
8982
|
}
|
|
8686
8983
|
}
|
|
8687
8984
|
var log13, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
@@ -8695,6 +8992,7 @@ var init_agent = __esm({
|
|
|
8695
8992
|
init_usageLedger();
|
|
8696
8993
|
init_parsePartialJson();
|
|
8697
8994
|
init_statusWatcher();
|
|
8995
|
+
init_resolve();
|
|
8698
8996
|
init_errors();
|
|
8699
8997
|
init_cleanMessages();
|
|
8700
8998
|
init_sentinel();
|
|
@@ -9244,13 +9542,24 @@ function loadQueue() {
|
|
|
9244
9542
|
}
|
|
9245
9543
|
return [];
|
|
9246
9544
|
}
|
|
9247
|
-
function
|
|
9545
|
+
function loadPassiveResults() {
|
|
9546
|
+
try {
|
|
9547
|
+
const stats = JSON.parse(readFileSync2(STATS_FILE, "utf-8"));
|
|
9548
|
+
if (Array.isArray(stats.passiveResults)) {
|
|
9549
|
+
return stats.passiveResults;
|
|
9550
|
+
}
|
|
9551
|
+
} catch {
|
|
9552
|
+
}
|
|
9553
|
+
return [];
|
|
9554
|
+
}
|
|
9555
|
+
function writeStats(stats, queue, passiveResults) {
|
|
9248
9556
|
try {
|
|
9249
9557
|
writeFileSync2(
|
|
9250
9558
|
STATS_FILE,
|
|
9251
9559
|
JSON.stringify({
|
|
9252
9560
|
...stats,
|
|
9253
|
-
queue
|
|
9561
|
+
queue,
|
|
9562
|
+
passiveResults
|
|
9254
9563
|
})
|
|
9255
9564
|
);
|
|
9256
9565
|
} catch {
|
|
@@ -9325,6 +9634,21 @@ var init_messageQueue = __esm({
|
|
|
9325
9634
|
}
|
|
9326
9635
|
return removed;
|
|
9327
9636
|
}
|
|
9637
|
+
/**
|
|
9638
|
+
* Change a queued item's delivery semantics, keyed by its command
|
|
9639
|
+
* requestId. Fires onChange (→ persist + queue_changed) on success.
|
|
9640
|
+
* Returns the item, or undefined if no queued item matches (e.g. it was
|
|
9641
|
+
* already consumed).
|
|
9642
|
+
*/
|
|
9643
|
+
setDelivery(id, delivery) {
|
|
9644
|
+
const item = this.items.find((it) => it.command.requestId === id);
|
|
9645
|
+
if (!item) {
|
|
9646
|
+
return void 0;
|
|
9647
|
+
}
|
|
9648
|
+
item.delivery = delivery;
|
|
9649
|
+
this.onChange?.();
|
|
9650
|
+
return item;
|
|
9651
|
+
}
|
|
9328
9652
|
/** Copy of current queue contents (for surfacing on events). */
|
|
9329
9653
|
snapshot() {
|
|
9330
9654
|
return [...this.items];
|
|
@@ -9344,70 +9668,6 @@ var init_messageQueue = __esm({
|
|
|
9344
9668
|
}
|
|
9345
9669
|
});
|
|
9346
9670
|
|
|
9347
|
-
// src/automatedActions/resolve.ts
|
|
9348
|
-
function resolveAction(text) {
|
|
9349
|
-
const parsed = parseSentinel(text);
|
|
9350
|
-
if (!parsed) {
|
|
9351
|
-
return null;
|
|
9352
|
-
}
|
|
9353
|
-
const { name: triggerName, remainder } = parsed;
|
|
9354
|
-
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
9355
|
-
return null;
|
|
9356
|
-
}
|
|
9357
|
-
let params = {};
|
|
9358
|
-
if (remainder) {
|
|
9359
|
-
try {
|
|
9360
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
9361
|
-
} catch {
|
|
9362
|
-
}
|
|
9363
|
-
}
|
|
9364
|
-
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
9365
|
-
let next;
|
|
9366
|
-
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9367
|
-
if (fmMatch) {
|
|
9368
|
-
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
9369
|
-
if (nextMatch) {
|
|
9370
|
-
next = nextMatch[1];
|
|
9371
|
-
}
|
|
9372
|
-
}
|
|
9373
|
-
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
9374
|
-
for (const [key, value] of Object.entries(params)) {
|
|
9375
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
9376
|
-
body = body.replaceAll(`{{${key}}}`, str);
|
|
9377
|
-
}
|
|
9378
|
-
return {
|
|
9379
|
-
message: automatedMessage(triggerName, body),
|
|
9380
|
-
next
|
|
9381
|
-
};
|
|
9382
|
-
}
|
|
9383
|
-
function getActionChain(startName) {
|
|
9384
|
-
const chain = [];
|
|
9385
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9386
|
-
let name = startName;
|
|
9387
|
-
while (name && !seen.has(name)) {
|
|
9388
|
-
seen.add(name);
|
|
9389
|
-
chain.push(name);
|
|
9390
|
-
let body;
|
|
9391
|
-
try {
|
|
9392
|
-
body = readAsset("automatedActions", `${name}.md`);
|
|
9393
|
-
} catch {
|
|
9394
|
-
break;
|
|
9395
|
-
}
|
|
9396
|
-
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
9397
|
-
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
9398
|
-
}
|
|
9399
|
-
return chain;
|
|
9400
|
-
}
|
|
9401
|
-
var NON_ACTION_SENTINELS;
|
|
9402
|
-
var init_resolve = __esm({
|
|
9403
|
-
"src/automatedActions/resolve.ts"() {
|
|
9404
|
-
"use strict";
|
|
9405
|
-
init_assets();
|
|
9406
|
-
init_sentinel();
|
|
9407
|
-
NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
9408
|
-
}
|
|
9409
|
-
});
|
|
9410
|
-
|
|
9411
9671
|
// src/headless/index.ts
|
|
9412
9672
|
var headless_exports = {};
|
|
9413
9673
|
__export(headless_exports, {
|
|
@@ -9431,6 +9691,7 @@ var init_headless = __esm({
|
|
|
9431
9691
|
init_attachments();
|
|
9432
9692
|
init_planFile();
|
|
9433
9693
|
init_stats();
|
|
9694
|
+
init_tools8();
|
|
9434
9695
|
init_messageQueue();
|
|
9435
9696
|
init_resolve();
|
|
9436
9697
|
init_sentinel();
|
|
@@ -9474,11 +9735,27 @@ var init_headless = __esm({
|
|
|
9474
9735
|
* to .remy-stats.json so queued work survives process restarts.
|
|
9475
9736
|
*/
|
|
9476
9737
|
queue;
|
|
9738
|
+
/**
|
|
9739
|
+
* Holding pen for passive background results (tools with
|
|
9740
|
+
* `backgroundNotify: 'passive'`, e.g. specSync). Deliberately outside the
|
|
9741
|
+
* message queue: pen contents never initiate a turn, never latch the
|
|
9742
|
+
* sandbox's queue-derived busy state, and never trigger resume-on-restart.
|
|
9743
|
+
* Swept into the next real turn as a hidden background_results entry.
|
|
9744
|
+
* Persisted to .remy-stats.json alongside the queue.
|
|
9745
|
+
*/
|
|
9746
|
+
passivePen = [];
|
|
9477
9747
|
// External tool bridge
|
|
9478
9748
|
pendingTools = /* @__PURE__ */ new Map();
|
|
9479
9749
|
earlyResults = /* @__PURE__ */ new Map();
|
|
9480
9750
|
// Tool block updates from background completions (separate from the message queue)
|
|
9481
9751
|
pendingBlockUpdates = [];
|
|
9752
|
+
/**
|
|
9753
|
+
* Id of the UI-only tool block synthesized for the in-flight user/gate
|
|
9754
|
+
* compaction (see the compaction listener). Model-invoked compactions have
|
|
9755
|
+
* a real block and never set this. Single slot — triggerCompaction is
|
|
9756
|
+
* single-flight.
|
|
9757
|
+
*/
|
|
9758
|
+
syntheticCompactionId = null;
|
|
9482
9759
|
// Tool lifecycle management — shared across all nesting depths
|
|
9483
9760
|
toolRegistry = new ToolRegistry();
|
|
9484
9761
|
// IO — accumulates stdin bytes between newline boundaries
|
|
@@ -9509,6 +9786,8 @@ var init_headless = __esm({
|
|
|
9509
9786
|
this.persistStats();
|
|
9510
9787
|
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
9511
9788
|
});
|
|
9789
|
+
this.passivePen = loadPassiveResults();
|
|
9790
|
+
this.persistStats();
|
|
9512
9791
|
if (resumed) {
|
|
9513
9792
|
this.emit("session_restored", {
|
|
9514
9793
|
messageCount: this.state.messages.length,
|
|
@@ -9531,15 +9810,61 @@ var init_headless = __esm({
|
|
|
9531
9810
|
);
|
|
9532
9811
|
this.sessionStats.compactionInProgress = true;
|
|
9533
9812
|
this.persistStats();
|
|
9813
|
+
if (!event.toolCallId && (event.origin === "user" || event.origin === "gate")) {
|
|
9814
|
+
const id = `compact_${Date.now()}`;
|
|
9815
|
+
this.syntheticCompactionId = id;
|
|
9816
|
+
this.state.messages.push({
|
|
9817
|
+
role: "assistant",
|
|
9818
|
+
content: [
|
|
9819
|
+
{
|
|
9820
|
+
type: "tool",
|
|
9821
|
+
id,
|
|
9822
|
+
name: "compactConversation",
|
|
9823
|
+
input: {},
|
|
9824
|
+
startedAt: Date.now(),
|
|
9825
|
+
background: true,
|
|
9826
|
+
uiOnly: true,
|
|
9827
|
+
result: "Compaction started in the background."
|
|
9828
|
+
}
|
|
9829
|
+
]
|
|
9830
|
+
});
|
|
9831
|
+
saveSession(this.state);
|
|
9832
|
+
this.onEvent({
|
|
9833
|
+
type: "tool_start",
|
|
9834
|
+
id,
|
|
9835
|
+
name: "compactConversation",
|
|
9836
|
+
input: {},
|
|
9837
|
+
background: true
|
|
9838
|
+
});
|
|
9839
|
+
this.onEvent({
|
|
9840
|
+
type: "tool_done",
|
|
9841
|
+
id,
|
|
9842
|
+
name: "compactConversation",
|
|
9843
|
+
result: "Compaction started in the background.",
|
|
9844
|
+
isError: false
|
|
9845
|
+
});
|
|
9846
|
+
}
|
|
9534
9847
|
} else {
|
|
9535
9848
|
const data = event.error ? { error: event.error } : {};
|
|
9536
9849
|
this.emit("compaction_complete", data, event.requestId);
|
|
9850
|
+
if (this.syntheticCompactionId) {
|
|
9851
|
+
const id = this.syntheticCompactionId;
|
|
9852
|
+
this.syntheticCompactionId = null;
|
|
9853
|
+
this.onBackgroundComplete(
|
|
9854
|
+
id,
|
|
9855
|
+
"compactConversation",
|
|
9856
|
+
event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
|
|
9857
|
+
);
|
|
9858
|
+
}
|
|
9537
9859
|
this.sessionStats.compactionInProgress = false;
|
|
9538
9860
|
if (!event.error) {
|
|
9539
9861
|
this.sessionStats.lastContextSize = 0;
|
|
9540
9862
|
}
|
|
9541
9863
|
this.sessionStats.messageCount = this.state.messages.length;
|
|
9542
9864
|
this.persistStats();
|
|
9865
|
+
if (!this.running) {
|
|
9866
|
+
this.kickDrain();
|
|
9867
|
+
}
|
|
9543
9868
|
}
|
|
9544
9869
|
});
|
|
9545
9870
|
process.stdin.setEncoding("utf-8");
|
|
@@ -9620,10 +9945,10 @@ var init_headless = __esm({
|
|
|
9620
9945
|
//////////////////////////////////////////////////////////////////////////////
|
|
9621
9946
|
// Stats + queue persistence
|
|
9622
9947
|
//////////////////////////////////////////////////////////////////////////////
|
|
9623
|
-
/** Persist sessionStats + queue snapshot to .remy-stats.json. */
|
|
9948
|
+
/** Persist sessionStats + queue snapshot + passive pen to .remy-stats.json. */
|
|
9624
9949
|
persistStats() {
|
|
9625
9950
|
this.sessionStats.updatedAt = Date.now();
|
|
9626
|
-
writeStats(this.sessionStats, this.queue.snapshot());
|
|
9951
|
+
writeStats(this.sessionStats, this.queue.snapshot(), this.passivePen);
|
|
9627
9952
|
}
|
|
9628
9953
|
//////////////////////////////////////////////////////////////////////////////
|
|
9629
9954
|
// Background completions (tool-block mutation; message delivery via queue)
|
|
@@ -9678,17 +10003,20 @@ var init_headless = __esm({
|
|
|
9678
10003
|
await triggerCompaction(this.state, this.config, {
|
|
9679
10004
|
blocking: true,
|
|
9680
10005
|
requestId,
|
|
9681
|
-
model: this.opts.model
|
|
10006
|
+
model: this.opts.model,
|
|
10007
|
+
origin: "gate"
|
|
9682
10008
|
});
|
|
9683
10009
|
applyPendingSummaries(this.state);
|
|
9684
10010
|
} catch {
|
|
9685
10011
|
}
|
|
9686
10012
|
}
|
|
9687
10013
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
10014
|
+
const notify = getToolByName(name)?.backgroundNotify ?? "wake";
|
|
9688
10015
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
9689
10016
|
log16.info("Background complete", {
|
|
9690
10017
|
toolCallId,
|
|
9691
10018
|
name,
|
|
10019
|
+
notify,
|
|
9692
10020
|
requestId: this.currentRequestId
|
|
9693
10021
|
});
|
|
9694
10022
|
this.onEvent({
|
|
@@ -9697,6 +10025,20 @@ var init_headless = __esm({
|
|
|
9697
10025
|
name,
|
|
9698
10026
|
result
|
|
9699
10027
|
});
|
|
10028
|
+
if (notify === "passive") {
|
|
10029
|
+
this.passivePen.push({ toolCallId, name, result });
|
|
10030
|
+
this.persistStats();
|
|
10031
|
+
if (!this.running) {
|
|
10032
|
+
this.applyPendingBlockUpdates();
|
|
10033
|
+
}
|
|
10034
|
+
return;
|
|
10035
|
+
}
|
|
10036
|
+
if (notify === "silent") {
|
|
10037
|
+
if (!this.running) {
|
|
10038
|
+
this.applyPendingBlockUpdates();
|
|
10039
|
+
}
|
|
10040
|
+
return;
|
|
10041
|
+
}
|
|
9700
10042
|
this.queue.push({
|
|
9701
10043
|
command: {
|
|
9702
10044
|
action: "message",
|
|
@@ -9765,7 +10107,10 @@ var init_headless = __esm({
|
|
|
9765
10107
|
...e.attachments && { attachments: e.attachments },
|
|
9766
10108
|
// Queue-delivered entries are flagged so the frontend renders the
|
|
9767
10109
|
// echo (idle sends are rendered optimistically instead).
|
|
9768
|
-
...e.queued && { queued: true }
|
|
10110
|
+
...e.queued && { queued: true },
|
|
10111
|
+
// Hidden entries (e.g. the passive background_results sweep) are
|
|
10112
|
+
// flagged so the sandbox/frontend suppress the bubble.
|
|
10113
|
+
...e.hidden && { hidden: true }
|
|
9769
10114
|
},
|
|
9770
10115
|
// A merged turn emits one user_message per absorbed entry — each
|
|
9771
10116
|
// carries its own original requestId, not the turn's.
|
|
@@ -10075,6 +10420,43 @@ var init_headless = __esm({
|
|
|
10075
10420
|
this.completedEmitted = false;
|
|
10076
10421
|
this.lastCompleted = null;
|
|
10077
10422
|
this.turnStart = Date.now();
|
|
10423
|
+
const inflight2 = getInflightCompaction();
|
|
10424
|
+
if (inflight2) {
|
|
10425
|
+
await inflight2.catch(() => {
|
|
10426
|
+
});
|
|
10427
|
+
}
|
|
10428
|
+
applyPendingSummaries(this.state);
|
|
10429
|
+
if (this.passivePen.length > 0) {
|
|
10430
|
+
const swept = this.passivePen.splice(0);
|
|
10431
|
+
this.persistStats();
|
|
10432
|
+
entries.unshift({
|
|
10433
|
+
text: buildBackgroundResultsMessage(swept),
|
|
10434
|
+
hidden: true
|
|
10435
|
+
});
|
|
10436
|
+
}
|
|
10437
|
+
const consumedRids = [];
|
|
10438
|
+
const takeSteering = async () => {
|
|
10439
|
+
const items = this.queue.removeWhere(
|
|
10440
|
+
(it) => it.source === "user" && it.delivery === "asap" && !this.isDrainBarrier(it)
|
|
10441
|
+
);
|
|
10442
|
+
const steered = [];
|
|
10443
|
+
for (const it of items) {
|
|
10444
|
+
const attachments = it.command.attachments;
|
|
10445
|
+
const attachmentHeader = await this.persistEntryAttachments(attachments);
|
|
10446
|
+
const rid = it.command.requestId;
|
|
10447
|
+
if (rid) {
|
|
10448
|
+
consumedRids.push(rid);
|
|
10449
|
+
}
|
|
10450
|
+
steered.push({
|
|
10451
|
+
text: it.command.text ?? "",
|
|
10452
|
+
attachments,
|
|
10453
|
+
attachmentHeader,
|
|
10454
|
+
requestId: rid,
|
|
10455
|
+
queued: true
|
|
10456
|
+
});
|
|
10457
|
+
}
|
|
10458
|
+
return steered;
|
|
10459
|
+
};
|
|
10078
10460
|
await this.runForcedCompactionIfNeeded(requestId);
|
|
10079
10461
|
try {
|
|
10080
10462
|
await runTurn({
|
|
@@ -10088,6 +10470,7 @@ var init_headless = __esm({
|
|
|
10088
10470
|
requestId,
|
|
10089
10471
|
signal: this.currentAbort.signal,
|
|
10090
10472
|
onEvent: this.onEvent,
|
|
10473
|
+
takeSteering,
|
|
10091
10474
|
resolveExternalTool: this.resolveExternalTool,
|
|
10092
10475
|
toolRegistry: this.toolRegistry,
|
|
10093
10476
|
onBackgroundComplete: this.onBackgroundComplete
|
|
@@ -10117,7 +10500,7 @@ var init_headless = __esm({
|
|
|
10117
10500
|
});
|
|
10118
10501
|
}
|
|
10119
10502
|
const outcome = this.primaryOutcome();
|
|
10120
|
-
for (const rid of absorbedRids) {
|
|
10503
|
+
for (const rid of [...absorbedRids, ...consumedRids]) {
|
|
10121
10504
|
this.emit(
|
|
10122
10505
|
"completed",
|
|
10123
10506
|
{
|
|
@@ -10132,7 +10515,7 @@ var init_headless = __esm({
|
|
|
10132
10515
|
this.applyPendingBlockUpdates();
|
|
10133
10516
|
}
|
|
10134
10517
|
async handleMessage(parsed, requestId) {
|
|
10135
|
-
if (this.running) {
|
|
10518
|
+
if (this.running || getInflightCompaction()) {
|
|
10136
10519
|
const command = { ...parsed };
|
|
10137
10520
|
if (requestId && command.requestId === void 0) {
|
|
10138
10521
|
command.requestId = requestId;
|
|
@@ -10142,6 +10525,9 @@ var init_headless = __esm({
|
|
|
10142
10525
|
source: "user",
|
|
10143
10526
|
enqueuedAt: Date.now()
|
|
10144
10527
|
});
|
|
10528
|
+
if (!this.running && !getInflightCompaction()) {
|
|
10529
|
+
this.kickDrain();
|
|
10530
|
+
}
|
|
10145
10531
|
return;
|
|
10146
10532
|
}
|
|
10147
10533
|
this.running = true;
|
|
@@ -10149,6 +10535,7 @@ var init_headless = __esm({
|
|
|
10149
10535
|
await this.runSingleTurn(parsed, requestId);
|
|
10150
10536
|
await this.drainQueueLoop();
|
|
10151
10537
|
} finally {
|
|
10538
|
+
this.currentAbort?.abort();
|
|
10152
10539
|
this.currentAbort = null;
|
|
10153
10540
|
this.currentRequestId = void 0;
|
|
10154
10541
|
this.running = false;
|
|
@@ -10180,6 +10567,18 @@ var init_headless = __esm({
|
|
|
10180
10567
|
async drainQueueLoop() {
|
|
10181
10568
|
while (this.queue.length > 0) {
|
|
10182
10569
|
const head = this.queue.peek();
|
|
10570
|
+
if (head.command.action === "compact") {
|
|
10571
|
+
this.queue.shift();
|
|
10572
|
+
await triggerCompaction(this.state, this.config, {
|
|
10573
|
+
blocking: true,
|
|
10574
|
+
requestId: head.command.requestId,
|
|
10575
|
+
model: this.opts.model,
|
|
10576
|
+
origin: "user"
|
|
10577
|
+
}).catch(() => {
|
|
10578
|
+
});
|
|
10579
|
+
applyPendingSummaries(this.state);
|
|
10580
|
+
continue;
|
|
10581
|
+
}
|
|
10183
10582
|
if (head.source === "chain") {
|
|
10184
10583
|
const item = this.queue.shift();
|
|
10185
10584
|
const rid = item.command.requestId ?? `chain-${Date.now()}`;
|
|
@@ -10224,6 +10623,7 @@ var init_headless = __esm({
|
|
|
10224
10623
|
try {
|
|
10225
10624
|
await this.drainQueueLoop();
|
|
10226
10625
|
} finally {
|
|
10626
|
+
this.currentAbort?.abort();
|
|
10227
10627
|
this.currentAbort = null;
|
|
10228
10628
|
this.currentRequestId = void 0;
|
|
10229
10629
|
this.running = false;
|
|
@@ -10412,6 +10812,33 @@ var init_headless = __esm({
|
|
|
10412
10812
|
);
|
|
10413
10813
|
return;
|
|
10414
10814
|
}
|
|
10815
|
+
if (action === "setQueuedDelivery") {
|
|
10816
|
+
const id = parsed.id;
|
|
10817
|
+
const delivery = parsed.delivery;
|
|
10818
|
+
if (!id || delivery !== "asap" && delivery !== "afterTurn") {
|
|
10819
|
+
this.emit(
|
|
10820
|
+
"completed",
|
|
10821
|
+
{
|
|
10822
|
+
success: false,
|
|
10823
|
+
error: 'setQueuedDelivery requires id and delivery ("asap" | "afterTurn")'
|
|
10824
|
+
},
|
|
10825
|
+
requestId
|
|
10826
|
+
);
|
|
10827
|
+
return;
|
|
10828
|
+
}
|
|
10829
|
+
const target = this.queue.snapshot().find((it) => it.command.requestId === id);
|
|
10830
|
+
if (!target || target.source !== "user" || this.isDrainBarrier(target)) {
|
|
10831
|
+
this.emit(
|
|
10832
|
+
"completed",
|
|
10833
|
+
{ success: false, error: "message not found or not promotable" },
|
|
10834
|
+
requestId
|
|
10835
|
+
);
|
|
10836
|
+
return;
|
|
10837
|
+
}
|
|
10838
|
+
this.queue.setDelivery(id, delivery);
|
|
10839
|
+
this.emit("completed", { success: true }, requestId);
|
|
10840
|
+
return;
|
|
10841
|
+
}
|
|
10415
10842
|
if (action === "stop_tool") {
|
|
10416
10843
|
const id = parsed.id;
|
|
10417
10844
|
const mode = parsed.mode ?? "hard";
|
|
@@ -10443,11 +10870,31 @@ var init_headless = __esm({
|
|
|
10443
10870
|
return;
|
|
10444
10871
|
}
|
|
10445
10872
|
if (action === "compact") {
|
|
10873
|
+
if (this.running) {
|
|
10874
|
+
const alreadyQueued = this.queue.snapshot().some((it) => it.command.action === "compact");
|
|
10875
|
+
if (!alreadyQueued) {
|
|
10876
|
+
this.queue.push({
|
|
10877
|
+
command: {
|
|
10878
|
+
action: "compact",
|
|
10879
|
+
// Display label for the queue card (matches the automated
|
|
10880
|
+
// action idiom); never resolved as an action — drainQueueLoop
|
|
10881
|
+
// routes on command.action before any sentinel handling.
|
|
10882
|
+
text: sentinel("compact"),
|
|
10883
|
+
requestId
|
|
10884
|
+
},
|
|
10885
|
+
source: "user",
|
|
10886
|
+
enqueuedAt: Date.now()
|
|
10887
|
+
});
|
|
10888
|
+
}
|
|
10889
|
+
this.emit("completed", { success: true, queued: true }, requestId);
|
|
10890
|
+
return;
|
|
10891
|
+
}
|
|
10446
10892
|
try {
|
|
10447
10893
|
await triggerCompaction(this.state, this.config, {
|
|
10448
10894
|
blocking: false,
|
|
10449
10895
|
requestId,
|
|
10450
|
-
model: this.opts.model
|
|
10896
|
+
model: this.opts.model,
|
|
10897
|
+
origin: "user"
|
|
10451
10898
|
});
|
|
10452
10899
|
if (!this.running) {
|
|
10453
10900
|
applyPendingSummaries(this.state);
|