@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/headless.js
CHANGED
|
@@ -2082,6 +2082,12 @@ var setProjectMetadataTool = {
|
|
|
2082
2082
|
|
|
2083
2083
|
// src/tools/common/compactConversation.ts
|
|
2084
2084
|
var compactConversationTool = {
|
|
2085
|
+
backgroundOnly: true,
|
|
2086
|
+
// Silent: the completed summary is written onto the tool block for the UI
|
|
2087
|
+
// detail view only. The model must never receive it as a message — it gets
|
|
2088
|
+
// the summary the correct way, as the checkpoint prefix in every later API
|
|
2089
|
+
// call (cleanMessagesForApi).
|
|
2090
|
+
backgroundNotify: "silent",
|
|
2085
2091
|
definition: {
|
|
2086
2092
|
clearable: false,
|
|
2087
2093
|
name: "compactConversation",
|
|
@@ -2095,11 +2101,32 @@ var compactConversationTool = {
|
|
|
2095
2101
|
if (!context?.conversationMessages || !context.apiConfig) {
|
|
2096
2102
|
return "Error: compaction requires execution context.";
|
|
2097
2103
|
}
|
|
2104
|
+
const { toolCallId, onBackgroundComplete, toolRegistry } = context;
|
|
2098
2105
|
triggerCompaction(
|
|
2099
2106
|
{ messages: context.conversationMessages },
|
|
2100
2107
|
context.apiConfig,
|
|
2101
|
-
|
|
2102
|
-
|
|
2108
|
+
// origin/toolCallId tell the headless listener a real block exists —
|
|
2109
|
+
// it must not synthesize a UI-only one for this compaction.
|
|
2110
|
+
{
|
|
2111
|
+
blocking: false,
|
|
2112
|
+
requestId: context.requestId,
|
|
2113
|
+
origin: "tool",
|
|
2114
|
+
toolCallId
|
|
2115
|
+
}
|
|
2116
|
+
).then((summaries) => {
|
|
2117
|
+
onBackgroundComplete?.(
|
|
2118
|
+
toolCallId,
|
|
2119
|
+
"compactConversation",
|
|
2120
|
+
formatSummariesResult(summaries)
|
|
2121
|
+
);
|
|
2122
|
+
}).catch((err) => {
|
|
2123
|
+
onBackgroundComplete?.(
|
|
2124
|
+
toolCallId,
|
|
2125
|
+
"compactConversation",
|
|
2126
|
+
`Error: ${err.message || "Compaction failed"}`
|
|
2127
|
+
);
|
|
2128
|
+
}).finally(() => {
|
|
2129
|
+
toolRegistry?.unregister(toolCallId);
|
|
2103
2130
|
});
|
|
2104
2131
|
return "Compaction started in the background.";
|
|
2105
2132
|
}
|
|
@@ -3385,16 +3412,55 @@ function capToolResult(result) {
|
|
|
3385
3412
|
}
|
|
3386
3413
|
|
|
3387
3414
|
// src/statusWatcher.ts
|
|
3415
|
+
var INTERNAL_PAYLOAD_MARKERS = [
|
|
3416
|
+
"[USER CANCELLED]",
|
|
3417
|
+
"[INTERRUPTED]",
|
|
3418
|
+
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
3419
|
+
"<background_results>",
|
|
3420
|
+
"<tool_result"
|
|
3421
|
+
];
|
|
3422
|
+
function sanitizeStatusText(text) {
|
|
3423
|
+
if (!text) {
|
|
3424
|
+
return "";
|
|
3425
|
+
}
|
|
3426
|
+
for (const marker of INTERNAL_PAYLOAD_MARKERS) {
|
|
3427
|
+
if (text.includes(marker)) {
|
|
3428
|
+
return "";
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
const truncIdx = text.indexOf("(tool result truncated at");
|
|
3432
|
+
return truncIdx === -1 ? text : text.slice(0, truncIdx);
|
|
3433
|
+
}
|
|
3388
3434
|
function startStatusWatcher(config) {
|
|
3389
3435
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
3390
3436
|
let inflight2 = false;
|
|
3391
3437
|
let stopped = false;
|
|
3392
3438
|
let pauseCount = 0;
|
|
3393
3439
|
const url = `${apiConfig.baseUrl}/_internal/v2/agent/remy/generate-status`;
|
|
3440
|
+
let consecutiveFailures = 0;
|
|
3441
|
+
let backoffMs = 0;
|
|
3442
|
+
let nextAllowedAt = 0;
|
|
3443
|
+
const MAX_CONSECUTIVE_FAILURES = 10;
|
|
3444
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
3445
|
+
function recordFailure() {
|
|
3446
|
+
consecutiveFailures++;
|
|
3447
|
+
backoffMs = Math.min(
|
|
3448
|
+
backoffMs === 0 ? interval : backoffMs * 2,
|
|
3449
|
+
MAX_BACKOFF_MS
|
|
3450
|
+
);
|
|
3451
|
+
nextAllowedAt = Date.now() + backoffMs;
|
|
3452
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !stopped) {
|
|
3453
|
+
stopped = true;
|
|
3454
|
+
clearInterval(timer);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3394
3457
|
async function tick() {
|
|
3395
3458
|
if (stopped || signal?.aborted || inflight2 || pauseCount > 0) {
|
|
3396
3459
|
return;
|
|
3397
3460
|
}
|
|
3461
|
+
if (Date.now() < nextAllowedAt) {
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3398
3464
|
inflight2 = true;
|
|
3399
3465
|
try {
|
|
3400
3466
|
const context = getContext();
|
|
@@ -3410,18 +3476,29 @@ function startStatusWatcher(config) {
|
|
|
3410
3476
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
3411
3477
|
signal
|
|
3412
3478
|
});
|
|
3413
|
-
if (
|
|
3479
|
+
if (stopped) {
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
if (!res.ok) {
|
|
3483
|
+
recordFailure();
|
|
3414
3484
|
return;
|
|
3415
3485
|
}
|
|
3416
3486
|
const data = await res.json();
|
|
3417
3487
|
if (!data.label) {
|
|
3488
|
+
recordFailure();
|
|
3418
3489
|
return;
|
|
3419
3490
|
}
|
|
3491
|
+
consecutiveFailures = 0;
|
|
3492
|
+
backoffMs = 0;
|
|
3493
|
+
nextAllowedAt = 0;
|
|
3420
3494
|
if (pauseCount > 0 || stopped || signal?.aborted) {
|
|
3421
3495
|
return;
|
|
3422
3496
|
}
|
|
3423
3497
|
onStatus(data.label);
|
|
3424
3498
|
} catch {
|
|
3499
|
+
if (!stopped && !signal?.aborted) {
|
|
3500
|
+
recordFailure();
|
|
3501
|
+
}
|
|
3425
3502
|
} finally {
|
|
3426
3503
|
inflight2 = false;
|
|
3427
3504
|
}
|
|
@@ -3518,7 +3595,12 @@ ${summaryBlock.text}
|
|
|
3518
3595
|
}
|
|
3519
3596
|
startIdx = checkpointIdx + 1;
|
|
3520
3597
|
}
|
|
3521
|
-
const
|
|
3598
|
+
const apiMessages = messages.slice(startIdx).filter(
|
|
3599
|
+
(msg) => !(msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some(
|
|
3600
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
3601
|
+
))
|
|
3602
|
+
);
|
|
3603
|
+
const messagesToProcess = fixOrphanedToolCalls(apiMessages);
|
|
3522
3604
|
const toolUseIds = /* @__PURE__ */ new Set();
|
|
3523
3605
|
for (const msg of messagesToProcess) {
|
|
3524
3606
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
@@ -3660,329 +3742,341 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3660
3742
|
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
3661
3743
|
}
|
|
3662
3744
|
let lastToolResult = "";
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3745
|
+
let watchedBlocks = [];
|
|
3746
|
+
let watchedToolNames = "";
|
|
3747
|
+
const statusWatcher = startStatusWatcher({
|
|
3748
|
+
apiConfig,
|
|
3749
|
+
getContext: () => {
|
|
3750
|
+
const parts = [];
|
|
3751
|
+
if (task) {
|
|
3752
|
+
parts.push(`Task: ${sanitizeStatusText(task).slice(-200)}`);
|
|
3753
|
+
}
|
|
3754
|
+
const text = getPartialText(watchedBlocks);
|
|
3755
|
+
if (text) {
|
|
3756
|
+
parts.push(`Assistant text: ${text.slice(-500)}`);
|
|
3757
|
+
}
|
|
3758
|
+
if (watchedToolNames) {
|
|
3759
|
+
parts.push(`Tool: ${watchedToolNames}`);
|
|
3760
|
+
}
|
|
3761
|
+
const toolResult = sanitizeStatusText(lastToolResult);
|
|
3762
|
+
if (toolResult) {
|
|
3763
|
+
parts.push(`Tool result: ${toolResult.slice(-200)}`);
|
|
3764
|
+
}
|
|
3765
|
+
return parts.join("\n");
|
|
3766
|
+
},
|
|
3767
|
+
onStatus: (label) => emit({ type: "status", message: label }),
|
|
3768
|
+
signal
|
|
3769
|
+
});
|
|
3770
|
+
try {
|
|
3771
|
+
while (true) {
|
|
3772
|
+
turns++;
|
|
3773
|
+
if (signal?.aborted) {
|
|
3774
|
+
return abortResult([]);
|
|
3775
|
+
}
|
|
3776
|
+
const iterStart = Date.now();
|
|
3777
|
+
const contentBlocks = [];
|
|
3778
|
+
let thinkingStartedAt = 0;
|
|
3779
|
+
let lastThinkingRelatedStartedAt;
|
|
3780
|
+
let stopReason = "end_turn";
|
|
3781
|
+
let lastUsage;
|
|
3782
|
+
let lastProviderMetadata;
|
|
3783
|
+
let lastModelId;
|
|
3784
|
+
watchedBlocks = contentBlocks;
|
|
3785
|
+
watchedToolNames = "";
|
|
3786
|
+
try {
|
|
3787
|
+
for await (const event of streamChatWithRetry(
|
|
3788
|
+
{
|
|
3789
|
+
...apiConfig,
|
|
3790
|
+
model,
|
|
3791
|
+
requestId,
|
|
3792
|
+
subAgentId,
|
|
3793
|
+
system: fullSystem,
|
|
3794
|
+
messages: cleanMessagesForApi(messages),
|
|
3795
|
+
tools: tools2,
|
|
3796
|
+
signal
|
|
3797
|
+
},
|
|
3798
|
+
{
|
|
3799
|
+
onRetry: (attempt) => emit({
|
|
3800
|
+
type: "status",
|
|
3801
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
3802
|
+
})
|
|
3803
|
+
}
|
|
3804
|
+
)) {
|
|
3805
|
+
if (signal?.aborted) {
|
|
3806
|
+
break;
|
|
3807
|
+
}
|
|
3808
|
+
switch (event.type) {
|
|
3809
|
+
case "text": {
|
|
3810
|
+
const lastBlock = contentBlocks.at(-1);
|
|
3811
|
+
if (lastBlock?.type === "text") {
|
|
3812
|
+
lastBlock.text += event.text;
|
|
3813
|
+
} else {
|
|
3814
|
+
contentBlocks.push({
|
|
3815
|
+
type: "text",
|
|
3816
|
+
text: event.text,
|
|
3817
|
+
startedAt: event.ts
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
emit({ type: "text", text: event.text });
|
|
3821
|
+
break;
|
|
3822
|
+
}
|
|
3823
|
+
case "thinking":
|
|
3824
|
+
if (!thinkingStartedAt) {
|
|
3825
|
+
thinkingStartedAt = event.ts;
|
|
3826
|
+
}
|
|
3827
|
+
emit({ type: "thinking", text: event.text });
|
|
3828
|
+
break;
|
|
3829
|
+
case "thinking_complete":
|
|
3830
|
+
contentBlocks.push({
|
|
3831
|
+
type: "thinking",
|
|
3832
|
+
thinking: event.thinking,
|
|
3833
|
+
signature: event.signature,
|
|
3834
|
+
startedAt: thinkingStartedAt,
|
|
3835
|
+
completedAt: event.ts
|
|
3836
|
+
});
|
|
3837
|
+
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
3838
|
+
thinkingStartedAt = 0;
|
|
3839
|
+
break;
|
|
3840
|
+
case "redacted_thinking_complete": {
|
|
3841
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
3842
|
+
contentBlocks.push({
|
|
3843
|
+
type: "redacted_thinking",
|
|
3844
|
+
data: event.data,
|
|
3845
|
+
startedAt,
|
|
3846
|
+
completedAt: event.ts
|
|
3847
|
+
});
|
|
3848
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
3849
|
+
break;
|
|
3850
|
+
}
|
|
3851
|
+
case "tool_use":
|
|
3852
|
+
contentBlocks.push({
|
|
3853
|
+
type: "tool",
|
|
3854
|
+
id: event.id,
|
|
3855
|
+
name: event.name,
|
|
3856
|
+
input: event.input,
|
|
3857
|
+
startedAt: Date.now()
|
|
3858
|
+
});
|
|
3859
|
+
emit({
|
|
3860
|
+
type: "tool_start",
|
|
3861
|
+
id: event.id,
|
|
3862
|
+
name: event.name,
|
|
3863
|
+
input: event.input
|
|
3864
|
+
});
|
|
3865
|
+
break;
|
|
3866
|
+
case "done":
|
|
3867
|
+
stopReason = event.stopReason;
|
|
3868
|
+
lastUsage = {
|
|
3869
|
+
inputTokens: event.usage.inputTokens,
|
|
3870
|
+
outputTokens: event.usage.outputTokens,
|
|
3871
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3872
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3873
|
+
llmCalls: 1
|
|
3874
|
+
};
|
|
3875
|
+
lastProviderMetadata = event.providerMetadata;
|
|
3876
|
+
lastModelId = event.modelId;
|
|
3877
|
+
recordUsage({
|
|
3878
|
+
ts: Date.now(),
|
|
3879
|
+
requestId,
|
|
3880
|
+
agentName: subAgentId || "sub-agent",
|
|
3881
|
+
parentToolId,
|
|
3882
|
+
modelId: event.modelId,
|
|
3883
|
+
inputTokens: event.usage.inputTokens,
|
|
3884
|
+
outputTokens: event.usage.outputTokens,
|
|
3885
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3886
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3887
|
+
cost: nanoToDollars(event.cost),
|
|
3888
|
+
billingEvents: event.billingEvents,
|
|
3889
|
+
durationMs: Date.now() - iterStart,
|
|
3890
|
+
toolNames: contentBlocks.filter(
|
|
3891
|
+
(b) => b.type === "tool"
|
|
3892
|
+
).map((b) => b.name)
|
|
3893
|
+
});
|
|
3894
|
+
break;
|
|
3895
|
+
case "error":
|
|
3896
|
+
statusWatcher.stop();
|
|
3897
|
+
return {
|
|
3898
|
+
text: `Error: ${event.error}`,
|
|
3899
|
+
messages: thisInvocation()
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3716
3902
|
}
|
|
3717
|
-
)
|
|
3718
|
-
if (signal?.aborted) {
|
|
3719
|
-
|
|
3903
|
+
} catch (err) {
|
|
3904
|
+
if (!signal?.aborted) {
|
|
3905
|
+
throw err;
|
|
3720
3906
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3907
|
+
}
|
|
3908
|
+
if (signal?.aborted) {
|
|
3909
|
+
statusWatcher.stop();
|
|
3910
|
+
return abortResult(contentBlocks);
|
|
3911
|
+
}
|
|
3912
|
+
messages.push({
|
|
3913
|
+
role: "assistant",
|
|
3914
|
+
content: contentBlocks,
|
|
3915
|
+
...lastUsage ? { usage: lastUsage } : {},
|
|
3916
|
+
...lastProviderMetadata ? { providerMetadata: lastProviderMetadata } : {},
|
|
3917
|
+
model: lastModelId ?? model
|
|
3918
|
+
});
|
|
3919
|
+
const toolCalls = contentBlocks.filter(
|
|
3920
|
+
(b) => b.type === "tool"
|
|
3921
|
+
);
|
|
3922
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3923
|
+
statusWatcher.stop();
|
|
3924
|
+
const text = getPartialText(contentBlocks);
|
|
3925
|
+
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
3926
|
+
return {
|
|
3927
|
+
text,
|
|
3928
|
+
messages: thisInvocation(),
|
|
3929
|
+
...hasArtifacts ? { artifacts } : {}
|
|
3930
|
+
};
|
|
3931
|
+
}
|
|
3932
|
+
log7.info("Tools executing", {
|
|
3933
|
+
requestId,
|
|
3934
|
+
parentToolId,
|
|
3935
|
+
count: toolCalls.length,
|
|
3936
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
3937
|
+
});
|
|
3938
|
+
watchedToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
3939
|
+
const results = await Promise.all(
|
|
3940
|
+
toolCalls.map(async (tc) => {
|
|
3941
|
+
if (signal?.aborted) {
|
|
3942
|
+
return {
|
|
3943
|
+
id: tc.id,
|
|
3944
|
+
result: USER_CANCELLED_RESULT,
|
|
3945
|
+
isError: true
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
let settle;
|
|
3949
|
+
const resultPromise = new Promise((res) => {
|
|
3950
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
3951
|
+
});
|
|
3952
|
+
let toolAbort = new AbortController();
|
|
3953
|
+
const cascadeAbort = () => toolAbort.abort();
|
|
3954
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
3955
|
+
let settled = false;
|
|
3956
|
+
const safeSettle = (result, isError) => {
|
|
3957
|
+
if (settled) {
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
settled = true;
|
|
3961
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
3962
|
+
settle(result, isError);
|
|
3963
|
+
};
|
|
3964
|
+
const run2 = async (input) => {
|
|
3965
|
+
try {
|
|
3966
|
+
let result;
|
|
3967
|
+
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
3968
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
3969
|
+
} else {
|
|
3970
|
+
const onLog = (line) => emit({
|
|
3971
|
+
type: "tool_input_delta",
|
|
3972
|
+
id: tc.id,
|
|
3973
|
+
name: tc.name,
|
|
3974
|
+
result: line
|
|
3975
|
+
});
|
|
3976
|
+
result = await executeTool2(
|
|
3977
|
+
tc.name,
|
|
3978
|
+
input,
|
|
3979
|
+
tc.id,
|
|
3980
|
+
onLog,
|
|
3981
|
+
subAgentMessages
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3984
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
3985
|
+
} catch (err) {
|
|
3986
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3989
|
+
const entry = {
|
|
3990
|
+
id: tc.id,
|
|
3991
|
+
name: tc.name,
|
|
3992
|
+
input: tc.input,
|
|
3993
|
+
parentToolId,
|
|
3994
|
+
abortController: toolAbort,
|
|
3995
|
+
startedAt: Date.now(),
|
|
3996
|
+
settle: safeSettle,
|
|
3997
|
+
rerun: (newInput) => {
|
|
3998
|
+
settled = false;
|
|
3999
|
+
toolAbort = new AbortController();
|
|
4000
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
4001
|
+
once: true
|
|
3731
4002
|
});
|
|
4003
|
+
entry.abortController = toolAbort;
|
|
4004
|
+
entry.input = newInput;
|
|
4005
|
+
run2(newInput);
|
|
3732
4006
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
4007
|
+
};
|
|
4008
|
+
toolRegistry?.register(entry);
|
|
4009
|
+
const toolStart = Date.now();
|
|
4010
|
+
run2(tc.input);
|
|
4011
|
+
const r = await resultPromise;
|
|
4012
|
+
toolRegistry?.unregister(tc.id);
|
|
4013
|
+
log7.info("Tool completed", {
|
|
4014
|
+
requestId,
|
|
4015
|
+
parentToolId,
|
|
4016
|
+
toolCallId: tc.id,
|
|
4017
|
+
name: tc.name,
|
|
4018
|
+
durationMs: Date.now() - toolStart,
|
|
4019
|
+
isError: r.isError
|
|
4020
|
+
});
|
|
4021
|
+
emit({
|
|
4022
|
+
type: "tool_done",
|
|
4023
|
+
id: tc.id,
|
|
4024
|
+
name: tc.name,
|
|
4025
|
+
result: r.result,
|
|
4026
|
+
isError: r.isError
|
|
4027
|
+
});
|
|
4028
|
+
return r;
|
|
4029
|
+
})
|
|
4030
|
+
);
|
|
4031
|
+
lastToolResult = results.at(-1)?.result ?? "";
|
|
4032
|
+
for (const r of results) {
|
|
4033
|
+
const block = contentBlocks.find(
|
|
4034
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
4035
|
+
);
|
|
4036
|
+
if (block?.type === "tool") {
|
|
4037
|
+
block.result = r.result;
|
|
4038
|
+
block.isError = r.isError;
|
|
4039
|
+
block.completedAt = Date.now();
|
|
4040
|
+
const innerMsgs = subAgentMessages.get(r.id);
|
|
4041
|
+
if (innerMsgs) {
|
|
4042
|
+
block.subAgentMessages = innerMsgs;
|
|
3735
4043
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
4044
|
+
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
4045
|
+
try {
|
|
4046
|
+
artifacts[block.name] = JSON.parse(r.result);
|
|
4047
|
+
} catch {
|
|
3739
4048
|
}
|
|
3740
|
-
emit({ type: "thinking", text: event.text });
|
|
3741
|
-
break;
|
|
3742
|
-
case "thinking_complete":
|
|
3743
|
-
contentBlocks.push({
|
|
3744
|
-
type: "thinking",
|
|
3745
|
-
thinking: event.thinking,
|
|
3746
|
-
signature: event.signature,
|
|
3747
|
-
startedAt: thinkingStartedAt,
|
|
3748
|
-
completedAt: event.ts
|
|
3749
|
-
});
|
|
3750
|
-
lastThinkingRelatedStartedAt = thinkingStartedAt;
|
|
3751
|
-
thinkingStartedAt = 0;
|
|
3752
|
-
break;
|
|
3753
|
-
case "redacted_thinking_complete": {
|
|
3754
|
-
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
3755
|
-
contentBlocks.push({
|
|
3756
|
-
type: "redacted_thinking",
|
|
3757
|
-
data: event.data,
|
|
3758
|
-
startedAt,
|
|
3759
|
-
completedAt: event.ts
|
|
3760
|
-
});
|
|
3761
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
3762
|
-
break;
|
|
3763
4049
|
}
|
|
3764
|
-
case "tool_use":
|
|
3765
|
-
contentBlocks.push({
|
|
3766
|
-
type: "tool",
|
|
3767
|
-
id: event.id,
|
|
3768
|
-
name: event.name,
|
|
3769
|
-
input: event.input,
|
|
3770
|
-
startedAt: Date.now()
|
|
3771
|
-
});
|
|
3772
|
-
emit({
|
|
3773
|
-
type: "tool_start",
|
|
3774
|
-
id: event.id,
|
|
3775
|
-
name: event.name,
|
|
3776
|
-
input: event.input
|
|
3777
|
-
});
|
|
3778
|
-
break;
|
|
3779
|
-
case "done":
|
|
3780
|
-
stopReason = event.stopReason;
|
|
3781
|
-
lastUsage = {
|
|
3782
|
-
inputTokens: event.usage.inputTokens,
|
|
3783
|
-
outputTokens: event.usage.outputTokens,
|
|
3784
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3785
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3786
|
-
llmCalls: 1
|
|
3787
|
-
};
|
|
3788
|
-
lastProviderMetadata = event.providerMetadata;
|
|
3789
|
-
lastModelId = event.modelId;
|
|
3790
|
-
recordUsage({
|
|
3791
|
-
ts: Date.now(),
|
|
3792
|
-
requestId,
|
|
3793
|
-
agentName: subAgentId || "sub-agent",
|
|
3794
|
-
parentToolId,
|
|
3795
|
-
modelId: event.modelId,
|
|
3796
|
-
inputTokens: event.usage.inputTokens,
|
|
3797
|
-
outputTokens: event.usage.outputTokens,
|
|
3798
|
-
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
3799
|
-
cacheReadTokens: event.usage.cacheReadTokens,
|
|
3800
|
-
cost: nanoToDollars(event.cost),
|
|
3801
|
-
billingEvents: event.billingEvents,
|
|
3802
|
-
durationMs: Date.now() - iterStart,
|
|
3803
|
-
toolNames: contentBlocks.filter(
|
|
3804
|
-
(b) => b.type === "tool"
|
|
3805
|
-
).map((b) => b.name)
|
|
3806
|
-
});
|
|
3807
|
-
break;
|
|
3808
|
-
case "error":
|
|
3809
|
-
return {
|
|
3810
|
-
text: `Error: ${event.error}`,
|
|
3811
|
-
messages: thisInvocation()
|
|
3812
|
-
};
|
|
3813
4050
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
4051
|
+
messages.push({
|
|
4052
|
+
role: "user",
|
|
4053
|
+
content: r.result,
|
|
4054
|
+
toolCallId: r.id,
|
|
4055
|
+
isToolError: r.isError
|
|
4056
|
+
});
|
|
3818
4057
|
}
|
|
3819
4058
|
}
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4059
|
+
} finally {
|
|
4060
|
+
statusWatcher.stop();
|
|
4061
|
+
}
|
|
4062
|
+
};
|
|
4063
|
+
const wrapRun = async () => {
|
|
4064
|
+
try {
|
|
4065
|
+
const result = await run();
|
|
4066
|
+
log7.info("Sub-agent complete", {
|
|
4067
|
+
requestId,
|
|
4068
|
+
parentToolId,
|
|
4069
|
+
agentName,
|
|
4070
|
+
durationMs: Date.now() - runStart,
|
|
4071
|
+
turns
|
|
3830
4072
|
});
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3835
|
-
statusWatcher.stop();
|
|
3836
|
-
const text = getPartialText(contentBlocks);
|
|
3837
|
-
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
3838
|
-
return {
|
|
3839
|
-
text,
|
|
3840
|
-
messages: thisInvocation(),
|
|
3841
|
-
...hasArtifacts ? { artifacts } : {}
|
|
3842
|
-
};
|
|
3843
|
-
}
|
|
3844
|
-
log7.info("Tools executing", {
|
|
4073
|
+
return result;
|
|
4074
|
+
} catch (err) {
|
|
4075
|
+
log7.warn("Sub-agent error", {
|
|
3845
4076
|
requestId,
|
|
3846
4077
|
parentToolId,
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
});
|
|
3850
|
-
currentToolNames = toolCalls.map((tc) => tc.name).join(", ");
|
|
3851
|
-
const results = await Promise.all(
|
|
3852
|
-
toolCalls.map(async (tc) => {
|
|
3853
|
-
if (signal?.aborted) {
|
|
3854
|
-
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
3855
|
-
}
|
|
3856
|
-
let settle;
|
|
3857
|
-
const resultPromise = new Promise((res) => {
|
|
3858
|
-
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
3859
|
-
});
|
|
3860
|
-
let toolAbort = new AbortController();
|
|
3861
|
-
const cascadeAbort = () => toolAbort.abort();
|
|
3862
|
-
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
3863
|
-
let settled = false;
|
|
3864
|
-
const safeSettle = (result, isError) => {
|
|
3865
|
-
if (settled) {
|
|
3866
|
-
return;
|
|
3867
|
-
}
|
|
3868
|
-
settled = true;
|
|
3869
|
-
signal?.removeEventListener("abort", cascadeAbort);
|
|
3870
|
-
settle(result, isError);
|
|
3871
|
-
};
|
|
3872
|
-
const run2 = async (input) => {
|
|
3873
|
-
try {
|
|
3874
|
-
let result;
|
|
3875
|
-
if (externalTools.has(tc.name) && resolveExternalTool) {
|
|
3876
|
-
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
3877
|
-
} else {
|
|
3878
|
-
const onLog = (line) => emit({
|
|
3879
|
-
type: "tool_input_delta",
|
|
3880
|
-
id: tc.id,
|
|
3881
|
-
name: tc.name,
|
|
3882
|
-
result: line
|
|
3883
|
-
});
|
|
3884
|
-
result = await executeTool2(
|
|
3885
|
-
tc.name,
|
|
3886
|
-
input,
|
|
3887
|
-
tc.id,
|
|
3888
|
-
onLog,
|
|
3889
|
-
subAgentMessages
|
|
3890
|
-
);
|
|
3891
|
-
}
|
|
3892
|
-
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
3893
|
-
} catch (err) {
|
|
3894
|
-
safeSettle(`Error: ${err.message}`, true);
|
|
3895
|
-
}
|
|
3896
|
-
};
|
|
3897
|
-
const entry = {
|
|
3898
|
-
id: tc.id,
|
|
3899
|
-
name: tc.name,
|
|
3900
|
-
input: tc.input,
|
|
3901
|
-
parentToolId,
|
|
3902
|
-
abortController: toolAbort,
|
|
3903
|
-
startedAt: Date.now(),
|
|
3904
|
-
settle: safeSettle,
|
|
3905
|
-
rerun: (newInput) => {
|
|
3906
|
-
settled = false;
|
|
3907
|
-
toolAbort = new AbortController();
|
|
3908
|
-
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
3909
|
-
once: true
|
|
3910
|
-
});
|
|
3911
|
-
entry.abortController = toolAbort;
|
|
3912
|
-
entry.input = newInput;
|
|
3913
|
-
run2(newInput);
|
|
3914
|
-
}
|
|
3915
|
-
};
|
|
3916
|
-
toolRegistry?.register(entry);
|
|
3917
|
-
const toolStart = Date.now();
|
|
3918
|
-
run2(tc.input);
|
|
3919
|
-
const r = await resultPromise;
|
|
3920
|
-
toolRegistry?.unregister(tc.id);
|
|
3921
|
-
log7.info("Tool completed", {
|
|
3922
|
-
requestId,
|
|
3923
|
-
parentToolId,
|
|
3924
|
-
toolCallId: tc.id,
|
|
3925
|
-
name: tc.name,
|
|
3926
|
-
durationMs: Date.now() - toolStart,
|
|
3927
|
-
isError: r.isError
|
|
3928
|
-
});
|
|
3929
|
-
emit({
|
|
3930
|
-
type: "tool_done",
|
|
3931
|
-
id: tc.id,
|
|
3932
|
-
name: tc.name,
|
|
3933
|
-
result: r.result,
|
|
3934
|
-
isError: r.isError
|
|
3935
|
-
});
|
|
3936
|
-
return r;
|
|
3937
|
-
})
|
|
3938
|
-
);
|
|
3939
|
-
statusWatcher.stop();
|
|
3940
|
-
lastToolResult = results.at(-1)?.result ?? "";
|
|
3941
|
-
for (const r of results) {
|
|
3942
|
-
const block = contentBlocks.find(
|
|
3943
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
3944
|
-
);
|
|
3945
|
-
if (block?.type === "tool") {
|
|
3946
|
-
block.result = r.result;
|
|
3947
|
-
block.isError = r.isError;
|
|
3948
|
-
block.completedAt = Date.now();
|
|
3949
|
-
const innerMsgs = subAgentMessages.get(r.id);
|
|
3950
|
-
if (innerMsgs) {
|
|
3951
|
-
block.subAgentMessages = innerMsgs;
|
|
3952
|
-
}
|
|
3953
|
-
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
3954
|
-
try {
|
|
3955
|
-
artifacts[block.name] = JSON.parse(r.result);
|
|
3956
|
-
} catch {
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
messages.push({
|
|
3961
|
-
role: "user",
|
|
3962
|
-
content: r.result,
|
|
3963
|
-
toolCallId: r.id,
|
|
3964
|
-
isToolError: r.isError
|
|
3965
|
-
});
|
|
3966
|
-
}
|
|
3967
|
-
}
|
|
3968
|
-
};
|
|
3969
|
-
const wrapRun = async () => {
|
|
3970
|
-
try {
|
|
3971
|
-
const result = await run();
|
|
3972
|
-
log7.info("Sub-agent complete", {
|
|
3973
|
-
requestId,
|
|
3974
|
-
parentToolId,
|
|
3975
|
-
agentName,
|
|
3976
|
-
durationMs: Date.now() - runStart,
|
|
3977
|
-
turns
|
|
3978
|
-
});
|
|
3979
|
-
return result;
|
|
3980
|
-
} catch (err) {
|
|
3981
|
-
log7.warn("Sub-agent error", {
|
|
3982
|
-
requestId,
|
|
3983
|
-
parentToolId,
|
|
3984
|
-
agentName,
|
|
3985
|
-
error: err.message
|
|
4078
|
+
agentName,
|
|
4079
|
+
error: err.message
|
|
3986
4080
|
});
|
|
3987
4081
|
throw err;
|
|
3988
4082
|
}
|
|
@@ -4099,7 +4193,7 @@ var BROWSER_TOOLS = [
|
|
|
4099
4193
|
"screenshotViewport",
|
|
4100
4194
|
"setViewport"
|
|
4101
4195
|
],
|
|
4102
|
-
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
|
|
4196
|
+
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.'
|
|
4103
4197
|
},
|
|
4104
4198
|
ref: {
|
|
4105
4199
|
type: "string",
|
|
@@ -4141,6 +4235,10 @@ var BROWSER_TOOLS = [
|
|
|
4141
4235
|
type: "string",
|
|
4142
4236
|
description: 'For navigate: the URL to navigate to (e.g., "/quiz", "/settings").'
|
|
4143
4237
|
},
|
|
4238
|
+
fresh: {
|
|
4239
|
+
type: "boolean",
|
|
4240
|
+
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."
|
|
4241
|
+
},
|
|
4144
4242
|
properties: {
|
|
4145
4243
|
type: "array",
|
|
4146
4244
|
items: { type: "string" },
|
|
@@ -5925,6 +6023,122 @@ var codeSanityCheckTool = {
|
|
|
5925
6023
|
}
|
|
5926
6024
|
};
|
|
5927
6025
|
|
|
6026
|
+
// src/tools/spec/writeBuildOverview.ts
|
|
6027
|
+
import fs20 from "fs";
|
|
6028
|
+
var OVERVIEW_FILE = "src/overview.html";
|
|
6029
|
+
var 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).
|
|
6030
|
+
|
|
6031
|
+
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.
|
|
6032
|
+
|
|
6033
|
+
### The single hard rule
|
|
6034
|
+
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.
|
|
6035
|
+
|
|
6036
|
+
### What it is (and is not)
|
|
6037
|
+
- 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."
|
|
6038
|
+
- 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.
|
|
6039
|
+
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
6040
|
+
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
6041
|
+
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
6042
|
+
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
6043
|
+
|
|
6044
|
+
### Constraints
|
|
6045
|
+
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
6046
|
+
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
6047
|
+
var OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
6048
|
+
<html lang="en">
|
|
6049
|
+
<head>
|
|
6050
|
+
<meta charset="utf-8">
|
|
6051
|
+
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
6052
|
+
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
6053
|
+
The user-scalable=no viewport is intentional; leave it. -->
|
|
6054
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
6055
|
+
<title>Build Overview</title>
|
|
6056
|
+
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
6057
|
+
<style>
|
|
6058
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
6059
|
+
</style>
|
|
6060
|
+
</head>
|
|
6061
|
+
<body>
|
|
6062
|
+
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
6063
|
+
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
6064
|
+
app's spec. -->
|
|
6065
|
+
</body>
|
|
6066
|
+
</html>`;
|
|
6067
|
+
function initialDelivery() {
|
|
6068
|
+
return `### Your deliverable
|
|
6069
|
+
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.
|
|
6070
|
+
|
|
6071
|
+
<overview_shell>
|
|
6072
|
+
${OVERVIEW_SHELL}
|
|
6073
|
+
</overview_shell>
|
|
6074
|
+
|
|
6075
|
+
Then reply with a one-line summary of what you wrote.`;
|
|
6076
|
+
}
|
|
6077
|
+
function refreshDelivery() {
|
|
6078
|
+
return `### Your deliverable
|
|
6079
|
+
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.
|
|
6080
|
+
|
|
6081
|
+
Then reply with a one-line summary of what you changed.`;
|
|
6082
|
+
}
|
|
6083
|
+
async function renderBuildOverview(content, context, opts) {
|
|
6084
|
+
if (!content) {
|
|
6085
|
+
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
6086
|
+
}
|
|
6087
|
+
const exists = fs20.existsSync(OVERVIEW_FILE);
|
|
6088
|
+
const task = `<overview_copy>${content}</overview_copy>
|
|
6089
|
+
|
|
6090
|
+
${DESIGN_BRIEF}
|
|
6091
|
+
|
|
6092
|
+
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
6093
|
+
try {
|
|
6094
|
+
if (opts.background) {
|
|
6095
|
+
const result2 = await runDesignExpertRender(
|
|
6096
|
+
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
6097
|
+
context
|
|
6098
|
+
);
|
|
6099
|
+
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
6100
|
+
return result2.text;
|
|
6101
|
+
}
|
|
6102
|
+
const result = await runDesignExpertRender({ task }, context);
|
|
6103
|
+
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6104
|
+
if (!fs20.existsSync(OVERVIEW_FILE)) {
|
|
6105
|
+
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
6106
|
+
${result.text}`;
|
|
6107
|
+
}
|
|
6108
|
+
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
6109
|
+
} catch (err) {
|
|
6110
|
+
return `Error generating build overview: ${err.message}`;
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
var buildOverviewTool = {
|
|
6114
|
+
definition: {
|
|
6115
|
+
clearable: false,
|
|
6116
|
+
name: "writeBuildOverview",
|
|
6117
|
+
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.",
|
|
6118
|
+
inputSchema: {
|
|
6119
|
+
type: "object",
|
|
6120
|
+
properties: {
|
|
6121
|
+
content: {
|
|
6122
|
+
type: "string",
|
|
6123
|
+
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."
|
|
6124
|
+
}
|
|
6125
|
+
},
|
|
6126
|
+
required: ["content"]
|
|
6127
|
+
}
|
|
6128
|
+
},
|
|
6129
|
+
async execute(input, context) {
|
|
6130
|
+
if (!context) {
|
|
6131
|
+
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
6132
|
+
}
|
|
6133
|
+
const content = (input.content ?? "").trim();
|
|
6134
|
+
const background = Boolean(content) && fs20.existsSync(OVERVIEW_FILE);
|
|
6135
|
+
if (background) {
|
|
6136
|
+
input.background = true;
|
|
6137
|
+
}
|
|
6138
|
+
return renderBuildOverview(content, context, { background });
|
|
6139
|
+
}
|
|
6140
|
+
};
|
|
6141
|
+
|
|
5928
6142
|
// src/subagents/specSync/tools.ts
|
|
5929
6143
|
var SPEC_SYNC_TOOLS = [
|
|
5930
6144
|
...COMMON_READ_TOOLS,
|
|
@@ -5955,16 +6169,23 @@ ${readAsset(
|
|
|
5955
6169
|
</mindstudio_flavored_markdown_spec_docs>`;
|
|
5956
6170
|
var specSyncTool = {
|
|
5957
6171
|
backgroundOnly: true,
|
|
6172
|
+
// Fire-and-forget: completion never wakes the agent. The outcome rides the
|
|
6173
|
+
// next real turn as a hidden background_results note instead.
|
|
6174
|
+
backgroundNotify: "passive",
|
|
5958
6175
|
definition: {
|
|
5959
6176
|
clearable: false,
|
|
5960
6177
|
name: "specSync",
|
|
5961
|
-
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
|
|
6178
|
+
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.",
|
|
5962
6179
|
inputSchema: {
|
|
5963
6180
|
type: "object",
|
|
5964
6181
|
properties: {
|
|
5965
6182
|
task: {
|
|
5966
6183
|
type: "string",
|
|
5967
6184
|
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."
|
|
6185
|
+
},
|
|
6186
|
+
refreshBuildOverview: {
|
|
6187
|
+
type: "boolean",
|
|
6188
|
+
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."
|
|
5968
6189
|
}
|
|
5969
6190
|
},
|
|
5970
6191
|
required: ["task"]
|
|
@@ -5988,12 +6209,27 @@ var specSyncTool = {
|
|
|
5988
6209
|
parts.push(specIndex);
|
|
5989
6210
|
}
|
|
5990
6211
|
const system = parts.join("\n\n");
|
|
6212
|
+
const refreshOverview = input.refreshBuildOverview === true;
|
|
6213
|
+
const tools2 = refreshOverview ? [...SPEC_SYNC_TOOLS, buildOverviewTool.definition] : SPEC_SYNC_TOOLS;
|
|
6214
|
+
const task = refreshOverview ? `${input.task}
|
|
6215
|
+
|
|
6216
|
+
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;
|
|
5991
6217
|
const result = await runSubAgent({
|
|
5992
6218
|
system,
|
|
5993
|
-
task
|
|
5994
|
-
tools:
|
|
6219
|
+
task,
|
|
6220
|
+
tools: tools2,
|
|
5995
6221
|
externalTools: /* @__PURE__ */ new Set(),
|
|
5996
|
-
executeTool: (name, toolInput
|
|
6222
|
+
executeTool: (name, toolInput, toolCallId, _onLog, sams) => {
|
|
6223
|
+
if (name === "writeBuildOverview") {
|
|
6224
|
+
const childCtx = toolCallId ? { ...deriveContext(context, toolCallId), subAgentMessages: sams } : { ...context, subAgentMessages: sams };
|
|
6225
|
+
return renderBuildOverview(
|
|
6226
|
+
String(toolInput.content ?? "").trim(),
|
|
6227
|
+
childCtx,
|
|
6228
|
+
{ background: false }
|
|
6229
|
+
);
|
|
6230
|
+
}
|
|
6231
|
+
return executeTool(name, toolInput, context);
|
|
6232
|
+
},
|
|
5997
6233
|
apiConfig: context.apiConfig,
|
|
5998
6234
|
model: resolveModel("specSync", context.models, context.model),
|
|
5999
6235
|
subAgentId: "specSync",
|
|
@@ -6066,119 +6302,9 @@ var scrapeWebUrlTool = {
|
|
|
6066
6302
|
}
|
|
6067
6303
|
};
|
|
6068
6304
|
|
|
6069
|
-
// src/tools/
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
var 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).
|
|
6073
|
-
|
|
6074
|
-
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.
|
|
6075
|
-
|
|
6076
|
-
### The single hard rule
|
|
6077
|
-
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.
|
|
6078
|
-
|
|
6079
|
-
### What it is (and is not)
|
|
6080
|
-
- 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."
|
|
6081
|
-
- 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.
|
|
6082
|
-
- NOT a slide deck \u2014 no theatrics, no building to a climax, no persuasion.
|
|
6083
|
-
- NOT a docs site \u2014 no raised or shadowed cards, no hover-lift, no clickable-feeling surfaces, no sticky table of contents.
|
|
6084
|
-
- NOT a sparse memo \u2014 a restrained single-column page under-sells the work.
|
|
6085
|
-
- No header chrome and no footer: open straight on the app's logo and name so it sits flush with the IDE.
|
|
6086
|
-
|
|
6087
|
-
### Constraints
|
|
6088
|
-
- A single self-contained HTML file. Fonts may load from a CDN; everything else (CSS, the logo SVG) is inline.
|
|
6089
|
-
- Responsive: fills the embedded panel width and collapses gracefully at narrow widths.`;
|
|
6090
|
-
var OVERVIEW_SHELL = `<!DOCTYPE html>
|
|
6091
|
-
<html lang="en">
|
|
6092
|
-
<head>
|
|
6093
|
-
<meta charset="utf-8">
|
|
6094
|
-
<!-- This renders inside an iframe in the IDE. Keep it a simple, self-contained
|
|
6095
|
-
static document \u2014 no scroll/zoom/accessibility scaffolding beyond this head.
|
|
6096
|
-
The user-scalable=no viewport is intentional; leave it. -->
|
|
6097
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
|
6098
|
-
<title>Build Overview</title>
|
|
6099
|
-
<!-- SKIN: link the app's fonts here (CDN \u2014 e.g. Fontshare / Google Fonts). -->
|
|
6100
|
-
<style>
|
|
6101
|
-
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
6102
|
-
</style>
|
|
6103
|
-
</head>
|
|
6104
|
-
<body>
|
|
6105
|
-
<!-- Compose the Build Overview here. Layout, sections, type, and styling are
|
|
6106
|
-
yours \u2014 keep everything inline and self-contained, brand-skinned from the
|
|
6107
|
-
app's spec. -->
|
|
6108
|
-
</body>
|
|
6109
|
-
</html>`;
|
|
6110
|
-
function initialDelivery() {
|
|
6111
|
-
return `### Your deliverable
|
|
6112
|
-
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.
|
|
6113
|
-
|
|
6114
|
-
<overview_shell>
|
|
6115
|
-
${OVERVIEW_SHELL}
|
|
6116
|
-
</overview_shell>
|
|
6117
|
-
|
|
6118
|
-
Then reply with a one-line summary of what you wrote.`;
|
|
6119
|
-
}
|
|
6120
|
-
function refreshDelivery() {
|
|
6121
|
-
return `### Your deliverable
|
|
6122
|
-
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.
|
|
6123
|
-
|
|
6124
|
-
Then reply with a one-line summary of what you changed.`;
|
|
6125
|
-
}
|
|
6126
|
-
var buildOverviewTool = {
|
|
6127
|
-
definition: {
|
|
6128
|
-
clearable: false,
|
|
6129
|
-
name: "writeBuildOverview",
|
|
6130
|
-
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.",
|
|
6131
|
-
inputSchema: {
|
|
6132
|
-
type: "object",
|
|
6133
|
-
properties: {
|
|
6134
|
-
content: {
|
|
6135
|
-
type: "string",
|
|
6136
|
-
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."
|
|
6137
|
-
}
|
|
6138
|
-
},
|
|
6139
|
-
required: ["content"]
|
|
6140
|
-
}
|
|
6141
|
-
},
|
|
6142
|
-
async execute(input, context) {
|
|
6143
|
-
if (!context) {
|
|
6144
|
-
return "Error: writeBuildOverview requires execution context for design expert delegation";
|
|
6145
|
-
}
|
|
6146
|
-
const content = (input.content ?? "").trim();
|
|
6147
|
-
if (!content) {
|
|
6148
|
-
return "Error: writeBuildOverview requires non-empty `content` (the overview copy).";
|
|
6149
|
-
}
|
|
6150
|
-
const exists = fs20.existsSync(OVERVIEW_FILE);
|
|
6151
|
-
const task = `<overview_copy>${content}</overview_copy>
|
|
6152
|
-
|
|
6153
|
-
${DESIGN_BRIEF}
|
|
6154
|
-
|
|
6155
|
-
${exists ? refreshDelivery() : initialDelivery()}`;
|
|
6156
|
-
try {
|
|
6157
|
-
if (exists) {
|
|
6158
|
-
input.background = true;
|
|
6159
|
-
const result2 = await runDesignExpertRender(
|
|
6160
|
-
{ task, background: true, reportingName: "writeBuildOverview" },
|
|
6161
|
-
context
|
|
6162
|
-
);
|
|
6163
|
-
context.subAgentMessages?.set(context.toolCallId, result2.messages);
|
|
6164
|
-
return result2.text;
|
|
6165
|
-
}
|
|
6166
|
-
const result = await runDesignExpertRender({ task }, context);
|
|
6167
|
-
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
6168
|
-
if (!fs20.existsSync(OVERVIEW_FILE)) {
|
|
6169
|
-
return `Error: the design expert did not write ${OVERVIEW_FILE}. Its reply was:
|
|
6170
|
-
${result.text}`;
|
|
6171
|
-
}
|
|
6172
|
-
return `Build overview written to ${OVERVIEW_FILE}. ${result.text}`;
|
|
6173
|
-
} catch (err) {
|
|
6174
|
-
return `Error generating build overview: ${err.message}`;
|
|
6175
|
-
}
|
|
6176
|
-
}
|
|
6177
|
-
};
|
|
6178
|
-
|
|
6179
|
-
// src/tools/index.ts
|
|
6180
|
-
function deriveContext(parent, toolCallId) {
|
|
6181
|
-
return { ...parent, toolCallId };
|
|
6305
|
+
// src/tools/index.ts
|
|
6306
|
+
function deriveContext(parent, toolCallId) {
|
|
6307
|
+
return { ...parent, toolCallId };
|
|
6182
6308
|
}
|
|
6183
6309
|
var ALL_TOOLS = [
|
|
6184
6310
|
// Common
|
|
@@ -6374,7 +6500,11 @@ function getConversationMessagesForSummary(messages, endIndex) {
|
|
|
6374
6500
|
break;
|
|
6375
6501
|
}
|
|
6376
6502
|
}
|
|
6377
|
-
return messages.slice(startIdx, endIndex)
|
|
6503
|
+
return messages.slice(startIdx, endIndex).filter(
|
|
6504
|
+
(msg) => !(Array.isArray(msg.content) && msg.content.some(
|
|
6505
|
+
(b) => b.type === "tool" && b.uiOnly
|
|
6506
|
+
))
|
|
6507
|
+
);
|
|
6378
6508
|
}
|
|
6379
6509
|
function getSubAgentMessagesForSummary(messages, subAgentName, endIndex) {
|
|
6380
6510
|
let checkpointIdx = -1;
|
|
@@ -6652,6 +6782,11 @@ import path12 from "path";
|
|
|
6652
6782
|
var log10 = createLogger("session");
|
|
6653
6783
|
var SESSION_FILE = ".remy-session.json";
|
|
6654
6784
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
6785
|
+
function writeFileAtomicSync(file, data) {
|
|
6786
|
+
const tmp = `${file}.tmp`;
|
|
6787
|
+
fs21.writeFileSync(tmp, data, "utf-8");
|
|
6788
|
+
fs21.renameSync(tmp, file);
|
|
6789
|
+
}
|
|
6655
6790
|
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
6656
6791
|
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
6657
6792
|
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
@@ -6751,7 +6886,7 @@ function archiveMessages(messages, label, models) {
|
|
|
6751
6886
|
if (models && Object.keys(models).length > 0) {
|
|
6752
6887
|
payload.models = models;
|
|
6753
6888
|
}
|
|
6754
|
-
|
|
6889
|
+
writeFileAtomicSync(dest, JSON.stringify(payload));
|
|
6755
6890
|
archiveCountCache.set(path12.basename(dest), count);
|
|
6756
6891
|
log10.info("Session archived", { label, dest, messageCount: count });
|
|
6757
6892
|
pruneArchives();
|
|
@@ -6956,7 +7091,7 @@ function saveSession(state) {
|
|
|
6956
7091
|
if (Buffer.byteLength(serialized, "utf-8") > ROTATE_THRESHOLD_BYTES && rotate(state)) {
|
|
6957
7092
|
serialized = JSON.stringify(buildPayload(state));
|
|
6958
7093
|
}
|
|
6959
|
-
|
|
7094
|
+
writeFileAtomicSync(SESSION_FILE, serialized);
|
|
6960
7095
|
log10.info("Session saved", { messageCount: state.messages.length });
|
|
6961
7096
|
} catch (err) {
|
|
6962
7097
|
log10.warn("Session save failed", { error: err.message });
|
|
@@ -6986,6 +7121,28 @@ function clearSession(state) {
|
|
|
6986
7121
|
var log11 = createLogger("compaction:trigger");
|
|
6987
7122
|
var pending = null;
|
|
6988
7123
|
var inflightCompaction = null;
|
|
7124
|
+
function getInflightCompaction() {
|
|
7125
|
+
return inflightCompaction;
|
|
7126
|
+
}
|
|
7127
|
+
function summariesOf(result) {
|
|
7128
|
+
const out = [];
|
|
7129
|
+
for (const msg of result.checkpoints) {
|
|
7130
|
+
if (!Array.isArray(msg.content)) {
|
|
7131
|
+
continue;
|
|
7132
|
+
}
|
|
7133
|
+
for (const block of msg.content) {
|
|
7134
|
+
if (block.type === "summary") {
|
|
7135
|
+
out.push({
|
|
7136
|
+
name: block.name,
|
|
7137
|
+
text: block.text,
|
|
7138
|
+
...block.recent && { recent: block.recent },
|
|
7139
|
+
startedAt: block.startedAt
|
|
7140
|
+
});
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
return out;
|
|
7145
|
+
}
|
|
6989
7146
|
function applyPendingSummaries(state) {
|
|
6990
7147
|
const drained = pending;
|
|
6991
7148
|
pending = null;
|
|
@@ -7006,6 +7163,25 @@ function applyPendingSummaries(state) {
|
|
|
7006
7163
|
});
|
|
7007
7164
|
saveSession(state);
|
|
7008
7165
|
}
|
|
7166
|
+
var SUMMARY_SECTION_LABELS = {
|
|
7167
|
+
conversation: "Conversation",
|
|
7168
|
+
visualDesignExpert: "Design Agent Thread",
|
|
7169
|
+
productVision: "Roadmap Agent Thread"
|
|
7170
|
+
};
|
|
7171
|
+
function formatSummariesResult(summaries) {
|
|
7172
|
+
if (summaries === null) {
|
|
7173
|
+
return "A compaction checkpoint was already pending \u2014 no new compaction was needed.";
|
|
7174
|
+
}
|
|
7175
|
+
if (summaries.length === 0) {
|
|
7176
|
+
return "Nothing to compact \u2014 the conversation is already fully summarized.";
|
|
7177
|
+
}
|
|
7178
|
+
if (summaries.length === 1 && summaries[0].name === "conversation") {
|
|
7179
|
+
return summaries[0].text;
|
|
7180
|
+
}
|
|
7181
|
+
return summaries.map((s) => `## ${SUMMARY_SECTION_LABELS[s.name] ?? s.name}
|
|
7182
|
+
|
|
7183
|
+
${s.text}`).join("\n\n");
|
|
7184
|
+
}
|
|
7009
7185
|
var listener = null;
|
|
7010
7186
|
function setCompactionListener(l) {
|
|
7011
7187
|
listener = l;
|
|
@@ -7016,18 +7192,24 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
7016
7192
|
}
|
|
7017
7193
|
if (pending) {
|
|
7018
7194
|
log11.info("Compaction skipped \u2014 a checkpoint is already waiting to apply");
|
|
7019
|
-
return Promise.resolve();
|
|
7195
|
+
return Promise.resolve(null);
|
|
7020
7196
|
}
|
|
7021
|
-
const { blocking = false, requestId, model } = opts;
|
|
7022
|
-
listener?.({ type: "started", blocking, requestId });
|
|
7197
|
+
const { blocking = false, requestId, model, origin, toolCallId } = opts;
|
|
7198
|
+
listener?.({ type: "started", blocking, requestId, origin, toolCallId });
|
|
7023
7199
|
inflightCompaction = compactConversation(
|
|
7024
7200
|
state.messages,
|
|
7025
7201
|
apiConfig,
|
|
7026
7202
|
resolveModel("conversationSummarizer", state.models, model)
|
|
7027
7203
|
).then((result) => {
|
|
7028
7204
|
pending = result;
|
|
7029
|
-
|
|
7205
|
+
const summaries = summariesOf(result);
|
|
7206
|
+
listener?.({
|
|
7207
|
+
type: "complete",
|
|
7208
|
+
requestId,
|
|
7209
|
+
...summaries.length > 0 && { summaries }
|
|
7210
|
+
});
|
|
7030
7211
|
log11.info("Compaction complete");
|
|
7212
|
+
return summaries;
|
|
7031
7213
|
}).catch((err) => {
|
|
7032
7214
|
const message = err.message || "Compaction failed";
|
|
7033
7215
|
listener?.({ type: "complete", error: message, requestId });
|
|
@@ -7547,6 +7729,62 @@ function parsePartialJson(jsonString) {
|
|
|
7547
7729
|
return parseAny();
|
|
7548
7730
|
}
|
|
7549
7731
|
|
|
7732
|
+
// src/automatedActions/resolve.ts
|
|
7733
|
+
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
7734
|
+
function resolveAction(text) {
|
|
7735
|
+
const parsed = parseSentinel(text);
|
|
7736
|
+
if (!parsed) {
|
|
7737
|
+
return null;
|
|
7738
|
+
}
|
|
7739
|
+
const { name: triggerName, remainder } = parsed;
|
|
7740
|
+
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
7741
|
+
return null;
|
|
7742
|
+
}
|
|
7743
|
+
let params = {};
|
|
7744
|
+
if (remainder) {
|
|
7745
|
+
try {
|
|
7746
|
+
params = JSON.parse(remainder.split("\n")[0]);
|
|
7747
|
+
} catch {
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
7751
|
+
let next;
|
|
7752
|
+
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7753
|
+
if (fmMatch) {
|
|
7754
|
+
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
7755
|
+
if (nextMatch) {
|
|
7756
|
+
next = nextMatch[1];
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
7760
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7761
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
7762
|
+
body = body.replaceAll(`{{${key}}}`, str);
|
|
7763
|
+
}
|
|
7764
|
+
return {
|
|
7765
|
+
message: automatedMessage(triggerName, body),
|
|
7766
|
+
next
|
|
7767
|
+
};
|
|
7768
|
+
}
|
|
7769
|
+
function getActionChain(startName) {
|
|
7770
|
+
const chain = [];
|
|
7771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7772
|
+
let name = startName;
|
|
7773
|
+
while (name && !seen.has(name)) {
|
|
7774
|
+
seen.add(name);
|
|
7775
|
+
chain.push(name);
|
|
7776
|
+
let body;
|
|
7777
|
+
try {
|
|
7778
|
+
body = readAsset("automatedActions", `${name}.md`);
|
|
7779
|
+
} catch {
|
|
7780
|
+
break;
|
|
7781
|
+
}
|
|
7782
|
+
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7783
|
+
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
7784
|
+
}
|
|
7785
|
+
return chain;
|
|
7786
|
+
}
|
|
7787
|
+
|
|
7550
7788
|
// src/errors.ts
|
|
7551
7789
|
var patterns = [
|
|
7552
7790
|
[
|
|
@@ -7627,6 +7865,7 @@ async function runTurn(params) {
|
|
|
7627
7865
|
onboardingState,
|
|
7628
7866
|
signal,
|
|
7629
7867
|
onEvent,
|
|
7868
|
+
takeSteering,
|
|
7630
7869
|
resolveExternalTool,
|
|
7631
7870
|
requestId,
|
|
7632
7871
|
toolRegistry,
|
|
@@ -7661,7 +7900,7 @@ async function runTurn(params) {
|
|
|
7661
7900
|
onEvent({ type: "error", error: "Empty message" });
|
|
7662
7901
|
return;
|
|
7663
7902
|
}
|
|
7664
|
-
|
|
7903
|
+
const appendEntry = (entry) => {
|
|
7665
7904
|
const hasAttachments = (entry.attachments?.length ?? 0) > 0;
|
|
7666
7905
|
const userMsg = { role: "user", content: entry.text };
|
|
7667
7906
|
if (entry.hidden) {
|
|
@@ -7684,6 +7923,9 @@ async function runTurn(params) {
|
|
|
7684
7923
|
...entry.requestId && { requestId: entry.requestId },
|
|
7685
7924
|
...entry.queued && { queued: true }
|
|
7686
7925
|
});
|
|
7926
|
+
};
|
|
7927
|
+
for (const entry of keptEntries) {
|
|
7928
|
+
appendEntry(entry);
|
|
7687
7929
|
}
|
|
7688
7930
|
const isFirstMessage = state.messages.filter((m) => m.role === "user").length === 1;
|
|
7689
7931
|
const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -7694,6 +7936,9 @@ async function runTurn(params) {
|
|
|
7694
7936
|
let lastCompletedTools = "";
|
|
7695
7937
|
let lastCompletedInput = "";
|
|
7696
7938
|
let lastCompletedResult = "";
|
|
7939
|
+
let subAgentText = "";
|
|
7940
|
+
let currentToolNames = "";
|
|
7941
|
+
let contentBlocksRef = [];
|
|
7697
7942
|
let turnInputTokens = 0;
|
|
7698
7943
|
let turnOutputTokens = 0;
|
|
7699
7944
|
let turnCacheCreation = 0;
|
|
@@ -7702,283 +7947,330 @@ async function runTurn(params) {
|
|
|
7702
7947
|
let lastCallInputTokens = 0;
|
|
7703
7948
|
let lastCallCacheCreation = 0;
|
|
7704
7949
|
let lastCallCacheRead = 0;
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
} } : startStatusWatcher({
|
|
7736
|
-
apiConfig,
|
|
7737
|
-
getContext: () => {
|
|
7738
|
-
const parts = [];
|
|
7739
|
-
const toolName = currentToolNames || getToolCalls(contentBlocks).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
7740
|
-
if (toolName) {
|
|
7741
|
-
parts.push(`Tool: ${toolName}`);
|
|
7742
|
-
}
|
|
7743
|
-
if (lastCompletedInput) {
|
|
7744
|
-
parts.push(`Tool input: ${lastCompletedInput.slice(-1500)}`);
|
|
7745
|
-
}
|
|
7746
|
-
if (lastCompletedResult) {
|
|
7747
|
-
parts.push(`Tool result: ${lastCompletedResult.slice(-1500)}`);
|
|
7748
|
-
}
|
|
7749
|
-
const text = subAgentText || getTextContent(contentBlocks).slice(-2e3);
|
|
7750
|
-
if (text) {
|
|
7751
|
-
parts.push(`Assistant text: ${text}`);
|
|
7752
|
-
}
|
|
7753
|
-
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
7754
|
-
parts.push(`Build phase: ${onboardingState}`);
|
|
7950
|
+
const statusWatcher = isFirstMessage ? { stop() {
|
|
7951
|
+
}, pause() {
|
|
7952
|
+
}, resume() {
|
|
7953
|
+
} } : startStatusWatcher({
|
|
7954
|
+
apiConfig,
|
|
7955
|
+
getContext: () => {
|
|
7956
|
+
const parts = [];
|
|
7957
|
+
const toolName = currentToolNames || getToolCalls(contentBlocksRef).filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).at(-1)?.name || lastCompletedTools;
|
|
7958
|
+
if (toolName) {
|
|
7959
|
+
parts.push(`Tool: ${toolName}`);
|
|
7960
|
+
}
|
|
7961
|
+
const toolInput = sanitizeStatusText(lastCompletedInput);
|
|
7962
|
+
if (toolInput) {
|
|
7963
|
+
parts.push(`Tool input: ${toolInput.slice(-1500)}`);
|
|
7964
|
+
}
|
|
7965
|
+
const toolResult = sanitizeStatusText(lastCompletedResult);
|
|
7966
|
+
if (toolResult) {
|
|
7967
|
+
parts.push(`Tool result: ${toolResult.slice(-1500)}`);
|
|
7968
|
+
}
|
|
7969
|
+
const text = subAgentText || getTextContent(contentBlocksRef).slice(-2e3);
|
|
7970
|
+
if (text) {
|
|
7971
|
+
parts.push(`Assistant text: ${text}`);
|
|
7972
|
+
}
|
|
7973
|
+
if (onboardingState && onboardingState !== "onboardingFinished") {
|
|
7974
|
+
parts.push(`Build phase: ${onboardingState}`);
|
|
7975
|
+
}
|
|
7976
|
+
let hasUserSignal = false;
|
|
7977
|
+
for (const entry of keptEntries) {
|
|
7978
|
+
if (entry.hidden) {
|
|
7979
|
+
continue;
|
|
7755
7980
|
}
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
if (automated) {
|
|
7981
|
+
const automated = parseSentinel(entry.text);
|
|
7982
|
+
if (automated) {
|
|
7983
|
+
if (!NON_ACTION_SENTINELS.has(automated.name)) {
|
|
7759
7984
|
parts.push(`Automated action: ${automated.name}`);
|
|
7760
|
-
|
|
7761
|
-
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
7985
|
+
hasUserSignal = true;
|
|
7762
7986
|
}
|
|
7987
|
+
} else if (entry.text) {
|
|
7988
|
+
parts.push(`User request: ${entry.text.slice(-500)}`);
|
|
7989
|
+
hasUserSignal = true;
|
|
7763
7990
|
}
|
|
7764
|
-
return parts.join("\n");
|
|
7765
|
-
},
|
|
7766
|
-
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
7767
|
-
signal
|
|
7768
|
-
});
|
|
7769
|
-
async function handlePartialInput(acc, id, name, partial) {
|
|
7770
|
-
const tool = getToolByName(name);
|
|
7771
|
-
if (!tool?.streaming) {
|
|
7772
|
-
return;
|
|
7773
7991
|
}
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
transform,
|
|
7777
|
-
partialInput
|
|
7778
|
-
} = tool.streaming;
|
|
7779
|
-
if (partialInput) {
|
|
7780
|
-
const result = partialInput(partial, acc.lastEmittedCount);
|
|
7781
|
-
if (!result) {
|
|
7782
|
-
return;
|
|
7783
|
-
}
|
|
7784
|
-
acc.lastEmittedCount = result.emittedCount;
|
|
7785
|
-
acc.started = true;
|
|
7786
|
-
onEvent({
|
|
7787
|
-
type: "tool_start",
|
|
7788
|
-
id,
|
|
7789
|
-
name,
|
|
7790
|
-
input: result.input,
|
|
7791
|
-
partial: true
|
|
7792
|
-
});
|
|
7793
|
-
return;
|
|
7992
|
+
if (!hasUserSignal && !toolName && !text) {
|
|
7993
|
+
return "";
|
|
7794
7994
|
}
|
|
7795
|
-
|
|
7796
|
-
|
|
7995
|
+
return parts.join("\n");
|
|
7996
|
+
},
|
|
7997
|
+
onStatus: (label) => onEvent({ type: "status", message: label }),
|
|
7998
|
+
signal
|
|
7999
|
+
});
|
|
8000
|
+
try {
|
|
8001
|
+
while (true) {
|
|
8002
|
+
let getOrCreateAccumulator2 = function(id, name) {
|
|
8003
|
+
let acc = toolInputAccumulators.get(id);
|
|
8004
|
+
if (!acc) {
|
|
8005
|
+
acc = { name, json: "", started: false, lastEmittedCount: 0 };
|
|
8006
|
+
toolInputAccumulators.set(id, acc);
|
|
8007
|
+
}
|
|
8008
|
+
return acc;
|
|
8009
|
+
};
|
|
8010
|
+
var getOrCreateAccumulator = getOrCreateAccumulator2;
|
|
8011
|
+
if (signal?.aborted) {
|
|
8012
|
+
onEvent({ type: "turn_cancelled" });
|
|
8013
|
+
saveSession(state);
|
|
7797
8014
|
return;
|
|
7798
8015
|
}
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
8016
|
+
const iterStart = Date.now();
|
|
8017
|
+
const contentBlocks = [];
|
|
8018
|
+
const thinkingBlockStartTimes = [];
|
|
8019
|
+
let thinkingCompleteCount = 0;
|
|
8020
|
+
let lastThinkingRelatedStartedAt;
|
|
8021
|
+
let textBlockOpen = false;
|
|
8022
|
+
const toolInputAccumulators = /* @__PURE__ */ new Map();
|
|
8023
|
+
let stopReason = "end_turn";
|
|
8024
|
+
let turnProviderMetadata;
|
|
8025
|
+
let turnModelId;
|
|
8026
|
+
contentBlocksRef = contentBlocks;
|
|
8027
|
+
subAgentText = "";
|
|
8028
|
+
currentToolNames = "";
|
|
8029
|
+
async function handlePartialInput(acc, id, name, partial) {
|
|
8030
|
+
const tool = getToolByName(name);
|
|
8031
|
+
if (!tool?.streaming) {
|
|
7806
8032
|
return;
|
|
7807
8033
|
}
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
model: parentModel,
|
|
7818
|
-
requestId,
|
|
7819
|
-
system,
|
|
7820
|
-
messages: cleanMessagesForApi(state.messages),
|
|
7821
|
-
tools: tools2,
|
|
7822
|
-
signal
|
|
7823
|
-
},
|
|
7824
|
-
{
|
|
7825
|
-
onRetry: (attempt) => {
|
|
7826
|
-
onEvent({
|
|
7827
|
-
type: "status",
|
|
7828
|
-
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
7829
|
-
});
|
|
8034
|
+
const {
|
|
8035
|
+
contentField = "content",
|
|
8036
|
+
transform,
|
|
8037
|
+
partialInput
|
|
8038
|
+
} = tool.streaming;
|
|
8039
|
+
if (partialInput) {
|
|
8040
|
+
const result = partialInput(partial, acc.lastEmittedCount);
|
|
8041
|
+
if (!result) {
|
|
8042
|
+
return;
|
|
7830
8043
|
}
|
|
8044
|
+
acc.lastEmittedCount = result.emittedCount;
|
|
8045
|
+
acc.started = true;
|
|
8046
|
+
onEvent({
|
|
8047
|
+
type: "tool_start",
|
|
8048
|
+
id,
|
|
8049
|
+
name,
|
|
8050
|
+
input: result.input,
|
|
8051
|
+
partial: true
|
|
8052
|
+
});
|
|
8053
|
+
return;
|
|
7831
8054
|
}
|
|
7832
|
-
|
|
7833
|
-
if (
|
|
7834
|
-
|
|
8055
|
+
const content = partial[contentField];
|
|
8056
|
+
if (typeof content !== "string") {
|
|
8057
|
+
return;
|
|
7835
8058
|
}
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
text: event.text,
|
|
7845
|
-
startedAt: event.ts
|
|
7846
|
-
});
|
|
7847
|
-
}
|
|
7848
|
-
textBlockOpen = true;
|
|
7849
|
-
onEvent({ type: "text", text: event.text });
|
|
7850
|
-
break;
|
|
8059
|
+
if (!acc.started) {
|
|
8060
|
+
acc.started = true;
|
|
8061
|
+
onEvent({ type: "tool_start", id, name, input: partial });
|
|
8062
|
+
}
|
|
8063
|
+
if (transform) {
|
|
8064
|
+
const result = await transform(partial);
|
|
8065
|
+
if (result === null) {
|
|
8066
|
+
return;
|
|
7851
8067
|
}
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
8068
|
+
onEvent({ type: "tool_input_delta", id, name, result });
|
|
8069
|
+
} else {
|
|
8070
|
+
onEvent({ type: "tool_input_delta", id, name, result: content });
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
try {
|
|
8074
|
+
for await (const event of streamChatWithRetry(
|
|
8075
|
+
{
|
|
8076
|
+
...apiConfig,
|
|
8077
|
+
model: parentModel,
|
|
8078
|
+
requestId,
|
|
8079
|
+
system,
|
|
8080
|
+
messages: cleanMessagesForApi(state.messages),
|
|
8081
|
+
tools: tools2,
|
|
8082
|
+
signal
|
|
8083
|
+
},
|
|
8084
|
+
{
|
|
8085
|
+
onRetry: (attempt) => {
|
|
8086
|
+
onEvent({
|
|
8087
|
+
type: "status",
|
|
8088
|
+
message: `Lost connection, retrying (attempt ${attempt + 2} of 3)`
|
|
8089
|
+
});
|
|
7856
8090
|
}
|
|
7857
|
-
onEvent({ type: "thinking", text: event.text });
|
|
7858
|
-
break;
|
|
7859
|
-
case "thinking_complete": {
|
|
7860
|
-
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
7861
|
-
contentBlocks.push({
|
|
7862
|
-
type: "thinking",
|
|
7863
|
-
thinking: event.thinking,
|
|
7864
|
-
signature: event.signature,
|
|
7865
|
-
startedAt,
|
|
7866
|
-
completedAt: event.ts
|
|
7867
|
-
});
|
|
7868
|
-
thinkingCompleteCount++;
|
|
7869
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
7870
|
-
break;
|
|
7871
8091
|
}
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
contentBlocks.push({
|
|
7875
|
-
type: "redacted_thinking",
|
|
7876
|
-
data: event.data,
|
|
7877
|
-
startedAt,
|
|
7878
|
-
completedAt: event.ts
|
|
7879
|
-
});
|
|
7880
|
-
lastThinkingRelatedStartedAt = startedAt;
|
|
8092
|
+
)) {
|
|
8093
|
+
if (signal?.aborted) {
|
|
7881
8094
|
break;
|
|
7882
8095
|
}
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
8096
|
+
switch (event.type) {
|
|
8097
|
+
case "text": {
|
|
8098
|
+
const lastBlock = contentBlocks.at(-1);
|
|
8099
|
+
if (lastBlock?.type === "text" && textBlockOpen) {
|
|
8100
|
+
lastBlock.text += event.text;
|
|
8101
|
+
} else {
|
|
8102
|
+
contentBlocks.push({
|
|
8103
|
+
type: "text",
|
|
8104
|
+
text: event.text,
|
|
8105
|
+
startedAt: event.ts
|
|
8106
|
+
});
|
|
8107
|
+
}
|
|
8108
|
+
textBlockOpen = true;
|
|
8109
|
+
onEvent({ type: "text", text: event.text });
|
|
8110
|
+
break;
|
|
7890
8111
|
}
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
7896
|
-
break;
|
|
7897
|
-
}
|
|
7898
|
-
case "tool_use": {
|
|
7899
|
-
const tool = getToolByName(event.name);
|
|
7900
|
-
contentBlocks.push({
|
|
7901
|
-
type: "tool",
|
|
7902
|
-
id: event.id,
|
|
7903
|
-
name: event.name,
|
|
7904
|
-
input: event.input,
|
|
7905
|
-
startedAt: event.ts,
|
|
7906
|
-
...(event.input.background || tool?.backgroundOnly) && {
|
|
7907
|
-
background: true
|
|
8112
|
+
case "thinking":
|
|
8113
|
+
if (event.text === "") {
|
|
8114
|
+
thinkingBlockStartTimes.push(event.ts);
|
|
8115
|
+
textBlockOpen = false;
|
|
7908
8116
|
}
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
8117
|
+
onEvent({ type: "thinking", text: event.text });
|
|
8118
|
+
break;
|
|
8119
|
+
case "thinking_complete": {
|
|
8120
|
+
const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
|
|
8121
|
+
contentBlocks.push({
|
|
8122
|
+
type: "thinking",
|
|
8123
|
+
thinking: event.thinking,
|
|
8124
|
+
signature: event.signature,
|
|
8125
|
+
startedAt,
|
|
8126
|
+
completedAt: event.ts
|
|
8127
|
+
});
|
|
8128
|
+
thinkingCompleteCount++;
|
|
8129
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8130
|
+
break;
|
|
8131
|
+
}
|
|
8132
|
+
case "redacted_thinking_complete": {
|
|
8133
|
+
const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
|
|
8134
|
+
contentBlocks.push({
|
|
8135
|
+
type: "redacted_thinking",
|
|
8136
|
+
data: event.data,
|
|
8137
|
+
startedAt,
|
|
8138
|
+
completedAt: event.ts
|
|
8139
|
+
});
|
|
8140
|
+
lastThinkingRelatedStartedAt = startedAt;
|
|
8141
|
+
break;
|
|
8142
|
+
}
|
|
8143
|
+
case "tool_input_delta": {
|
|
8144
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8145
|
+
acc.json += event.delta;
|
|
8146
|
+
try {
|
|
8147
|
+
const partial = parsePartialJson(acc.json);
|
|
8148
|
+
await handlePartialInput(acc, event.id, event.name, partial);
|
|
8149
|
+
} catch {
|
|
8150
|
+
}
|
|
8151
|
+
break;
|
|
8152
|
+
}
|
|
8153
|
+
case "tool_input_args": {
|
|
8154
|
+
const acc = getOrCreateAccumulator2(event.id, event.name);
|
|
8155
|
+
await handlePartialInput(acc, event.id, event.name, event.args);
|
|
8156
|
+
break;
|
|
8157
|
+
}
|
|
8158
|
+
case "tool_use": {
|
|
8159
|
+
const tool = getToolByName(event.name);
|
|
8160
|
+
contentBlocks.push({
|
|
8161
|
+
type: "tool",
|
|
7921
8162
|
id: event.id,
|
|
7922
8163
|
name: event.name,
|
|
7923
|
-
input: event.input
|
|
8164
|
+
input: event.input,
|
|
8165
|
+
startedAt: event.ts,
|
|
8166
|
+
...(event.input.background || tool?.backgroundOnly) && {
|
|
8167
|
+
background: true
|
|
8168
|
+
}
|
|
8169
|
+
});
|
|
8170
|
+
const acc = toolInputAccumulators.get(event.id);
|
|
8171
|
+
const wasStreamed = acc?.started ?? false;
|
|
8172
|
+
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
8173
|
+
log14.info("Tool received", {
|
|
8174
|
+
requestId,
|
|
8175
|
+
toolCallId: event.id,
|
|
8176
|
+
name: event.name
|
|
7924
8177
|
});
|
|
8178
|
+
if (!wasStreamed || isInputStreaming) {
|
|
8179
|
+
onEvent({
|
|
8180
|
+
type: "tool_start",
|
|
8181
|
+
id: event.id,
|
|
8182
|
+
name: event.name,
|
|
8183
|
+
input: event.input,
|
|
8184
|
+
// Mirror the content-block stamp above so the live event
|
|
8185
|
+
// matches what a history reload shows — without it the
|
|
8186
|
+
// frontend renders background rows as already complete.
|
|
8187
|
+
...isBackgroundCall({
|
|
8188
|
+
name: event.name,
|
|
8189
|
+
input: event.input
|
|
8190
|
+
}) && { background: true }
|
|
8191
|
+
});
|
|
8192
|
+
}
|
|
8193
|
+
break;
|
|
7925
8194
|
}
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
}
|
|
7965
|
-
}
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
}
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
8195
|
+
case "done":
|
|
8196
|
+
stopReason = event.stopReason;
|
|
8197
|
+
turnProviderMetadata = event.providerMetadata;
|
|
8198
|
+
turnModelId = event.modelId;
|
|
8199
|
+
turnLlmCalls++;
|
|
8200
|
+
lastCallInputTokens = event.usage.inputTokens;
|
|
8201
|
+
lastCallCacheCreation = event.usage.cacheCreationTokens ?? 0;
|
|
8202
|
+
lastCallCacheRead = event.usage.cacheReadTokens ?? 0;
|
|
8203
|
+
turnInputTokens += lastCallInputTokens;
|
|
8204
|
+
turnOutputTokens += event.usage.outputTokens;
|
|
8205
|
+
turnCacheCreation += lastCallCacheCreation;
|
|
8206
|
+
turnCacheRead += lastCallCacheRead;
|
|
8207
|
+
recordUsage({
|
|
8208
|
+
ts: Date.now(),
|
|
8209
|
+
requestId,
|
|
8210
|
+
agentName: "parent",
|
|
8211
|
+
modelId: event.modelId,
|
|
8212
|
+
inputTokens: event.usage.inputTokens,
|
|
8213
|
+
outputTokens: event.usage.outputTokens,
|
|
8214
|
+
cacheCreationTokens: event.usage.cacheCreationTokens,
|
|
8215
|
+
cacheReadTokens: event.usage.cacheReadTokens,
|
|
8216
|
+
cost: nanoToDollars(event.cost),
|
|
8217
|
+
billingEvents: event.billingEvents,
|
|
8218
|
+
durationMs: Date.now() - iterStart,
|
|
8219
|
+
toolNames: contentBlocks.filter(
|
|
8220
|
+
(b) => b.type === "tool"
|
|
8221
|
+
).map((b) => b.name)
|
|
8222
|
+
});
|
|
8223
|
+
break;
|
|
8224
|
+
case "error":
|
|
8225
|
+
statusWatcher.stop();
|
|
8226
|
+
onEvent({
|
|
8227
|
+
type: "error",
|
|
8228
|
+
error: friendlyError(event.error),
|
|
8229
|
+
...event.code ? { code: event.code } : {}
|
|
8230
|
+
});
|
|
8231
|
+
return;
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
} catch (err) {
|
|
8235
|
+
if (signal?.aborted) {
|
|
8236
|
+
} else {
|
|
8237
|
+
throw err;
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
if (signal?.aborted) {
|
|
8241
|
+
statusWatcher.stop();
|
|
8242
|
+
if (contentBlocks.length > 0) {
|
|
8243
|
+
contentBlocks.push({
|
|
8244
|
+
type: "text",
|
|
8245
|
+
text: "\n\n(cancelled)",
|
|
8246
|
+
startedAt: Date.now()
|
|
8247
|
+
});
|
|
8248
|
+
state.messages.push({
|
|
8249
|
+
role: "assistant",
|
|
8250
|
+
content: [...contentBlocks].sort(
|
|
8251
|
+
(a, b) => a.startedAt - b.startedAt
|
|
8252
|
+
),
|
|
8253
|
+
usage: {
|
|
8254
|
+
inputTokens: turnInputTokens,
|
|
8255
|
+
outputTokens: turnOutputTokens,
|
|
8256
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8257
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8258
|
+
llmCalls: turnLlmCalls
|
|
8259
|
+
},
|
|
8260
|
+
...turnProviderMetadata && {
|
|
8261
|
+
providerMetadata: turnProviderMetadata
|
|
8262
|
+
},
|
|
8263
|
+
model: turnModelId ?? parentModel,
|
|
8264
|
+
...modelOverride && { modelOverride }
|
|
8265
|
+
});
|
|
8266
|
+
}
|
|
8267
|
+
onEvent({ type: "turn_cancelled" });
|
|
8268
|
+
saveSession(state);
|
|
8269
|
+
return;
|
|
8270
|
+
}
|
|
8271
|
+
if (contentBlocks.length > 0) {
|
|
8272
|
+
state.messages.push({
|
|
8273
|
+
role: "assistant",
|
|
7982
8274
|
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
7983
8275
|
usage: {
|
|
7984
8276
|
inputTokens: turnInputTokens,
|
|
@@ -7994,217 +8286,212 @@ async function runTurn(params) {
|
|
|
7994
8286
|
...modelOverride && { modelOverride }
|
|
7995
8287
|
});
|
|
7996
8288
|
}
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
});
|
|
8016
|
-
}
|
|
8017
|
-
const toolCalls = getToolCalls(contentBlocks);
|
|
8018
|
-
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8019
|
-
statusWatcher.stop();
|
|
8020
|
-
saveSession(state);
|
|
8021
|
-
onEvent({
|
|
8022
|
-
type: "turn_done",
|
|
8023
|
-
stats: {
|
|
8024
|
-
inputTokens: turnInputTokens,
|
|
8025
|
-
outputTokens: turnOutputTokens,
|
|
8026
|
-
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8027
|
-
cacheReadTokens: turnCacheRead || void 0,
|
|
8028
|
-
llmCalls: turnLlmCalls,
|
|
8029
|
-
lastCallInputTokens,
|
|
8030
|
-
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8031
|
-
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8032
|
-
}
|
|
8033
|
-
});
|
|
8034
|
-
return;
|
|
8035
|
-
}
|
|
8036
|
-
log14.info("Tools executing", {
|
|
8037
|
-
requestId,
|
|
8038
|
-
count: toolCalls.length,
|
|
8039
|
-
tools: toolCalls.map((tc) => tc.name)
|
|
8040
|
-
});
|
|
8041
|
-
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8042
|
-
const wrappedOnEvent = (e) => {
|
|
8043
|
-
if ("parentToolId" in e && e.parentToolId) {
|
|
8044
|
-
if (e.type === "text") {
|
|
8045
|
-
subAgentText = e.text;
|
|
8046
|
-
} else if (e.type === "tool_start") {
|
|
8047
|
-
subAgentText = `Using ${e.name}`;
|
|
8048
|
-
}
|
|
8289
|
+
const toolCalls = getToolCalls(contentBlocks);
|
|
8290
|
+
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
8291
|
+
statusWatcher.stop();
|
|
8292
|
+
saveSession(state);
|
|
8293
|
+
onEvent({
|
|
8294
|
+
type: "turn_done",
|
|
8295
|
+
stats: {
|
|
8296
|
+
inputTokens: turnInputTokens,
|
|
8297
|
+
outputTokens: turnOutputTokens,
|
|
8298
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
8299
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
8300
|
+
llmCalls: turnLlmCalls,
|
|
8301
|
+
lastCallInputTokens,
|
|
8302
|
+
lastCallCacheCreation: lastCallCacheCreation || void 0,
|
|
8303
|
+
lastCallCacheRead: lastCallCacheRead || void 0
|
|
8304
|
+
}
|
|
8305
|
+
});
|
|
8306
|
+
return;
|
|
8049
8307
|
}
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8308
|
+
log14.info("Tools executing", {
|
|
8309
|
+
requestId,
|
|
8310
|
+
count: toolCalls.length,
|
|
8311
|
+
tools: toolCalls.map((tc) => tc.name)
|
|
8312
|
+
});
|
|
8313
|
+
currentToolNames = toolCalls.filter((tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)).map((tc) => tc.name).join(", ");
|
|
8314
|
+
const wrappedOnEvent = (e) => {
|
|
8315
|
+
if ("parentToolId" in e && e.parentToolId) {
|
|
8316
|
+
if (e.type === "text") {
|
|
8317
|
+
subAgentText = e.text;
|
|
8318
|
+
} else if (e.type === "tool_start") {
|
|
8319
|
+
subAgentText = `Using ${e.name}`;
|
|
8320
|
+
}
|
|
8057
8321
|
}
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
const safeSettle = (result, isError) => {
|
|
8066
|
-
if (settled) {
|
|
8067
|
-
return;
|
|
8322
|
+
onEvent(e);
|
|
8323
|
+
};
|
|
8324
|
+
const subAgentMessages = /* @__PURE__ */ new Map();
|
|
8325
|
+
const results = await Promise.all(
|
|
8326
|
+
toolCalls.map(async (tc) => {
|
|
8327
|
+
if (signal?.aborted) {
|
|
8328
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
8068
8329
|
}
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
toolAbort
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8330
|
+
const toolStart = Date.now();
|
|
8331
|
+
let settle;
|
|
8332
|
+
const resultPromise = new Promise((res) => {
|
|
8333
|
+
settle = (result, isError) => res({ id: tc.id, result, isError });
|
|
8334
|
+
});
|
|
8335
|
+
let toolAbort = new AbortController();
|
|
8336
|
+
let settled = false;
|
|
8337
|
+
const safeSettle = (result, isError) => {
|
|
8338
|
+
if (settled) {
|
|
8339
|
+
return;
|
|
8340
|
+
}
|
|
8341
|
+
settled = true;
|
|
8342
|
+
signal?.removeEventListener("abort", cascadeAbort);
|
|
8343
|
+
settle(result, isError);
|
|
8344
|
+
};
|
|
8345
|
+
const cascadeAbort = () => {
|
|
8346
|
+
toolAbort.abort();
|
|
8347
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
8348
|
+
};
|
|
8349
|
+
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
8350
|
+
const run = async (input) => {
|
|
8351
|
+
try {
|
|
8352
|
+
let result;
|
|
8353
|
+
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
8354
|
+
saveSession(state);
|
|
8355
|
+
log14.info("Waiting for external tool result", {
|
|
8356
|
+
requestId,
|
|
8357
|
+
toolCallId: tc.id,
|
|
8358
|
+
name: tc.name
|
|
8359
|
+
});
|
|
8360
|
+
const blocksUser = USER_BLOCKING_EXTERNAL_TOOLS.has(tc.name);
|
|
8095
8361
|
if (blocksUser) {
|
|
8096
|
-
statusWatcher.
|
|
8362
|
+
statusWatcher.pause();
|
|
8363
|
+
}
|
|
8364
|
+
try {
|
|
8365
|
+
result = await resolveExternalTool(tc.id, tc.name, input);
|
|
8366
|
+
} finally {
|
|
8367
|
+
if (blocksUser) {
|
|
8368
|
+
statusWatcher.resume();
|
|
8369
|
+
}
|
|
8097
8370
|
}
|
|
8371
|
+
} else {
|
|
8372
|
+
result = await executeTool(tc.name, input, {
|
|
8373
|
+
apiConfig,
|
|
8374
|
+
model,
|
|
8375
|
+
models: state.models,
|
|
8376
|
+
signal: toolAbort.signal,
|
|
8377
|
+
onEvent: wrappedOnEvent,
|
|
8378
|
+
resolveExternalTool,
|
|
8379
|
+
toolCallId: tc.id,
|
|
8380
|
+
requestId,
|
|
8381
|
+
onboardingState,
|
|
8382
|
+
subAgentMessages,
|
|
8383
|
+
conversationMessages: state.messages,
|
|
8384
|
+
toolRegistry,
|
|
8385
|
+
onBackgroundComplete,
|
|
8386
|
+
onLog: (line) => wrappedOnEvent({
|
|
8387
|
+
type: "tool_input_delta",
|
|
8388
|
+
id: tc.id,
|
|
8389
|
+
name: tc.name,
|
|
8390
|
+
result: line
|
|
8391
|
+
})
|
|
8392
|
+
});
|
|
8098
8393
|
}
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
id: tc.id,
|
|
8117
|
-
name: tc.name,
|
|
8118
|
-
result: line
|
|
8119
|
-
})
|
|
8394
|
+
safeSettle(capToolResult(result), result.startsWith("Error"));
|
|
8395
|
+
} catch (err) {
|
|
8396
|
+
safeSettle(`Error: ${err.message}`, true);
|
|
8397
|
+
}
|
|
8398
|
+
};
|
|
8399
|
+
const entry = {
|
|
8400
|
+
id: tc.id,
|
|
8401
|
+
name: tc.name,
|
|
8402
|
+
input: tc.input,
|
|
8403
|
+
abortController: toolAbort,
|
|
8404
|
+
startedAt: toolStart,
|
|
8405
|
+
settle: safeSettle,
|
|
8406
|
+
rerun: (newInput) => {
|
|
8407
|
+
settled = false;
|
|
8408
|
+
toolAbort = new AbortController();
|
|
8409
|
+
signal?.addEventListener("abort", () => toolAbort.abort(), {
|
|
8410
|
+
once: true
|
|
8120
8411
|
});
|
|
8412
|
+
entry.abortController = toolAbort;
|
|
8413
|
+
entry.input = newInput;
|
|
8414
|
+
run(newInput);
|
|
8121
8415
|
}
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8416
|
+
};
|
|
8417
|
+
toolRegistry?.register(entry);
|
|
8418
|
+
run(tc.input);
|
|
8419
|
+
const r = await resultPromise;
|
|
8420
|
+
if (!isBackgroundCall(tc)) {
|
|
8421
|
+
toolRegistry?.unregister(tc.id);
|
|
8125
8422
|
}
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8423
|
+
log14.info("Tool completed", {
|
|
8424
|
+
requestId,
|
|
8425
|
+
toolCallId: tc.id,
|
|
8426
|
+
name: tc.name,
|
|
8427
|
+
durationMs: Date.now() - toolStart,
|
|
8428
|
+
isError: r.isError
|
|
8429
|
+
});
|
|
8430
|
+
onEvent({
|
|
8431
|
+
type: "tool_done",
|
|
8432
|
+
id: tc.id,
|
|
8433
|
+
name: tc.name,
|
|
8434
|
+
result: r.result,
|
|
8435
|
+
isError: r.isError
|
|
8436
|
+
});
|
|
8437
|
+
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8438
|
+
triggerBrandExtraction(
|
|
8439
|
+
apiConfig,
|
|
8440
|
+
resolveModel("brandExtractor", state.models, model)
|
|
8441
|
+
);
|
|
8442
|
+
}
|
|
8443
|
+
return r;
|
|
8444
|
+
})
|
|
8445
|
+
);
|
|
8446
|
+
for (const r of results) {
|
|
8447
|
+
const block = contentBlocks.find(
|
|
8448
|
+
(b) => b.type === "tool" && b.id === r.id
|
|
8449
|
+
);
|
|
8450
|
+
if (block?.type === "tool") {
|
|
8451
|
+
block.result = r.result;
|
|
8452
|
+
block.isError = r.isError;
|
|
8453
|
+
block.completedAt = Date.now();
|
|
8454
|
+
const msgs = subAgentMessages.get(r.id);
|
|
8455
|
+
if (msgs) {
|
|
8456
|
+
block.subAgentMessages = msgs;
|
|
8143
8457
|
}
|
|
8144
|
-
};
|
|
8145
|
-
toolRegistry?.register(entry);
|
|
8146
|
-
run(tc.input);
|
|
8147
|
-
const r = await resultPromise;
|
|
8148
|
-
if (!isBackgroundCall(tc)) {
|
|
8149
|
-
toolRegistry?.unregister(tc.id);
|
|
8150
|
-
}
|
|
8151
|
-
log14.info("Tool completed", {
|
|
8152
|
-
requestId,
|
|
8153
|
-
toolCallId: tc.id,
|
|
8154
|
-
name: tc.name,
|
|
8155
|
-
durationMs: Date.now() - toolStart,
|
|
8156
|
-
isError: r.isError
|
|
8157
|
-
});
|
|
8158
|
-
onEvent({
|
|
8159
|
-
type: "tool_done",
|
|
8160
|
-
id: tc.id,
|
|
8161
|
-
name: tc.name,
|
|
8162
|
-
result: r.result,
|
|
8163
|
-
isError: r.isError
|
|
8164
|
-
});
|
|
8165
|
-
if (!r.isError && BRAND_TRIGGERING_TOOLS.has(tc.name)) {
|
|
8166
|
-
triggerBrandExtraction(
|
|
8167
|
-
apiConfig,
|
|
8168
|
-
resolveModel("brandExtractor", state.models, model)
|
|
8169
|
-
);
|
|
8170
8458
|
}
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
statusWatcher.stop();
|
|
8175
|
-
for (const r of results) {
|
|
8176
|
-
const block = contentBlocks.find(
|
|
8177
|
-
(b) => b.type === "tool" && b.id === r.id
|
|
8459
|
+
}
|
|
8460
|
+
const lastNonExcluded = toolCalls.filter(
|
|
8461
|
+
(tc) => !STATUS_EXCLUDED_TOOLS.has(tc.name)
|
|
8178
8462
|
);
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8463
|
+
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8464
|
+
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8465
|
+
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8466
|
+
for (const r of results) {
|
|
8467
|
+
state.messages.push({
|
|
8468
|
+
role: "user",
|
|
8469
|
+
content: r.result,
|
|
8470
|
+
toolCallId: r.id,
|
|
8471
|
+
isToolError: r.isError
|
|
8472
|
+
});
|
|
8473
|
+
}
|
|
8474
|
+
if (takeSteering && !signal?.aborted) {
|
|
8475
|
+
const injected = (await takeSteering()).filter(
|
|
8476
|
+
(e) => e.text.trim().length > 0 || (e.attachments?.length ?? 0) > 0
|
|
8477
|
+
);
|
|
8478
|
+
if (injected.length > 0) {
|
|
8479
|
+
for (const entry of injected) {
|
|
8480
|
+
appendEntry(entry);
|
|
8481
|
+
keptEntries.push(entry);
|
|
8482
|
+
}
|
|
8483
|
+
saveSession(state);
|
|
8186
8484
|
}
|
|
8187
8485
|
}
|
|
8486
|
+
if (signal?.aborted) {
|
|
8487
|
+
statusWatcher.stop();
|
|
8488
|
+
onEvent({ type: "turn_cancelled" });
|
|
8489
|
+
saveSession(state);
|
|
8490
|
+
return;
|
|
8491
|
+
}
|
|
8188
8492
|
}
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
);
|
|
8192
|
-
lastCompletedTools = lastNonExcluded.map((tc) => tc.name).join(", ");
|
|
8193
|
-
lastCompletedInput = JSON.stringify(lastNonExcluded.at(-1)?.input ?? {});
|
|
8194
|
-
lastCompletedResult = results.at(-1)?.result ?? "";
|
|
8195
|
-
for (const r of results) {
|
|
8196
|
-
state.messages.push({
|
|
8197
|
-
role: "user",
|
|
8198
|
-
content: r.result,
|
|
8199
|
-
toolCallId: r.id,
|
|
8200
|
-
isToolError: r.isError
|
|
8201
|
-
});
|
|
8202
|
-
}
|
|
8203
|
-
if (signal?.aborted) {
|
|
8204
|
-
onEvent({ type: "turn_cancelled" });
|
|
8205
|
-
saveSession(state);
|
|
8206
|
-
return;
|
|
8207
|
-
}
|
|
8493
|
+
} finally {
|
|
8494
|
+
statusWatcher.stop();
|
|
8208
8495
|
}
|
|
8209
8496
|
}
|
|
8210
8497
|
|
|
@@ -8375,13 +8662,24 @@ function loadQueue() {
|
|
|
8375
8662
|
}
|
|
8376
8663
|
return [];
|
|
8377
8664
|
}
|
|
8378
|
-
function
|
|
8665
|
+
function loadPassiveResults() {
|
|
8666
|
+
try {
|
|
8667
|
+
const stats = JSON.parse(readFileSync2(STATS_FILE, "utf-8"));
|
|
8668
|
+
if (Array.isArray(stats.passiveResults)) {
|
|
8669
|
+
return stats.passiveResults;
|
|
8670
|
+
}
|
|
8671
|
+
} catch {
|
|
8672
|
+
}
|
|
8673
|
+
return [];
|
|
8674
|
+
}
|
|
8675
|
+
function writeStats(stats, queue, passiveResults) {
|
|
8379
8676
|
try {
|
|
8380
8677
|
writeFileSync2(
|
|
8381
8678
|
STATS_FILE,
|
|
8382
8679
|
JSON.stringify({
|
|
8383
8680
|
...stats,
|
|
8384
|
-
queue
|
|
8681
|
+
queue,
|
|
8682
|
+
passiveResults
|
|
8385
8683
|
})
|
|
8386
8684
|
);
|
|
8387
8685
|
} catch {
|
|
@@ -8445,6 +8743,21 @@ var MessageQueue = class {
|
|
|
8445
8743
|
}
|
|
8446
8744
|
return removed;
|
|
8447
8745
|
}
|
|
8746
|
+
/**
|
|
8747
|
+
* Change a queued item's delivery semantics, keyed by its command
|
|
8748
|
+
* requestId. Fires onChange (→ persist + queue_changed) on success.
|
|
8749
|
+
* Returns the item, or undefined if no queued item matches (e.g. it was
|
|
8750
|
+
* already consumed).
|
|
8751
|
+
*/
|
|
8752
|
+
setDelivery(id, delivery) {
|
|
8753
|
+
const item = this.items.find((it) => it.command.requestId === id);
|
|
8754
|
+
if (!item) {
|
|
8755
|
+
return void 0;
|
|
8756
|
+
}
|
|
8757
|
+
item.delivery = delivery;
|
|
8758
|
+
this.onChange?.();
|
|
8759
|
+
return item;
|
|
8760
|
+
}
|
|
8448
8761
|
/** Copy of current queue contents (for surfacing on events). */
|
|
8449
8762
|
snapshot() {
|
|
8450
8763
|
return [...this.items];
|
|
@@ -8462,62 +8775,6 @@ var MessageQueue = class {
|
|
|
8462
8775
|
}
|
|
8463
8776
|
};
|
|
8464
8777
|
|
|
8465
|
-
// src/automatedActions/resolve.ts
|
|
8466
|
-
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set(["background_results"]);
|
|
8467
|
-
function resolveAction(text) {
|
|
8468
|
-
const parsed = parseSentinel(text);
|
|
8469
|
-
if (!parsed) {
|
|
8470
|
-
return null;
|
|
8471
|
-
}
|
|
8472
|
-
const { name: triggerName, remainder } = parsed;
|
|
8473
|
-
if (NON_ACTION_SENTINELS.has(triggerName)) {
|
|
8474
|
-
return null;
|
|
8475
|
-
}
|
|
8476
|
-
let params = {};
|
|
8477
|
-
if (remainder) {
|
|
8478
|
-
try {
|
|
8479
|
-
params = JSON.parse(remainder.split("\n")[0]);
|
|
8480
|
-
} catch {
|
|
8481
|
-
}
|
|
8482
|
-
}
|
|
8483
|
-
let body = readAsset("automatedActions", `${triggerName}.md`);
|
|
8484
|
-
let next;
|
|
8485
|
-
const fmMatch = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
8486
|
-
if (fmMatch) {
|
|
8487
|
-
const nextMatch = fmMatch[1].match(/^\s*next:\s*(\w+)\s*$/m);
|
|
8488
|
-
if (nextMatch) {
|
|
8489
|
-
next = nextMatch[1];
|
|
8490
|
-
}
|
|
8491
|
-
}
|
|
8492
|
-
body = body.replace(/^---[\s\S]*?---\s*/, "");
|
|
8493
|
-
for (const [key, value] of Object.entries(params)) {
|
|
8494
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
8495
|
-
body = body.replaceAll(`{{${key}}}`, str);
|
|
8496
|
-
}
|
|
8497
|
-
return {
|
|
8498
|
-
message: automatedMessage(triggerName, body),
|
|
8499
|
-
next
|
|
8500
|
-
};
|
|
8501
|
-
}
|
|
8502
|
-
function getActionChain(startName) {
|
|
8503
|
-
const chain = [];
|
|
8504
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8505
|
-
let name = startName;
|
|
8506
|
-
while (name && !seen.has(name)) {
|
|
8507
|
-
seen.add(name);
|
|
8508
|
-
chain.push(name);
|
|
8509
|
-
let body;
|
|
8510
|
-
try {
|
|
8511
|
-
body = readAsset("automatedActions", `${name}.md`);
|
|
8512
|
-
} catch {
|
|
8513
|
-
break;
|
|
8514
|
-
}
|
|
8515
|
-
const fm = body.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
8516
|
-
name = fm?.[1].match(/^\s*next:\s*(\w+)\s*$/m)?.[1];
|
|
8517
|
-
}
|
|
8518
|
-
return chain;
|
|
8519
|
-
}
|
|
8520
|
-
|
|
8521
8778
|
// src/headless/index.ts
|
|
8522
8779
|
var log16 = createLogger("headless");
|
|
8523
8780
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
@@ -8559,11 +8816,27 @@ var HeadlessSession = class {
|
|
|
8559
8816
|
* to .remy-stats.json so queued work survives process restarts.
|
|
8560
8817
|
*/
|
|
8561
8818
|
queue;
|
|
8819
|
+
/**
|
|
8820
|
+
* Holding pen for passive background results (tools with
|
|
8821
|
+
* `backgroundNotify: 'passive'`, e.g. specSync). Deliberately outside the
|
|
8822
|
+
* message queue: pen contents never initiate a turn, never latch the
|
|
8823
|
+
* sandbox's queue-derived busy state, and never trigger resume-on-restart.
|
|
8824
|
+
* Swept into the next real turn as a hidden background_results entry.
|
|
8825
|
+
* Persisted to .remy-stats.json alongside the queue.
|
|
8826
|
+
*/
|
|
8827
|
+
passivePen = [];
|
|
8562
8828
|
// External tool bridge
|
|
8563
8829
|
pendingTools = /* @__PURE__ */ new Map();
|
|
8564
8830
|
earlyResults = /* @__PURE__ */ new Map();
|
|
8565
8831
|
// Tool block updates from background completions (separate from the message queue)
|
|
8566
8832
|
pendingBlockUpdates = [];
|
|
8833
|
+
/**
|
|
8834
|
+
* Id of the UI-only tool block synthesized for the in-flight user/gate
|
|
8835
|
+
* compaction (see the compaction listener). Model-invoked compactions have
|
|
8836
|
+
* a real block and never set this. Single slot — triggerCompaction is
|
|
8837
|
+
* single-flight.
|
|
8838
|
+
*/
|
|
8839
|
+
syntheticCompactionId = null;
|
|
8567
8840
|
// Tool lifecycle management — shared across all nesting depths
|
|
8568
8841
|
toolRegistry = new ToolRegistry();
|
|
8569
8842
|
// IO — accumulates stdin bytes between newline boundaries
|
|
@@ -8594,6 +8867,8 @@ var HeadlessSession = class {
|
|
|
8594
8867
|
this.persistStats();
|
|
8595
8868
|
this.emit("queue_changed", { queuedMessages: this.queue.snapshot() });
|
|
8596
8869
|
});
|
|
8870
|
+
this.passivePen = loadPassiveResults();
|
|
8871
|
+
this.persistStats();
|
|
8597
8872
|
if (resumed) {
|
|
8598
8873
|
this.emit("session_restored", {
|
|
8599
8874
|
messageCount: this.state.messages.length,
|
|
@@ -8616,15 +8891,61 @@ var HeadlessSession = class {
|
|
|
8616
8891
|
);
|
|
8617
8892
|
this.sessionStats.compactionInProgress = true;
|
|
8618
8893
|
this.persistStats();
|
|
8894
|
+
if (!event.toolCallId && (event.origin === "user" || event.origin === "gate")) {
|
|
8895
|
+
const id = `compact_${Date.now()}`;
|
|
8896
|
+
this.syntheticCompactionId = id;
|
|
8897
|
+
this.state.messages.push({
|
|
8898
|
+
role: "assistant",
|
|
8899
|
+
content: [
|
|
8900
|
+
{
|
|
8901
|
+
type: "tool",
|
|
8902
|
+
id,
|
|
8903
|
+
name: "compactConversation",
|
|
8904
|
+
input: {},
|
|
8905
|
+
startedAt: Date.now(),
|
|
8906
|
+
background: true,
|
|
8907
|
+
uiOnly: true,
|
|
8908
|
+
result: "Compaction started in the background."
|
|
8909
|
+
}
|
|
8910
|
+
]
|
|
8911
|
+
});
|
|
8912
|
+
saveSession(this.state);
|
|
8913
|
+
this.onEvent({
|
|
8914
|
+
type: "tool_start",
|
|
8915
|
+
id,
|
|
8916
|
+
name: "compactConversation",
|
|
8917
|
+
input: {},
|
|
8918
|
+
background: true
|
|
8919
|
+
});
|
|
8920
|
+
this.onEvent({
|
|
8921
|
+
type: "tool_done",
|
|
8922
|
+
id,
|
|
8923
|
+
name: "compactConversation",
|
|
8924
|
+
result: "Compaction started in the background.",
|
|
8925
|
+
isError: false
|
|
8926
|
+
});
|
|
8927
|
+
}
|
|
8619
8928
|
} else {
|
|
8620
8929
|
const data = event.error ? { error: event.error } : {};
|
|
8621
8930
|
this.emit("compaction_complete", data, event.requestId);
|
|
8931
|
+
if (this.syntheticCompactionId) {
|
|
8932
|
+
const id = this.syntheticCompactionId;
|
|
8933
|
+
this.syntheticCompactionId = null;
|
|
8934
|
+
this.onBackgroundComplete(
|
|
8935
|
+
id,
|
|
8936
|
+
"compactConversation",
|
|
8937
|
+
event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? [])
|
|
8938
|
+
);
|
|
8939
|
+
}
|
|
8622
8940
|
this.sessionStats.compactionInProgress = false;
|
|
8623
8941
|
if (!event.error) {
|
|
8624
8942
|
this.sessionStats.lastContextSize = 0;
|
|
8625
8943
|
}
|
|
8626
8944
|
this.sessionStats.messageCount = this.state.messages.length;
|
|
8627
8945
|
this.persistStats();
|
|
8946
|
+
if (!this.running) {
|
|
8947
|
+
this.kickDrain();
|
|
8948
|
+
}
|
|
8628
8949
|
}
|
|
8629
8950
|
});
|
|
8630
8951
|
process.stdin.setEncoding("utf-8");
|
|
@@ -8705,10 +9026,10 @@ var HeadlessSession = class {
|
|
|
8705
9026
|
//////////////////////////////////////////////////////////////////////////////
|
|
8706
9027
|
// Stats + queue persistence
|
|
8707
9028
|
//////////////////////////////////////////////////////////////////////////////
|
|
8708
|
-
/** Persist sessionStats + queue snapshot to .remy-stats.json. */
|
|
9029
|
+
/** Persist sessionStats + queue snapshot + passive pen to .remy-stats.json. */
|
|
8709
9030
|
persistStats() {
|
|
8710
9031
|
this.sessionStats.updatedAt = Date.now();
|
|
8711
|
-
writeStats(this.sessionStats, this.queue.snapshot());
|
|
9032
|
+
writeStats(this.sessionStats, this.queue.snapshot(), this.passivePen);
|
|
8712
9033
|
}
|
|
8713
9034
|
//////////////////////////////////////////////////////////////////////////////
|
|
8714
9035
|
// Background completions (tool-block mutation; message delivery via queue)
|
|
@@ -8763,17 +9084,20 @@ var HeadlessSession = class {
|
|
|
8763
9084
|
await triggerCompaction(this.state, this.config, {
|
|
8764
9085
|
blocking: true,
|
|
8765
9086
|
requestId,
|
|
8766
|
-
model: this.opts.model
|
|
9087
|
+
model: this.opts.model,
|
|
9088
|
+
origin: "gate"
|
|
8767
9089
|
});
|
|
8768
9090
|
applyPendingSummaries(this.state);
|
|
8769
9091
|
} catch {
|
|
8770
9092
|
}
|
|
8771
9093
|
}
|
|
8772
9094
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
9095
|
+
const notify = getToolByName(name)?.backgroundNotify ?? "wake";
|
|
8773
9096
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
8774
9097
|
log16.info("Background complete", {
|
|
8775
9098
|
toolCallId,
|
|
8776
9099
|
name,
|
|
9100
|
+
notify,
|
|
8777
9101
|
requestId: this.currentRequestId
|
|
8778
9102
|
});
|
|
8779
9103
|
this.onEvent({
|
|
@@ -8782,6 +9106,20 @@ var HeadlessSession = class {
|
|
|
8782
9106
|
name,
|
|
8783
9107
|
result
|
|
8784
9108
|
});
|
|
9109
|
+
if (notify === "passive") {
|
|
9110
|
+
this.passivePen.push({ toolCallId, name, result });
|
|
9111
|
+
this.persistStats();
|
|
9112
|
+
if (!this.running) {
|
|
9113
|
+
this.applyPendingBlockUpdates();
|
|
9114
|
+
}
|
|
9115
|
+
return;
|
|
9116
|
+
}
|
|
9117
|
+
if (notify === "silent") {
|
|
9118
|
+
if (!this.running) {
|
|
9119
|
+
this.applyPendingBlockUpdates();
|
|
9120
|
+
}
|
|
9121
|
+
return;
|
|
9122
|
+
}
|
|
8785
9123
|
this.queue.push({
|
|
8786
9124
|
command: {
|
|
8787
9125
|
action: "message",
|
|
@@ -8850,7 +9188,10 @@ var HeadlessSession = class {
|
|
|
8850
9188
|
...e.attachments && { attachments: e.attachments },
|
|
8851
9189
|
// Queue-delivered entries are flagged so the frontend renders the
|
|
8852
9190
|
// echo (idle sends are rendered optimistically instead).
|
|
8853
|
-
...e.queued && { queued: true }
|
|
9191
|
+
...e.queued && { queued: true },
|
|
9192
|
+
// Hidden entries (e.g. the passive background_results sweep) are
|
|
9193
|
+
// flagged so the sandbox/frontend suppress the bubble.
|
|
9194
|
+
...e.hidden && { hidden: true }
|
|
8854
9195
|
},
|
|
8855
9196
|
// A merged turn emits one user_message per absorbed entry — each
|
|
8856
9197
|
// carries its own original requestId, not the turn's.
|
|
@@ -9160,6 +9501,43 @@ var HeadlessSession = class {
|
|
|
9160
9501
|
this.completedEmitted = false;
|
|
9161
9502
|
this.lastCompleted = null;
|
|
9162
9503
|
this.turnStart = Date.now();
|
|
9504
|
+
const inflight2 = getInflightCompaction();
|
|
9505
|
+
if (inflight2) {
|
|
9506
|
+
await inflight2.catch(() => {
|
|
9507
|
+
});
|
|
9508
|
+
}
|
|
9509
|
+
applyPendingSummaries(this.state);
|
|
9510
|
+
if (this.passivePen.length > 0) {
|
|
9511
|
+
const swept = this.passivePen.splice(0);
|
|
9512
|
+
this.persistStats();
|
|
9513
|
+
entries.unshift({
|
|
9514
|
+
text: buildBackgroundResultsMessage(swept),
|
|
9515
|
+
hidden: true
|
|
9516
|
+
});
|
|
9517
|
+
}
|
|
9518
|
+
const consumedRids = [];
|
|
9519
|
+
const takeSteering = async () => {
|
|
9520
|
+
const items = this.queue.removeWhere(
|
|
9521
|
+
(it) => it.source === "user" && it.delivery === "asap" && !this.isDrainBarrier(it)
|
|
9522
|
+
);
|
|
9523
|
+
const steered = [];
|
|
9524
|
+
for (const it of items) {
|
|
9525
|
+
const attachments = it.command.attachments;
|
|
9526
|
+
const attachmentHeader = await this.persistEntryAttachments(attachments);
|
|
9527
|
+
const rid = it.command.requestId;
|
|
9528
|
+
if (rid) {
|
|
9529
|
+
consumedRids.push(rid);
|
|
9530
|
+
}
|
|
9531
|
+
steered.push({
|
|
9532
|
+
text: it.command.text ?? "",
|
|
9533
|
+
attachments,
|
|
9534
|
+
attachmentHeader,
|
|
9535
|
+
requestId: rid,
|
|
9536
|
+
queued: true
|
|
9537
|
+
});
|
|
9538
|
+
}
|
|
9539
|
+
return steered;
|
|
9540
|
+
};
|
|
9163
9541
|
await this.runForcedCompactionIfNeeded(requestId);
|
|
9164
9542
|
try {
|
|
9165
9543
|
await runTurn({
|
|
@@ -9173,6 +9551,7 @@ var HeadlessSession = class {
|
|
|
9173
9551
|
requestId,
|
|
9174
9552
|
signal: this.currentAbort.signal,
|
|
9175
9553
|
onEvent: this.onEvent,
|
|
9554
|
+
takeSteering,
|
|
9176
9555
|
resolveExternalTool: this.resolveExternalTool,
|
|
9177
9556
|
toolRegistry: this.toolRegistry,
|
|
9178
9557
|
onBackgroundComplete: this.onBackgroundComplete
|
|
@@ -9202,7 +9581,7 @@ var HeadlessSession = class {
|
|
|
9202
9581
|
});
|
|
9203
9582
|
}
|
|
9204
9583
|
const outcome = this.primaryOutcome();
|
|
9205
|
-
for (const rid of absorbedRids) {
|
|
9584
|
+
for (const rid of [...absorbedRids, ...consumedRids]) {
|
|
9206
9585
|
this.emit(
|
|
9207
9586
|
"completed",
|
|
9208
9587
|
{
|
|
@@ -9217,7 +9596,7 @@ var HeadlessSession = class {
|
|
|
9217
9596
|
this.applyPendingBlockUpdates();
|
|
9218
9597
|
}
|
|
9219
9598
|
async handleMessage(parsed, requestId) {
|
|
9220
|
-
if (this.running) {
|
|
9599
|
+
if (this.running || getInflightCompaction()) {
|
|
9221
9600
|
const command = { ...parsed };
|
|
9222
9601
|
if (requestId && command.requestId === void 0) {
|
|
9223
9602
|
command.requestId = requestId;
|
|
@@ -9227,6 +9606,9 @@ var HeadlessSession = class {
|
|
|
9227
9606
|
source: "user",
|
|
9228
9607
|
enqueuedAt: Date.now()
|
|
9229
9608
|
});
|
|
9609
|
+
if (!this.running && !getInflightCompaction()) {
|
|
9610
|
+
this.kickDrain();
|
|
9611
|
+
}
|
|
9230
9612
|
return;
|
|
9231
9613
|
}
|
|
9232
9614
|
this.running = true;
|
|
@@ -9234,6 +9616,7 @@ var HeadlessSession = class {
|
|
|
9234
9616
|
await this.runSingleTurn(parsed, requestId);
|
|
9235
9617
|
await this.drainQueueLoop();
|
|
9236
9618
|
} finally {
|
|
9619
|
+
this.currentAbort?.abort();
|
|
9237
9620
|
this.currentAbort = null;
|
|
9238
9621
|
this.currentRequestId = void 0;
|
|
9239
9622
|
this.running = false;
|
|
@@ -9265,6 +9648,18 @@ var HeadlessSession = class {
|
|
|
9265
9648
|
async drainQueueLoop() {
|
|
9266
9649
|
while (this.queue.length > 0) {
|
|
9267
9650
|
const head = this.queue.peek();
|
|
9651
|
+
if (head.command.action === "compact") {
|
|
9652
|
+
this.queue.shift();
|
|
9653
|
+
await triggerCompaction(this.state, this.config, {
|
|
9654
|
+
blocking: true,
|
|
9655
|
+
requestId: head.command.requestId,
|
|
9656
|
+
model: this.opts.model,
|
|
9657
|
+
origin: "user"
|
|
9658
|
+
}).catch(() => {
|
|
9659
|
+
});
|
|
9660
|
+
applyPendingSummaries(this.state);
|
|
9661
|
+
continue;
|
|
9662
|
+
}
|
|
9268
9663
|
if (head.source === "chain") {
|
|
9269
9664
|
const item = this.queue.shift();
|
|
9270
9665
|
const rid = item.command.requestId ?? `chain-${Date.now()}`;
|
|
@@ -9309,6 +9704,7 @@ var HeadlessSession = class {
|
|
|
9309
9704
|
try {
|
|
9310
9705
|
await this.drainQueueLoop();
|
|
9311
9706
|
} finally {
|
|
9707
|
+
this.currentAbort?.abort();
|
|
9312
9708
|
this.currentAbort = null;
|
|
9313
9709
|
this.currentRequestId = void 0;
|
|
9314
9710
|
this.running = false;
|
|
@@ -9497,6 +9893,33 @@ var HeadlessSession = class {
|
|
|
9497
9893
|
);
|
|
9498
9894
|
return;
|
|
9499
9895
|
}
|
|
9896
|
+
if (action === "setQueuedDelivery") {
|
|
9897
|
+
const id = parsed.id;
|
|
9898
|
+
const delivery = parsed.delivery;
|
|
9899
|
+
if (!id || delivery !== "asap" && delivery !== "afterTurn") {
|
|
9900
|
+
this.emit(
|
|
9901
|
+
"completed",
|
|
9902
|
+
{
|
|
9903
|
+
success: false,
|
|
9904
|
+
error: 'setQueuedDelivery requires id and delivery ("asap" | "afterTurn")'
|
|
9905
|
+
},
|
|
9906
|
+
requestId
|
|
9907
|
+
);
|
|
9908
|
+
return;
|
|
9909
|
+
}
|
|
9910
|
+
const target = this.queue.snapshot().find((it) => it.command.requestId === id);
|
|
9911
|
+
if (!target || target.source !== "user" || this.isDrainBarrier(target)) {
|
|
9912
|
+
this.emit(
|
|
9913
|
+
"completed",
|
|
9914
|
+
{ success: false, error: "message not found or not promotable" },
|
|
9915
|
+
requestId
|
|
9916
|
+
);
|
|
9917
|
+
return;
|
|
9918
|
+
}
|
|
9919
|
+
this.queue.setDelivery(id, delivery);
|
|
9920
|
+
this.emit("completed", { success: true }, requestId);
|
|
9921
|
+
return;
|
|
9922
|
+
}
|
|
9500
9923
|
if (action === "stop_tool") {
|
|
9501
9924
|
const id = parsed.id;
|
|
9502
9925
|
const mode = parsed.mode ?? "hard";
|
|
@@ -9528,11 +9951,31 @@ var HeadlessSession = class {
|
|
|
9528
9951
|
return;
|
|
9529
9952
|
}
|
|
9530
9953
|
if (action === "compact") {
|
|
9954
|
+
if (this.running) {
|
|
9955
|
+
const alreadyQueued = this.queue.snapshot().some((it) => it.command.action === "compact");
|
|
9956
|
+
if (!alreadyQueued) {
|
|
9957
|
+
this.queue.push({
|
|
9958
|
+
command: {
|
|
9959
|
+
action: "compact",
|
|
9960
|
+
// Display label for the queue card (matches the automated
|
|
9961
|
+
// action idiom); never resolved as an action — drainQueueLoop
|
|
9962
|
+
// routes on command.action before any sentinel handling.
|
|
9963
|
+
text: sentinel("compact"),
|
|
9964
|
+
requestId
|
|
9965
|
+
},
|
|
9966
|
+
source: "user",
|
|
9967
|
+
enqueuedAt: Date.now()
|
|
9968
|
+
});
|
|
9969
|
+
}
|
|
9970
|
+
this.emit("completed", { success: true, queued: true }, requestId);
|
|
9971
|
+
return;
|
|
9972
|
+
}
|
|
9531
9973
|
try {
|
|
9532
9974
|
await triggerCompaction(this.state, this.config, {
|
|
9533
9975
|
blocking: false,
|
|
9534
9976
|
requestId,
|
|
9535
|
-
model: this.opts.model
|
|
9977
|
+
model: this.opts.model,
|
|
9978
|
+
origin: "user"
|
|
9536
9979
|
});
|
|
9537
9980
|
if (!this.running) {
|
|
9538
9981
|
applyPendingSummaries(this.state);
|