@mindstudio-ai/remy 0.1.185 → 0.1.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +116 -115
- package/dist/index.js +134 -131
- package/dist/prompt/compiled/interfaces.md +19 -7
- package/dist/prompt/static/coding.md +2 -2
- package/package.json +1 -1
package/dist/headless.js
CHANGED
|
@@ -2902,6 +2902,82 @@ function acquireBrowserLock() {
|
|
|
2902
2902
|
return wait.then(() => release);
|
|
2903
2903
|
}
|
|
2904
2904
|
|
|
2905
|
+
// src/toolRegistry.ts
|
|
2906
|
+
var log5 = createLogger("tool-registry");
|
|
2907
|
+
var USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
2908
|
+
var ToolRegistry = class {
|
|
2909
|
+
entries = /* @__PURE__ */ new Map();
|
|
2910
|
+
onEvent;
|
|
2911
|
+
register(entry) {
|
|
2912
|
+
this.entries.set(entry.id, entry);
|
|
2913
|
+
}
|
|
2914
|
+
unregister(id) {
|
|
2915
|
+
this.entries.delete(id);
|
|
2916
|
+
}
|
|
2917
|
+
get(id) {
|
|
2918
|
+
return this.entries.get(id);
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Stop a running tool.
|
|
2922
|
+
*
|
|
2923
|
+
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
2924
|
+
* - hard: abort and settle with a generic error
|
|
2925
|
+
*
|
|
2926
|
+
* Returns true if the tool was found and stopped.
|
|
2927
|
+
*/
|
|
2928
|
+
stop(id, mode) {
|
|
2929
|
+
const entry = this.entries.get(id);
|
|
2930
|
+
if (!entry) {
|
|
2931
|
+
return false;
|
|
2932
|
+
}
|
|
2933
|
+
log5.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
2934
|
+
entry.abortController.abort(mode);
|
|
2935
|
+
if (mode === "graceful") {
|
|
2936
|
+
const partial = entry.getPartialResult?.() ?? "";
|
|
2937
|
+
const result = partial ? `[INTERRUPTED]
|
|
2938
|
+
|
|
2939
|
+
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
2940
|
+
entry.settle(result, false);
|
|
2941
|
+
} else {
|
|
2942
|
+
entry.settle(USER_CANCELLED_RESULT, true);
|
|
2943
|
+
}
|
|
2944
|
+
this.onEvent?.({
|
|
2945
|
+
type: "tool_stopped",
|
|
2946
|
+
id: entry.id,
|
|
2947
|
+
name: entry.name,
|
|
2948
|
+
mode,
|
|
2949
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
2950
|
+
});
|
|
2951
|
+
this.entries.delete(id);
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Restart a running tool with the same or patched input.
|
|
2956
|
+
* The original controllable promise stays pending and settles
|
|
2957
|
+
* when the new execution finishes.
|
|
2958
|
+
*
|
|
2959
|
+
* Returns true if the tool was found and restarted.
|
|
2960
|
+
*/
|
|
2961
|
+
restart(id, patchedInput) {
|
|
2962
|
+
const entry = this.entries.get(id);
|
|
2963
|
+
if (!entry) {
|
|
2964
|
+
return false;
|
|
2965
|
+
}
|
|
2966
|
+
log5.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
2967
|
+
entry.abortController.abort("restart");
|
|
2968
|
+
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
2969
|
+
this.onEvent?.({
|
|
2970
|
+
type: "tool_restarted",
|
|
2971
|
+
id: entry.id,
|
|
2972
|
+
name: entry.name,
|
|
2973
|
+
input: newInput,
|
|
2974
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
2975
|
+
});
|
|
2976
|
+
entry.rerun(newInput);
|
|
2977
|
+
return true;
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
|
|
2905
2981
|
// src/statusWatcher.ts
|
|
2906
2982
|
function startStatusWatcher(config) {
|
|
2907
2983
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
@@ -3147,7 +3223,7 @@ ${content}` : attachmentHeader;
|
|
|
3147
3223
|
}
|
|
3148
3224
|
|
|
3149
3225
|
// src/subagents/runner.ts
|
|
3150
|
-
var
|
|
3226
|
+
var log6 = createLogger("sub-agent");
|
|
3151
3227
|
async function runSubAgent(config) {
|
|
3152
3228
|
const {
|
|
3153
3229
|
system,
|
|
@@ -3174,7 +3250,7 @@ async function runSubAgent(config) {
|
|
|
3174
3250
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
3175
3251
|
const agentName = subAgentId || "sub-agent";
|
|
3176
3252
|
const runStart = Date.now();
|
|
3177
|
-
|
|
3253
|
+
log6.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
3178
3254
|
const emit = (e) => {
|
|
3179
3255
|
onEvent({ ...e, parentToolId });
|
|
3180
3256
|
};
|
|
@@ -3209,7 +3285,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3209
3285
|
messages: thisInvocation()
|
|
3210
3286
|
};
|
|
3211
3287
|
}
|
|
3212
|
-
return { text:
|
|
3288
|
+
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
3213
3289
|
}
|
|
3214
3290
|
let lastToolResult = "";
|
|
3215
3291
|
while (true) {
|
|
@@ -3391,7 +3467,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3391
3467
|
...hasArtifacts ? { artifacts } : {}
|
|
3392
3468
|
};
|
|
3393
3469
|
}
|
|
3394
|
-
|
|
3470
|
+
log6.info("Tools executing", {
|
|
3395
3471
|
requestId,
|
|
3396
3472
|
parentToolId,
|
|
3397
3473
|
count: toolCalls.length,
|
|
@@ -3401,7 +3477,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3401
3477
|
const results = await Promise.all(
|
|
3402
3478
|
toolCalls.map(async (tc) => {
|
|
3403
3479
|
if (signal?.aborted) {
|
|
3404
|
-
return { id: tc.id, result:
|
|
3480
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
3405
3481
|
}
|
|
3406
3482
|
let settle;
|
|
3407
3483
|
const resultPromise = new Promise((res) => {
|
|
@@ -3468,7 +3544,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3468
3544
|
run2(tc.input);
|
|
3469
3545
|
const r = await resultPromise;
|
|
3470
3546
|
toolRegistry?.unregister(tc.id);
|
|
3471
|
-
|
|
3547
|
+
log6.info("Tool completed", {
|
|
3472
3548
|
requestId,
|
|
3473
3549
|
parentToolId,
|
|
3474
3550
|
toolCallId: tc.id,
|
|
@@ -3519,7 +3595,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3519
3595
|
const wrapRun = async () => {
|
|
3520
3596
|
try {
|
|
3521
3597
|
const result = await run();
|
|
3522
|
-
|
|
3598
|
+
log6.info("Sub-agent complete", {
|
|
3523
3599
|
requestId,
|
|
3524
3600
|
parentToolId,
|
|
3525
3601
|
agentName,
|
|
@@ -3528,7 +3604,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3528
3604
|
});
|
|
3529
3605
|
return result;
|
|
3530
3606
|
} catch (err) {
|
|
3531
|
-
|
|
3607
|
+
log6.warn("Sub-agent error", {
|
|
3532
3608
|
requestId,
|
|
3533
3609
|
parentToolId,
|
|
3534
3610
|
agentName,
|
|
@@ -3540,7 +3616,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3540
3616
|
if (!background) {
|
|
3541
3617
|
return wrapRun();
|
|
3542
3618
|
}
|
|
3543
|
-
|
|
3619
|
+
log6.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
3544
3620
|
toolRegistry?.register({
|
|
3545
3621
|
id: parentToolId,
|
|
3546
3622
|
name: agentName,
|
|
@@ -3814,7 +3890,7 @@ function resolveModel(surfaceId, models, fallback) {
|
|
|
3814
3890
|
}
|
|
3815
3891
|
|
|
3816
3892
|
// src/subagents/browserAutomation/index.ts
|
|
3817
|
-
var
|
|
3893
|
+
var log7 = createLogger("browser-automation");
|
|
3818
3894
|
async function runBrowserAutomation(task, context) {
|
|
3819
3895
|
const release = await acquireBrowserLock();
|
|
3820
3896
|
try {
|
|
@@ -3906,7 +3982,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
3906
3982
|
}
|
|
3907
3983
|
}
|
|
3908
3984
|
} catch {
|
|
3909
|
-
|
|
3985
|
+
log7.debug("Failed to parse batch analysis result", {
|
|
3910
3986
|
batchResult
|
|
3911
3987
|
});
|
|
3912
3988
|
}
|
|
@@ -5573,7 +5649,7 @@ function executeTool(name, input, context) {
|
|
|
5573
5649
|
}
|
|
5574
5650
|
|
|
5575
5651
|
// src/compaction/trigger.ts
|
|
5576
|
-
var
|
|
5652
|
+
var log8 = createLogger("compaction:trigger");
|
|
5577
5653
|
var pendingSummaries = [];
|
|
5578
5654
|
var inflightCompaction = null;
|
|
5579
5655
|
function getPendingSummaries() {
|
|
@@ -5600,11 +5676,11 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
5600
5676
|
).then((summaries) => {
|
|
5601
5677
|
pendingSummaries.push(...summaries);
|
|
5602
5678
|
listener?.({ type: "complete", requestId });
|
|
5603
|
-
|
|
5679
|
+
log8.info("Compaction complete");
|
|
5604
5680
|
}).catch((err) => {
|
|
5605
5681
|
const message = err.message || "Compaction failed";
|
|
5606
5682
|
listener?.({ type: "complete", error: message, requestId });
|
|
5607
|
-
|
|
5683
|
+
log8.error("Compaction failed", { error: message });
|
|
5608
5684
|
throw err;
|
|
5609
5685
|
}).finally(() => {
|
|
5610
5686
|
inflightCompaction = null;
|
|
@@ -5616,7 +5692,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
5616
5692
|
import fs20 from "fs";
|
|
5617
5693
|
import path10 from "path";
|
|
5618
5694
|
import { createHash } from "crypto";
|
|
5619
|
-
var
|
|
5695
|
+
var log9 = createLogger("brandExtraction");
|
|
5620
5696
|
var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
5621
5697
|
var BRAND_FILE = ".remy-brand.json";
|
|
5622
5698
|
var CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -5624,17 +5700,17 @@ async function runExtraction(apiConfig, model) {
|
|
|
5624
5700
|
const inputHash = computeInputHash();
|
|
5625
5701
|
const cached2 = readCache();
|
|
5626
5702
|
if (cached2 && cached2.inputHash === inputHash) {
|
|
5627
|
-
|
|
5703
|
+
log9.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
5628
5704
|
return null;
|
|
5629
5705
|
}
|
|
5630
|
-
|
|
5706
|
+
log9.info("Extracting brand", { inputHash });
|
|
5631
5707
|
const brand = await extractBrand(apiConfig, model);
|
|
5632
5708
|
if (!brand) {
|
|
5633
|
-
|
|
5709
|
+
log9.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
5634
5710
|
return null;
|
|
5635
5711
|
}
|
|
5636
5712
|
persistBrand(brand, inputHash);
|
|
5637
|
-
|
|
5713
|
+
log9.info("Brand persisted", { inputHash });
|
|
5638
5714
|
return brand;
|
|
5639
5715
|
}
|
|
5640
5716
|
function computeInputHash() {
|
|
@@ -5700,7 +5776,7 @@ function parseFrontmatter3(filePath) {
|
|
|
5700
5776
|
async function extractBrand(apiConfig, model) {
|
|
5701
5777
|
const corpus = buildCorpus();
|
|
5702
5778
|
if (!corpus.trim()) {
|
|
5703
|
-
|
|
5779
|
+
log9.debug("No spec corpus \u2014 emitting empty brand");
|
|
5704
5780
|
return { version: 1 };
|
|
5705
5781
|
}
|
|
5706
5782
|
let responseText = "";
|
|
@@ -5731,17 +5807,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
5731
5807
|
toolNames: []
|
|
5732
5808
|
});
|
|
5733
5809
|
} else if (event.type === "error") {
|
|
5734
|
-
|
|
5810
|
+
log9.error("Brand extraction stream error", { error: event.error });
|
|
5735
5811
|
return null;
|
|
5736
5812
|
}
|
|
5737
5813
|
}
|
|
5738
5814
|
} catch (err) {
|
|
5739
|
-
|
|
5815
|
+
log9.error("Brand extraction threw", { error: err?.message });
|
|
5740
5816
|
return null;
|
|
5741
5817
|
}
|
|
5742
5818
|
const parsed = parseJsonResponse(responseText);
|
|
5743
5819
|
if (!parsed) {
|
|
5744
|
-
|
|
5820
|
+
log9.warn("Brand extraction returned unparseable JSON", {
|
|
5745
5821
|
preview: responseText.slice(0, 200)
|
|
5746
5822
|
});
|
|
5747
5823
|
return null;
|
|
@@ -5883,7 +5959,7 @@ function readCache() {
|
|
|
5883
5959
|
}
|
|
5884
5960
|
|
|
5885
5961
|
// src/brandExtraction/trigger.ts
|
|
5886
|
-
var
|
|
5962
|
+
var log10 = createLogger("brandExtraction:trigger");
|
|
5887
5963
|
var inflight = false;
|
|
5888
5964
|
var dirty = false;
|
|
5889
5965
|
function triggerBrandExtraction(apiConfig, model) {
|
|
@@ -5893,7 +5969,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
5893
5969
|
}
|
|
5894
5970
|
inflight = true;
|
|
5895
5971
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
5896
|
-
|
|
5972
|
+
log10.error("Brand extraction failed", { error: err?.message });
|
|
5897
5973
|
}).finally(() => {
|
|
5898
5974
|
inflight = false;
|
|
5899
5975
|
if (dirty) {
|
|
@@ -5906,7 +5982,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
5906
5982
|
// src/session.ts
|
|
5907
5983
|
import fs21 from "fs";
|
|
5908
5984
|
import path11 from "path";
|
|
5909
|
-
var
|
|
5985
|
+
var log11 = createLogger("session");
|
|
5910
5986
|
var SESSION_FILE = ".remy-session.json";
|
|
5911
5987
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
5912
5988
|
function loadSession(state) {
|
|
@@ -5918,7 +5994,7 @@ function loadSession(state) {
|
|
|
5918
5994
|
}
|
|
5919
5995
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
5920
5996
|
state.messages = sanitizeMessages(data.messages);
|
|
5921
|
-
|
|
5997
|
+
log11.info("Session loaded", {
|
|
5922
5998
|
messageCount: state.messages.length,
|
|
5923
5999
|
...state.models && { models: state.models }
|
|
5924
6000
|
});
|
|
@@ -5971,9 +6047,9 @@ function saveSession(state) {
|
|
|
5971
6047
|
payload.models = state.models;
|
|
5972
6048
|
}
|
|
5973
6049
|
fs21.writeFileSync(SESSION_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
5974
|
-
|
|
6050
|
+
log11.info("Session saved", { messageCount: state.messages.length });
|
|
5975
6051
|
} catch (err) {
|
|
5976
|
-
|
|
6052
|
+
log11.warn("Session save failed", { error: err.message });
|
|
5977
6053
|
}
|
|
5978
6054
|
}
|
|
5979
6055
|
function clearSession(state) {
|
|
@@ -5984,10 +6060,10 @@ function clearSession(state) {
|
|
|
5984
6060
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5985
6061
|
const dest = path11.join(ARCHIVE_DIR, `cleared-${ts}.json`);
|
|
5986
6062
|
fs21.renameSync(SESSION_FILE, dest);
|
|
5987
|
-
|
|
6063
|
+
log11.info("Session archived on clear", { dest });
|
|
5988
6064
|
}
|
|
5989
6065
|
} catch (err) {
|
|
5990
|
-
|
|
6066
|
+
log11.warn("Session archive on clear failed, deleting instead", {
|
|
5991
6067
|
error: err.message
|
|
5992
6068
|
});
|
|
5993
6069
|
try {
|
|
@@ -6191,7 +6267,7 @@ function friendlyError(raw) {
|
|
|
6191
6267
|
}
|
|
6192
6268
|
|
|
6193
6269
|
// src/agent.ts
|
|
6194
|
-
var
|
|
6270
|
+
var log12 = createLogger("agent");
|
|
6195
6271
|
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
6196
6272
|
function getTextContent(blocks) {
|
|
6197
6273
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
@@ -6240,7 +6316,7 @@ async function runTurn(params) {
|
|
|
6240
6316
|
} = params;
|
|
6241
6317
|
const tools2 = getToolDefinitions(onboardingState);
|
|
6242
6318
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
6243
|
-
|
|
6319
|
+
log12.info("Turn started", {
|
|
6244
6320
|
requestId,
|
|
6245
6321
|
model,
|
|
6246
6322
|
toolCount: tools2.length,
|
|
@@ -6493,7 +6569,7 @@ async function runTurn(params) {
|
|
|
6493
6569
|
const tool = getToolByName(event.name);
|
|
6494
6570
|
const wasStreamed = acc?.started ?? false;
|
|
6495
6571
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
6496
|
-
|
|
6572
|
+
log12.info("Tool received", {
|
|
6497
6573
|
requestId,
|
|
6498
6574
|
toolCallId: event.id,
|
|
6499
6575
|
name: event.name
|
|
@@ -6607,7 +6683,7 @@ async function runTurn(params) {
|
|
|
6607
6683
|
});
|
|
6608
6684
|
return;
|
|
6609
6685
|
}
|
|
6610
|
-
|
|
6686
|
+
log12.info("Tools executing", {
|
|
6611
6687
|
requestId,
|
|
6612
6688
|
count: toolCalls.length,
|
|
6613
6689
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -6627,7 +6703,7 @@ async function runTurn(params) {
|
|
|
6627
6703
|
const results = await Promise.all(
|
|
6628
6704
|
toolCalls.map(async (tc) => {
|
|
6629
6705
|
if (signal?.aborted) {
|
|
6630
|
-
return { id: tc.id, result:
|
|
6706
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
6631
6707
|
}
|
|
6632
6708
|
const toolStart = Date.now();
|
|
6633
6709
|
let settle;
|
|
@@ -6646,7 +6722,7 @@ async function runTurn(params) {
|
|
|
6646
6722
|
};
|
|
6647
6723
|
const cascadeAbort = () => {
|
|
6648
6724
|
toolAbort.abort();
|
|
6649
|
-
safeSettle(
|
|
6725
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
6650
6726
|
};
|
|
6651
6727
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
6652
6728
|
const run = async (input) => {
|
|
@@ -6654,7 +6730,7 @@ async function runTurn(params) {
|
|
|
6654
6730
|
let result;
|
|
6655
6731
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
6656
6732
|
saveSession(state);
|
|
6657
|
-
|
|
6733
|
+
log12.info("Waiting for external tool result", {
|
|
6658
6734
|
requestId,
|
|
6659
6735
|
toolCallId: tc.id,
|
|
6660
6736
|
name: tc.name
|
|
@@ -6722,7 +6798,7 @@ async function runTurn(params) {
|
|
|
6722
6798
|
if (!tc.input.background) {
|
|
6723
6799
|
toolRegistry?.unregister(tc.id);
|
|
6724
6800
|
}
|
|
6725
|
-
|
|
6801
|
+
log12.info("Tool completed", {
|
|
6726
6802
|
requestId,
|
|
6727
6803
|
toolCallId: tc.id,
|
|
6728
6804
|
name: tc.name,
|
|
@@ -6782,81 +6858,6 @@ async function runTurn(params) {
|
|
|
6782
6858
|
}
|
|
6783
6859
|
}
|
|
6784
6860
|
|
|
6785
|
-
// src/toolRegistry.ts
|
|
6786
|
-
var log12 = createLogger("tool-registry");
|
|
6787
|
-
var ToolRegistry = class {
|
|
6788
|
-
entries = /* @__PURE__ */ new Map();
|
|
6789
|
-
onEvent;
|
|
6790
|
-
register(entry) {
|
|
6791
|
-
this.entries.set(entry.id, entry);
|
|
6792
|
-
}
|
|
6793
|
-
unregister(id) {
|
|
6794
|
-
this.entries.delete(id);
|
|
6795
|
-
}
|
|
6796
|
-
get(id) {
|
|
6797
|
-
return this.entries.get(id);
|
|
6798
|
-
}
|
|
6799
|
-
/**
|
|
6800
|
-
* Stop a running tool.
|
|
6801
|
-
*
|
|
6802
|
-
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
6803
|
-
* - hard: abort and settle with a generic error
|
|
6804
|
-
*
|
|
6805
|
-
* Returns true if the tool was found and stopped.
|
|
6806
|
-
*/
|
|
6807
|
-
stop(id, mode) {
|
|
6808
|
-
const entry = this.entries.get(id);
|
|
6809
|
-
if (!entry) {
|
|
6810
|
-
return false;
|
|
6811
|
-
}
|
|
6812
|
-
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6813
|
-
entry.abortController.abort(mode);
|
|
6814
|
-
if (mode === "graceful") {
|
|
6815
|
-
const partial = entry.getPartialResult?.() ?? "";
|
|
6816
|
-
const result = partial ? `[INTERRUPTED]
|
|
6817
|
-
|
|
6818
|
-
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
6819
|
-
entry.settle(result, false);
|
|
6820
|
-
} else {
|
|
6821
|
-
entry.settle("Error: tool was cancelled", true);
|
|
6822
|
-
}
|
|
6823
|
-
this.onEvent?.({
|
|
6824
|
-
type: "tool_stopped",
|
|
6825
|
-
id: entry.id,
|
|
6826
|
-
name: entry.name,
|
|
6827
|
-
mode,
|
|
6828
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
6829
|
-
});
|
|
6830
|
-
this.entries.delete(id);
|
|
6831
|
-
return true;
|
|
6832
|
-
}
|
|
6833
|
-
/**
|
|
6834
|
-
* Restart a running tool with the same or patched input.
|
|
6835
|
-
* The original controllable promise stays pending and settles
|
|
6836
|
-
* when the new execution finishes.
|
|
6837
|
-
*
|
|
6838
|
-
* Returns true if the tool was found and restarted.
|
|
6839
|
-
*/
|
|
6840
|
-
restart(id, patchedInput) {
|
|
6841
|
-
const entry = this.entries.get(id);
|
|
6842
|
-
if (!entry) {
|
|
6843
|
-
return false;
|
|
6844
|
-
}
|
|
6845
|
-
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6846
|
-
entry.abortController.abort("restart");
|
|
6847
|
-
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6848
|
-
this.onEvent?.({
|
|
6849
|
-
type: "tool_restarted",
|
|
6850
|
-
id: entry.id,
|
|
6851
|
-
name: entry.name,
|
|
6852
|
-
input: newInput,
|
|
6853
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
6854
|
-
});
|
|
6855
|
-
entry.rerun(newInput);
|
|
6856
|
-
return true;
|
|
6857
|
-
}
|
|
6858
|
-
};
|
|
6859
|
-
|
|
6860
6861
|
// src/headless/attachments.ts
|
|
6861
6862
|
import { mkdirSync, existsSync } from "fs";
|
|
6862
6863
|
import { writeFile } from "fs/promises";
|
|
@@ -7805,7 +7806,7 @@ var HeadlessSession = class {
|
|
|
7805
7806
|
}
|
|
7806
7807
|
for (const [id, pending] of this.pendingTools) {
|
|
7807
7808
|
clearTimeout(pending.timeout);
|
|
7808
|
-
pending.resolve(
|
|
7809
|
+
pending.resolve(USER_CANCELLED_RESULT);
|
|
7809
7810
|
this.pendingTools.delete(id);
|
|
7810
7811
|
}
|
|
7811
7812
|
return this.queue.drain();
|
package/dist/index.js
CHANGED
|
@@ -3320,6 +3320,89 @@ var init_browserLock = __esm({
|
|
|
3320
3320
|
}
|
|
3321
3321
|
});
|
|
3322
3322
|
|
|
3323
|
+
// src/toolRegistry.ts
|
|
3324
|
+
var log5, USER_CANCELLED_RESULT, ToolRegistry;
|
|
3325
|
+
var init_toolRegistry = __esm({
|
|
3326
|
+
"src/toolRegistry.ts"() {
|
|
3327
|
+
"use strict";
|
|
3328
|
+
init_logger();
|
|
3329
|
+
log5 = createLogger("tool-registry");
|
|
3330
|
+
USER_CANCELLED_RESULT = "[USER CANCELLED] The user manually cancelled this tool. Do not retry it automatically \u2014 wait for the user\u2019s next message for direction.";
|
|
3331
|
+
ToolRegistry = class {
|
|
3332
|
+
entries = /* @__PURE__ */ new Map();
|
|
3333
|
+
onEvent;
|
|
3334
|
+
register(entry) {
|
|
3335
|
+
this.entries.set(entry.id, entry);
|
|
3336
|
+
}
|
|
3337
|
+
unregister(id) {
|
|
3338
|
+
this.entries.delete(id);
|
|
3339
|
+
}
|
|
3340
|
+
get(id) {
|
|
3341
|
+
return this.entries.get(id);
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Stop a running tool.
|
|
3345
|
+
*
|
|
3346
|
+
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
3347
|
+
* - hard: abort and settle with a generic error
|
|
3348
|
+
*
|
|
3349
|
+
* Returns true if the tool was found and stopped.
|
|
3350
|
+
*/
|
|
3351
|
+
stop(id, mode) {
|
|
3352
|
+
const entry = this.entries.get(id);
|
|
3353
|
+
if (!entry) {
|
|
3354
|
+
return false;
|
|
3355
|
+
}
|
|
3356
|
+
log5.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
3357
|
+
entry.abortController.abort(mode);
|
|
3358
|
+
if (mode === "graceful") {
|
|
3359
|
+
const partial = entry.getPartialResult?.() ?? "";
|
|
3360
|
+
const result = partial ? `[INTERRUPTED]
|
|
3361
|
+
|
|
3362
|
+
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
3363
|
+
entry.settle(result, false);
|
|
3364
|
+
} else {
|
|
3365
|
+
entry.settle(USER_CANCELLED_RESULT, true);
|
|
3366
|
+
}
|
|
3367
|
+
this.onEvent?.({
|
|
3368
|
+
type: "tool_stopped",
|
|
3369
|
+
id: entry.id,
|
|
3370
|
+
name: entry.name,
|
|
3371
|
+
mode,
|
|
3372
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
3373
|
+
});
|
|
3374
|
+
this.entries.delete(id);
|
|
3375
|
+
return true;
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* Restart a running tool with the same or patched input.
|
|
3379
|
+
* The original controllable promise stays pending and settles
|
|
3380
|
+
* when the new execution finishes.
|
|
3381
|
+
*
|
|
3382
|
+
* Returns true if the tool was found and restarted.
|
|
3383
|
+
*/
|
|
3384
|
+
restart(id, patchedInput) {
|
|
3385
|
+
const entry = this.entries.get(id);
|
|
3386
|
+
if (!entry) {
|
|
3387
|
+
return false;
|
|
3388
|
+
}
|
|
3389
|
+
log5.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
3390
|
+
entry.abortController.abort("restart");
|
|
3391
|
+
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
3392
|
+
this.onEvent?.({
|
|
3393
|
+
type: "tool_restarted",
|
|
3394
|
+
id: entry.id,
|
|
3395
|
+
name: entry.name,
|
|
3396
|
+
input: newInput,
|
|
3397
|
+
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
3398
|
+
});
|
|
3399
|
+
entry.rerun(newInput);
|
|
3400
|
+
return true;
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3323
3406
|
// src/statusWatcher.ts
|
|
3324
3407
|
function startStatusWatcher(config) {
|
|
3325
3408
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
@@ -3607,7 +3690,7 @@ async function runSubAgent(config) {
|
|
|
3607
3690
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
3608
3691
|
const agentName = subAgentId || "sub-agent";
|
|
3609
3692
|
const runStart = Date.now();
|
|
3610
|
-
|
|
3693
|
+
log6.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
3611
3694
|
const emit = (e) => {
|
|
3612
3695
|
onEvent({ ...e, parentToolId });
|
|
3613
3696
|
};
|
|
@@ -3642,7 +3725,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3642
3725
|
messages: thisInvocation()
|
|
3643
3726
|
};
|
|
3644
3727
|
}
|
|
3645
|
-
return { text:
|
|
3728
|
+
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
3646
3729
|
}
|
|
3647
3730
|
let lastToolResult = "";
|
|
3648
3731
|
while (true) {
|
|
@@ -3824,7 +3907,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3824
3907
|
...hasArtifacts ? { artifacts } : {}
|
|
3825
3908
|
};
|
|
3826
3909
|
}
|
|
3827
|
-
|
|
3910
|
+
log6.info("Tools executing", {
|
|
3828
3911
|
requestId,
|
|
3829
3912
|
parentToolId,
|
|
3830
3913
|
count: toolCalls.length,
|
|
@@ -3834,7 +3917,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3834
3917
|
const results = await Promise.all(
|
|
3835
3918
|
toolCalls.map(async (tc) => {
|
|
3836
3919
|
if (signal?.aborted) {
|
|
3837
|
-
return { id: tc.id, result:
|
|
3920
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
3838
3921
|
}
|
|
3839
3922
|
let settle;
|
|
3840
3923
|
const resultPromise = new Promise((res) => {
|
|
@@ -3901,7 +3984,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3901
3984
|
run2(tc.input);
|
|
3902
3985
|
const r = await resultPromise;
|
|
3903
3986
|
toolRegistry?.unregister(tc.id);
|
|
3904
|
-
|
|
3987
|
+
log6.info("Tool completed", {
|
|
3905
3988
|
requestId,
|
|
3906
3989
|
parentToolId,
|
|
3907
3990
|
toolCallId: tc.id,
|
|
@@ -3952,7 +4035,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3952
4035
|
const wrapRun = async () => {
|
|
3953
4036
|
try {
|
|
3954
4037
|
const result = await run();
|
|
3955
|
-
|
|
4038
|
+
log6.info("Sub-agent complete", {
|
|
3956
4039
|
requestId,
|
|
3957
4040
|
parentToolId,
|
|
3958
4041
|
agentName,
|
|
@@ -3961,7 +4044,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3961
4044
|
});
|
|
3962
4045
|
return result;
|
|
3963
4046
|
} catch (err) {
|
|
3964
|
-
|
|
4047
|
+
log6.warn("Sub-agent error", {
|
|
3965
4048
|
requestId,
|
|
3966
4049
|
parentToolId,
|
|
3967
4050
|
agentName,
|
|
@@ -3973,7 +4056,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3973
4056
|
if (!background) {
|
|
3974
4057
|
return wrapRun();
|
|
3975
4058
|
}
|
|
3976
|
-
|
|
4059
|
+
log6.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
3977
4060
|
toolRegistry?.register({
|
|
3978
4061
|
id: parentToolId,
|
|
3979
4062
|
name: agentName,
|
|
@@ -4000,16 +4083,17 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
4000
4083
|
});
|
|
4001
4084
|
return { text: ack, messages: [], backgrounded: true };
|
|
4002
4085
|
}
|
|
4003
|
-
var
|
|
4086
|
+
var log6;
|
|
4004
4087
|
var init_runner = __esm({
|
|
4005
4088
|
"src/subagents/runner.ts"() {
|
|
4006
4089
|
"use strict";
|
|
4007
4090
|
init_api();
|
|
4008
4091
|
init_logger();
|
|
4009
4092
|
init_usageLedger();
|
|
4093
|
+
init_toolRegistry();
|
|
4010
4094
|
init_statusWatcher();
|
|
4011
4095
|
init_cleanMessages();
|
|
4012
|
-
|
|
4096
|
+
log6 = createLogger("sub-agent");
|
|
4013
4097
|
}
|
|
4014
4098
|
});
|
|
4015
4099
|
|
|
@@ -4269,7 +4353,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
4269
4353
|
}
|
|
4270
4354
|
}
|
|
4271
4355
|
} catch {
|
|
4272
|
-
|
|
4356
|
+
log7.debug("Failed to parse batch analysis result", {
|
|
4273
4357
|
batchResult
|
|
4274
4358
|
});
|
|
4275
4359
|
}
|
|
@@ -4293,7 +4377,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
4293
4377
|
release();
|
|
4294
4378
|
}
|
|
4295
4379
|
}
|
|
4296
|
-
var
|
|
4380
|
+
var log7, browserAutomationTool;
|
|
4297
4381
|
var init_browserAutomation = __esm({
|
|
4298
4382
|
"src/subagents/browserAutomation/index.ts"() {
|
|
4299
4383
|
"use strict";
|
|
@@ -4306,7 +4390,7 @@ var init_browserAutomation = __esm({
|
|
|
4306
4390
|
init_runMindstudioCli();
|
|
4307
4391
|
init_surfaces();
|
|
4308
4392
|
init_logger();
|
|
4309
|
-
|
|
4393
|
+
log7 = createLogger("browser-automation");
|
|
4310
4394
|
browserAutomationTool = {
|
|
4311
4395
|
clearable: true,
|
|
4312
4396
|
definition: {
|
|
@@ -6237,7 +6321,7 @@ function loadSession(state) {
|
|
|
6237
6321
|
}
|
|
6238
6322
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
6239
6323
|
state.messages = sanitizeMessages(data.messages);
|
|
6240
|
-
|
|
6324
|
+
log8.info("Session loaded", {
|
|
6241
6325
|
messageCount: state.messages.length,
|
|
6242
6326
|
...state.models && { models: state.models }
|
|
6243
6327
|
});
|
|
@@ -6290,9 +6374,9 @@ function saveSession(state) {
|
|
|
6290
6374
|
payload.models = state.models;
|
|
6291
6375
|
}
|
|
6292
6376
|
fs19.writeFileSync(SESSION_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
6293
|
-
|
|
6377
|
+
log8.info("Session saved", { messageCount: state.messages.length });
|
|
6294
6378
|
} catch (err) {
|
|
6295
|
-
|
|
6379
|
+
log8.warn("Session save failed", { error: err.message });
|
|
6296
6380
|
}
|
|
6297
6381
|
}
|
|
6298
6382
|
function clearSession(state) {
|
|
@@ -6303,10 +6387,10 @@ function clearSession(state) {
|
|
|
6303
6387
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6304
6388
|
const dest = path9.join(ARCHIVE_DIR, `cleared-${ts}.json`);
|
|
6305
6389
|
fs19.renameSync(SESSION_FILE, dest);
|
|
6306
|
-
|
|
6390
|
+
log8.info("Session archived on clear", { dest });
|
|
6307
6391
|
}
|
|
6308
6392
|
} catch (err) {
|
|
6309
|
-
|
|
6393
|
+
log8.warn("Session archive on clear failed, deleting instead", {
|
|
6310
6394
|
error: err.message
|
|
6311
6395
|
});
|
|
6312
6396
|
try {
|
|
@@ -6315,12 +6399,12 @@ function clearSession(state) {
|
|
|
6315
6399
|
}
|
|
6316
6400
|
}
|
|
6317
6401
|
}
|
|
6318
|
-
var
|
|
6402
|
+
var log8, SESSION_FILE, ARCHIVE_DIR;
|
|
6319
6403
|
var init_session = __esm({
|
|
6320
6404
|
"src/session.ts"() {
|
|
6321
6405
|
"use strict";
|
|
6322
6406
|
init_logger();
|
|
6323
|
-
|
|
6407
|
+
log8 = createLogger("session");
|
|
6324
6408
|
SESSION_FILE = ".remy-session.json";
|
|
6325
6409
|
ARCHIVE_DIR = ".logs/sessions";
|
|
6326
6410
|
}
|
|
@@ -6539,17 +6623,17 @@ async function runExtraction(apiConfig, model) {
|
|
|
6539
6623
|
const inputHash = computeInputHash();
|
|
6540
6624
|
const cached2 = readCache();
|
|
6541
6625
|
if (cached2 && cached2.inputHash === inputHash) {
|
|
6542
|
-
|
|
6626
|
+
log9.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
6543
6627
|
return null;
|
|
6544
6628
|
}
|
|
6545
|
-
|
|
6629
|
+
log9.info("Extracting brand", { inputHash });
|
|
6546
6630
|
const brand = await extractBrand(apiConfig, model);
|
|
6547
6631
|
if (!brand) {
|
|
6548
|
-
|
|
6632
|
+
log9.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
6549
6633
|
return null;
|
|
6550
6634
|
}
|
|
6551
6635
|
persistBrand(brand, inputHash);
|
|
6552
|
-
|
|
6636
|
+
log9.info("Brand persisted", { inputHash });
|
|
6553
6637
|
return brand;
|
|
6554
6638
|
}
|
|
6555
6639
|
function computeInputHash() {
|
|
@@ -6615,7 +6699,7 @@ function parseFrontmatter3(filePath) {
|
|
|
6615
6699
|
async function extractBrand(apiConfig, model) {
|
|
6616
6700
|
const corpus = buildCorpus();
|
|
6617
6701
|
if (!corpus.trim()) {
|
|
6618
|
-
|
|
6702
|
+
log9.debug("No spec corpus \u2014 emitting empty brand");
|
|
6619
6703
|
return { version: 1 };
|
|
6620
6704
|
}
|
|
6621
6705
|
let responseText = "";
|
|
@@ -6646,17 +6730,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
6646
6730
|
toolNames: []
|
|
6647
6731
|
});
|
|
6648
6732
|
} else if (event.type === "error") {
|
|
6649
|
-
|
|
6733
|
+
log9.error("Brand extraction stream error", { error: event.error });
|
|
6650
6734
|
return null;
|
|
6651
6735
|
}
|
|
6652
6736
|
}
|
|
6653
6737
|
} catch (err) {
|
|
6654
|
-
|
|
6738
|
+
log9.error("Brand extraction threw", { error: err?.message });
|
|
6655
6739
|
return null;
|
|
6656
6740
|
}
|
|
6657
6741
|
const parsed = parseJsonResponse(responseText);
|
|
6658
6742
|
if (!parsed) {
|
|
6659
|
-
|
|
6743
|
+
log9.warn("Brand extraction returned unparseable JSON", {
|
|
6660
6744
|
preview: responseText.slice(0, 200)
|
|
6661
6745
|
});
|
|
6662
6746
|
return null;
|
|
@@ -6796,7 +6880,7 @@ function readCache() {
|
|
|
6796
6880
|
return null;
|
|
6797
6881
|
}
|
|
6798
6882
|
}
|
|
6799
|
-
var
|
|
6883
|
+
var log9, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE;
|
|
6800
6884
|
var init_brandExtraction = __esm({
|
|
6801
6885
|
"src/brandExtraction/index.ts"() {
|
|
6802
6886
|
"use strict";
|
|
@@ -6804,7 +6888,7 @@ var init_brandExtraction = __esm({
|
|
|
6804
6888
|
init_assets();
|
|
6805
6889
|
init_logger();
|
|
6806
6890
|
init_usageLedger();
|
|
6807
|
-
|
|
6891
|
+
log9 = createLogger("brandExtraction");
|
|
6808
6892
|
EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
6809
6893
|
BRAND_FILE = ".remy-brand.json";
|
|
6810
6894
|
CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -6819,7 +6903,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
6819
6903
|
}
|
|
6820
6904
|
inflight = true;
|
|
6821
6905
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
6822
|
-
|
|
6906
|
+
log10.error("Brand extraction failed", { error: err?.message });
|
|
6823
6907
|
}).finally(() => {
|
|
6824
6908
|
inflight = false;
|
|
6825
6909
|
if (dirty) {
|
|
@@ -6828,13 +6912,13 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
6828
6912
|
}
|
|
6829
6913
|
});
|
|
6830
6914
|
}
|
|
6831
|
-
var
|
|
6915
|
+
var log10, inflight, dirty;
|
|
6832
6916
|
var init_trigger2 = __esm({
|
|
6833
6917
|
"src/brandExtraction/trigger.ts"() {
|
|
6834
6918
|
"use strict";
|
|
6835
6919
|
init_brandExtraction();
|
|
6836
6920
|
init_logger();
|
|
6837
|
-
|
|
6921
|
+
log10 = createLogger("brandExtraction:trigger");
|
|
6838
6922
|
inflight = false;
|
|
6839
6923
|
dirty = false;
|
|
6840
6924
|
}
|
|
@@ -6872,7 +6956,7 @@ async function runTurn(params) {
|
|
|
6872
6956
|
} = params;
|
|
6873
6957
|
const tools2 = getToolDefinitions(onboardingState);
|
|
6874
6958
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
6875
|
-
|
|
6959
|
+
log11.info("Turn started", {
|
|
6876
6960
|
requestId,
|
|
6877
6961
|
model,
|
|
6878
6962
|
toolCount: tools2.length,
|
|
@@ -7125,7 +7209,7 @@ async function runTurn(params) {
|
|
|
7125
7209
|
const tool = getToolByName(event.name);
|
|
7126
7210
|
const wasStreamed = acc?.started ?? false;
|
|
7127
7211
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
7128
|
-
|
|
7212
|
+
log11.info("Tool received", {
|
|
7129
7213
|
requestId,
|
|
7130
7214
|
toolCallId: event.id,
|
|
7131
7215
|
name: event.name
|
|
@@ -7239,7 +7323,7 @@ async function runTurn(params) {
|
|
|
7239
7323
|
});
|
|
7240
7324
|
return;
|
|
7241
7325
|
}
|
|
7242
|
-
|
|
7326
|
+
log11.info("Tools executing", {
|
|
7243
7327
|
requestId,
|
|
7244
7328
|
count: toolCalls.length,
|
|
7245
7329
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -7259,7 +7343,7 @@ async function runTurn(params) {
|
|
|
7259
7343
|
const results = await Promise.all(
|
|
7260
7344
|
toolCalls.map(async (tc) => {
|
|
7261
7345
|
if (signal?.aborted) {
|
|
7262
|
-
return { id: tc.id, result:
|
|
7346
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
7263
7347
|
}
|
|
7264
7348
|
const toolStart = Date.now();
|
|
7265
7349
|
let settle;
|
|
@@ -7278,7 +7362,7 @@ async function runTurn(params) {
|
|
|
7278
7362
|
};
|
|
7279
7363
|
const cascadeAbort = () => {
|
|
7280
7364
|
toolAbort.abort();
|
|
7281
|
-
safeSettle(
|
|
7365
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
7282
7366
|
};
|
|
7283
7367
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
7284
7368
|
const run = async (input) => {
|
|
@@ -7286,7 +7370,7 @@ async function runTurn(params) {
|
|
|
7286
7370
|
let result;
|
|
7287
7371
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
7288
7372
|
saveSession(state);
|
|
7289
|
-
|
|
7373
|
+
log11.info("Waiting for external tool result", {
|
|
7290
7374
|
requestId,
|
|
7291
7375
|
toolCallId: tc.id,
|
|
7292
7376
|
name: tc.name
|
|
@@ -7354,7 +7438,7 @@ async function runTurn(params) {
|
|
|
7354
7438
|
if (!tc.input.background) {
|
|
7355
7439
|
toolRegistry?.unregister(tc.id);
|
|
7356
7440
|
}
|
|
7357
|
-
|
|
7441
|
+
log11.info("Tool completed", {
|
|
7358
7442
|
requestId,
|
|
7359
7443
|
toolCallId: tc.id,
|
|
7360
7444
|
name: tc.name,
|
|
@@ -7413,7 +7497,7 @@ async function runTurn(params) {
|
|
|
7413
7497
|
}
|
|
7414
7498
|
}
|
|
7415
7499
|
}
|
|
7416
|
-
var
|
|
7500
|
+
var log11, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
7417
7501
|
var init_agent = __esm({
|
|
7418
7502
|
"src/agent.ts"() {
|
|
7419
7503
|
"use strict";
|
|
@@ -7430,7 +7514,8 @@ var init_agent = __esm({
|
|
|
7430
7514
|
init_sentinel();
|
|
7431
7515
|
init_trigger2();
|
|
7432
7516
|
init_surfaces();
|
|
7433
|
-
|
|
7517
|
+
init_toolRegistry();
|
|
7518
|
+
log11 = createLogger("agent");
|
|
7434
7519
|
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
7435
7520
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
7436
7521
|
"promptUser",
|
|
@@ -7458,10 +7543,10 @@ import os from "os";
|
|
|
7458
7543
|
function loadConfigFile() {
|
|
7459
7544
|
try {
|
|
7460
7545
|
const raw = fs21.readFileSync(CONFIG_PATH, "utf-8");
|
|
7461
|
-
|
|
7546
|
+
log12.debug("Loaded config file", { path: CONFIG_PATH });
|
|
7462
7547
|
return JSON.parse(raw);
|
|
7463
7548
|
} catch (err) {
|
|
7464
|
-
|
|
7549
|
+
log12.debug("No config file found", {
|
|
7465
7550
|
path: CONFIG_PATH,
|
|
7466
7551
|
error: err.message
|
|
7467
7552
|
});
|
|
@@ -7476,13 +7561,13 @@ function resolveConfig(flags2) {
|
|
|
7476
7561
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
7477
7562
|
const appId = process.env.MINDSTUDIO_APP_ID || void 0;
|
|
7478
7563
|
if (!apiKey) {
|
|
7479
|
-
|
|
7564
|
+
log12.error("No API key found");
|
|
7480
7565
|
throw new Error(
|
|
7481
7566
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
7482
7567
|
);
|
|
7483
7568
|
}
|
|
7484
7569
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
7485
|
-
|
|
7570
|
+
log12.info("Config resolved", {
|
|
7486
7571
|
baseUrl: baseUrl2,
|
|
7487
7572
|
keySource,
|
|
7488
7573
|
environment: activeEnv,
|
|
@@ -7490,12 +7575,12 @@ function resolveConfig(flags2) {
|
|
|
7490
7575
|
});
|
|
7491
7576
|
return { apiKey, baseUrl: baseUrl2, appId };
|
|
7492
7577
|
}
|
|
7493
|
-
var
|
|
7578
|
+
var log12, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
7494
7579
|
var init_config = __esm({
|
|
7495
7580
|
"src/config.ts"() {
|
|
7496
7581
|
"use strict";
|
|
7497
7582
|
init_logger();
|
|
7498
|
-
|
|
7583
|
+
log12 = createLogger("config");
|
|
7499
7584
|
CONFIG_PATH = path11.join(
|
|
7500
7585
|
os.homedir(),
|
|
7501
7586
|
".mindstudio-local-tunnel",
|
|
@@ -7505,88 +7590,6 @@ var init_config = __esm({
|
|
|
7505
7590
|
}
|
|
7506
7591
|
});
|
|
7507
7592
|
|
|
7508
|
-
// src/toolRegistry.ts
|
|
7509
|
-
var log12, ToolRegistry;
|
|
7510
|
-
var init_toolRegistry = __esm({
|
|
7511
|
-
"src/toolRegistry.ts"() {
|
|
7512
|
-
"use strict";
|
|
7513
|
-
init_logger();
|
|
7514
|
-
log12 = createLogger("tool-registry");
|
|
7515
|
-
ToolRegistry = class {
|
|
7516
|
-
entries = /* @__PURE__ */ new Map();
|
|
7517
|
-
onEvent;
|
|
7518
|
-
register(entry) {
|
|
7519
|
-
this.entries.set(entry.id, entry);
|
|
7520
|
-
}
|
|
7521
|
-
unregister(id) {
|
|
7522
|
-
this.entries.delete(id);
|
|
7523
|
-
}
|
|
7524
|
-
get(id) {
|
|
7525
|
-
return this.entries.get(id);
|
|
7526
|
-
}
|
|
7527
|
-
/**
|
|
7528
|
-
* Stop a running tool.
|
|
7529
|
-
*
|
|
7530
|
-
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
7531
|
-
* - hard: abort and settle with a generic error
|
|
7532
|
-
*
|
|
7533
|
-
* Returns true if the tool was found and stopped.
|
|
7534
|
-
*/
|
|
7535
|
-
stop(id, mode) {
|
|
7536
|
-
const entry = this.entries.get(id);
|
|
7537
|
-
if (!entry) {
|
|
7538
|
-
return false;
|
|
7539
|
-
}
|
|
7540
|
-
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
7541
|
-
entry.abortController.abort(mode);
|
|
7542
|
-
if (mode === "graceful") {
|
|
7543
|
-
const partial = entry.getPartialResult?.() ?? "";
|
|
7544
|
-
const result = partial ? `[INTERRUPTED]
|
|
7545
|
-
|
|
7546
|
-
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
7547
|
-
entry.settle(result, false);
|
|
7548
|
-
} else {
|
|
7549
|
-
entry.settle("Error: tool was cancelled", true);
|
|
7550
|
-
}
|
|
7551
|
-
this.onEvent?.({
|
|
7552
|
-
type: "tool_stopped",
|
|
7553
|
-
id: entry.id,
|
|
7554
|
-
name: entry.name,
|
|
7555
|
-
mode,
|
|
7556
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
7557
|
-
});
|
|
7558
|
-
this.entries.delete(id);
|
|
7559
|
-
return true;
|
|
7560
|
-
}
|
|
7561
|
-
/**
|
|
7562
|
-
* Restart a running tool with the same or patched input.
|
|
7563
|
-
* The original controllable promise stays pending and settles
|
|
7564
|
-
* when the new execution finishes.
|
|
7565
|
-
*
|
|
7566
|
-
* Returns true if the tool was found and restarted.
|
|
7567
|
-
*/
|
|
7568
|
-
restart(id, patchedInput) {
|
|
7569
|
-
const entry = this.entries.get(id);
|
|
7570
|
-
if (!entry) {
|
|
7571
|
-
return false;
|
|
7572
|
-
}
|
|
7573
|
-
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
7574
|
-
entry.abortController.abort("restart");
|
|
7575
|
-
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
7576
|
-
this.onEvent?.({
|
|
7577
|
-
type: "tool_restarted",
|
|
7578
|
-
id: entry.id,
|
|
7579
|
-
name: entry.name,
|
|
7580
|
-
input: newInput,
|
|
7581
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
7582
|
-
});
|
|
7583
|
-
entry.rerun(newInput);
|
|
7584
|
-
return true;
|
|
7585
|
-
}
|
|
7586
|
-
};
|
|
7587
|
-
}
|
|
7588
|
-
});
|
|
7589
|
-
|
|
7590
7593
|
// src/headless/attachments.ts
|
|
7591
7594
|
import { mkdirSync, existsSync } from "fs";
|
|
7592
7595
|
import { writeFile } from "fs/promises";
|
|
@@ -8594,7 +8597,7 @@ var init_headless = __esm({
|
|
|
8594
8597
|
}
|
|
8595
8598
|
for (const [id, pending] of this.pendingTools) {
|
|
8596
8599
|
clearTimeout(pending.timeout);
|
|
8597
|
-
pending.resolve(
|
|
8600
|
+
pending.resolve(USER_CANCELLED_RESULT);
|
|
8598
8601
|
this.pendingTools.delete(id);
|
|
8599
8602
|
}
|
|
8600
8603
|
return this.queue.drain();
|
|
@@ -344,7 +344,19 @@ Accepts any HTTP method. The method receives `{ method, headers, query, body }`
|
|
|
344
344
|
|
|
345
345
|
## Email
|
|
346
346
|
|
|
347
|
-
Inbound email triggers.
|
|
347
|
+
Inbound email triggers. Each app has one email-handler method; the platform routes all inbound mail destined for the app — across any of its address tiers — to that method.
|
|
348
|
+
|
|
349
|
+
### Address tiers
|
|
350
|
+
|
|
351
|
+
Three tiers, all delivered to the same handler method. The new tiers are catchall (no localpart registration); the legacy tier is specific-localpart and frozen for new apps.
|
|
352
|
+
|
|
353
|
+
| Tier | Address | How it's set up |
|
|
354
|
+
|---|---|---|
|
|
355
|
+
| Platform subdomain (default) | `*@<custom_subdomain>.madewithremy.com` | Automatic the moment the app has a `custom_subdomain` set. Every address on that subdomain delivers to the handler. |
|
|
356
|
+
| Custom domain | `*@<their-domain>` | The user adds a domain in the dashboard's email-domains settings and points one MX record at `mx.msagent.ai`. Not something the agent provisions. |
|
|
357
|
+
| Legacy `mindstudio-hooks.com` | `<name>@mindstudio-hooks.com` | Existing apps only — frozen for new apps. Don't recommend it; treat as read-only history. |
|
|
358
|
+
|
|
359
|
+
Because the new tiers are catchall, `to` carries an arbitrary localpart. Methods that need to branch on it should read `input.to` (e.g. `if (input.to.startsWith('support@')) ...`).
|
|
348
360
|
|
|
349
361
|
### Config (`interface.json`)
|
|
350
362
|
|
|
@@ -357,27 +369,27 @@ Inbound email triggers. An app can register one inbound address that routes all
|
|
|
357
369
|
}
|
|
358
370
|
```
|
|
359
371
|
|
|
360
|
-
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com` wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before the method runs.
|
|
361
|
-
|
|
362
|
-
Address pattern: `{custom-name}@mindstudio-hooks.com`.
|
|
372
|
+
`approvedSenders` is optional. When set, only senders matching an exact address or `*@domain.com` wildcard reach the method; everything else is rejected by the platform with `400 invalid_sender` before the method runs (silently — the sender isn't bounced). Matching is case-insensitive. The same list applies uniformly across all three address tiers.
|
|
363
373
|
|
|
364
374
|
### Input shape
|
|
365
375
|
|
|
366
376
|
```ts
|
|
367
377
|
{
|
|
368
|
-
to: string; //
|
|
378
|
+
to: string; // full recipient address; localpart is arbitrary on catchall tiers
|
|
369
379
|
from: string; // bare address, extracted from "Name <a@b>" form
|
|
370
380
|
subject: string; // 'No Subject' if missing
|
|
371
|
-
message: string; // plain text body; 'No Body' if neither
|
|
381
|
+
message: string; // plain text body, falls back to HTML if text is missing; 'No Body' if neither was sent
|
|
372
382
|
html: string; // HTML body, or '' when text-only
|
|
373
383
|
attachments: string[]; // CDN URLs — already uploaded by the platform
|
|
374
384
|
}
|
|
375
385
|
```
|
|
376
386
|
|
|
377
|
-
### Attachments
|
|
387
|
+
### Attachments and size limits
|
|
378
388
|
|
|
379
389
|
`attachments[]` is an array of CDN URLs — the platform has already received and uploaded the files. Fetch them server-side via the URL when you need the bytes; pass them through as URLs to UI or downstream services.
|
|
380
390
|
|
|
391
|
+
Max inbound message size is 25 MB total (including all attachments). Oversized messages are rejected by the platform before the method runs.
|
|
392
|
+
|
|
381
393
|
### Auth
|
|
382
394
|
|
|
383
395
|
Methods invoked through this interface run with `auth.roles: ['system']` (see the system-roles section above). They have no user session and can't impersonate. Use `auth.requireRole('system')` to gate methods that should only be reachable via email.
|
|
@@ -78,6 +78,6 @@ You have access to the `mindstudio` CLI, which exposes every SDK action as a com
|
|
|
78
78
|
### Production App Management
|
|
79
79
|
You have access to `mindstudio-prod`, a CLI for managing the user's production MindStudio app. Use it via your bash tool. All output is JSON. Run `mindstudio-prod --help` or `mindstudio-prod <command> --help` to discover usage and available options.
|
|
80
80
|
|
|
81
|
-
Available commands: `requests` (logs, error rates, latency), `releases` (deploy status, history), `domains` (custom subdomains and fully custom domains), `users` (list, set roles), `db` (query production sql db), `methods` (list, invoke), `secrets` (list, get, set, delete).
|
|
81
|
+
Available commands: `requests` (server-side request logs, error rates, latency), `crashes` (frontend browser errors — grouped issues + drill-down to individual events), `analytics` (traffic, top pages/referrers/geo, AI-referral attribution, live counters), `releases` (deploy status, history), `domains` (custom subdomains and fully custom domains), `users` (list, set roles), `db` (query production sql db), `data` (live db operations like lift-from-dev), `methods` (list, invoke), `secrets` (list, get, set, delete).
|
|
82
82
|
|
|
83
|
-
Use when the user asks about production behavior (errors
|
|
83
|
+
Use when the user asks about production behavior (server errors via `requests`, browser crashes via `crashes`, traffic/engagement via `analytics`), wants to manage their live app (domains, users, roles), needs to seed or query production data, or wants to check release status.
|