@mindstudio-ai/remy 0.1.184 → 0.1.186
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 +119 -117
- package/dist/index.js +137 -133
- package/dist/prompt/compiled/interfaces.md +19 -7
- 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,
|
|
@@ -3722,14 +3798,14 @@ ${appSpec}
|
|
|
3722
3798
|
// src/models/surfaces.ts
|
|
3723
3799
|
var MODEL_SURFACES = {
|
|
3724
3800
|
parent: {
|
|
3725
|
-
default: "claude-4-
|
|
3801
|
+
default: "claude-4-8-opus",
|
|
3726
3802
|
label: "Remy",
|
|
3727
3803
|
description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
|
|
3728
3804
|
modelType: "text",
|
|
3729
3805
|
userPickable: true
|
|
3730
3806
|
},
|
|
3731
3807
|
visualDesignExpert: {
|
|
3732
|
-
default: "claude-4-
|
|
3808
|
+
default: "claude-4-8-opus",
|
|
3733
3809
|
label: "Design Agent",
|
|
3734
3810
|
description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
|
|
3735
3811
|
modelType: "text",
|
|
@@ -3796,6 +3872,7 @@ var MODEL_SURFACES = {
|
|
|
3796
3872
|
};
|
|
3797
3873
|
var ALLOWED_MODELS_BY_TYPE = {
|
|
3798
3874
|
text: [
|
|
3875
|
+
"claude-4-8-opus",
|
|
3799
3876
|
"claude-4-7-opus",
|
|
3800
3877
|
"claude-4-6-opus",
|
|
3801
3878
|
"claude-4-6-sonnet",
|
|
@@ -3813,7 +3890,7 @@ function resolveModel(surfaceId, models, fallback) {
|
|
|
3813
3890
|
}
|
|
3814
3891
|
|
|
3815
3892
|
// src/subagents/browserAutomation/index.ts
|
|
3816
|
-
var
|
|
3893
|
+
var log7 = createLogger("browser-automation");
|
|
3817
3894
|
async function runBrowserAutomation(task, context) {
|
|
3818
3895
|
const release = await acquireBrowserLock();
|
|
3819
3896
|
try {
|
|
@@ -3905,7 +3982,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
3905
3982
|
}
|
|
3906
3983
|
}
|
|
3907
3984
|
} catch {
|
|
3908
|
-
|
|
3985
|
+
log7.debug("Failed to parse batch analysis result", {
|
|
3909
3986
|
batchResult
|
|
3910
3987
|
});
|
|
3911
3988
|
}
|
|
@@ -5572,7 +5649,7 @@ function executeTool(name, input, context) {
|
|
|
5572
5649
|
}
|
|
5573
5650
|
|
|
5574
5651
|
// src/compaction/trigger.ts
|
|
5575
|
-
var
|
|
5652
|
+
var log8 = createLogger("compaction:trigger");
|
|
5576
5653
|
var pendingSummaries = [];
|
|
5577
5654
|
var inflightCompaction = null;
|
|
5578
5655
|
function getPendingSummaries() {
|
|
@@ -5599,11 +5676,11 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
5599
5676
|
).then((summaries) => {
|
|
5600
5677
|
pendingSummaries.push(...summaries);
|
|
5601
5678
|
listener?.({ type: "complete", requestId });
|
|
5602
|
-
|
|
5679
|
+
log8.info("Compaction complete");
|
|
5603
5680
|
}).catch((err) => {
|
|
5604
5681
|
const message = err.message || "Compaction failed";
|
|
5605
5682
|
listener?.({ type: "complete", error: message, requestId });
|
|
5606
|
-
|
|
5683
|
+
log8.error("Compaction failed", { error: message });
|
|
5607
5684
|
throw err;
|
|
5608
5685
|
}).finally(() => {
|
|
5609
5686
|
inflightCompaction = null;
|
|
@@ -5615,7 +5692,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
|
|
|
5615
5692
|
import fs20 from "fs";
|
|
5616
5693
|
import path10 from "path";
|
|
5617
5694
|
import { createHash } from "crypto";
|
|
5618
|
-
var
|
|
5695
|
+
var log9 = createLogger("brandExtraction");
|
|
5619
5696
|
var EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
5620
5697
|
var BRAND_FILE = ".remy-brand.json";
|
|
5621
5698
|
var CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -5623,17 +5700,17 @@ async function runExtraction(apiConfig, model) {
|
|
|
5623
5700
|
const inputHash = computeInputHash();
|
|
5624
5701
|
const cached2 = readCache();
|
|
5625
5702
|
if (cached2 && cached2.inputHash === inputHash) {
|
|
5626
|
-
|
|
5703
|
+
log9.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
5627
5704
|
return null;
|
|
5628
5705
|
}
|
|
5629
|
-
|
|
5706
|
+
log9.info("Extracting brand", { inputHash });
|
|
5630
5707
|
const brand = await extractBrand(apiConfig, model);
|
|
5631
5708
|
if (!brand) {
|
|
5632
|
-
|
|
5709
|
+
log9.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
5633
5710
|
return null;
|
|
5634
5711
|
}
|
|
5635
5712
|
persistBrand(brand, inputHash);
|
|
5636
|
-
|
|
5713
|
+
log9.info("Brand persisted", { inputHash });
|
|
5637
5714
|
return brand;
|
|
5638
5715
|
}
|
|
5639
5716
|
function computeInputHash() {
|
|
@@ -5699,7 +5776,7 @@ function parseFrontmatter3(filePath) {
|
|
|
5699
5776
|
async function extractBrand(apiConfig, model) {
|
|
5700
5777
|
const corpus = buildCorpus();
|
|
5701
5778
|
if (!corpus.trim()) {
|
|
5702
|
-
|
|
5779
|
+
log9.debug("No spec corpus \u2014 emitting empty brand");
|
|
5703
5780
|
return { version: 1 };
|
|
5704
5781
|
}
|
|
5705
5782
|
let responseText = "";
|
|
@@ -5730,17 +5807,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
5730
5807
|
toolNames: []
|
|
5731
5808
|
});
|
|
5732
5809
|
} else if (event.type === "error") {
|
|
5733
|
-
|
|
5810
|
+
log9.error("Brand extraction stream error", { error: event.error });
|
|
5734
5811
|
return null;
|
|
5735
5812
|
}
|
|
5736
5813
|
}
|
|
5737
5814
|
} catch (err) {
|
|
5738
|
-
|
|
5815
|
+
log9.error("Brand extraction threw", { error: err?.message });
|
|
5739
5816
|
return null;
|
|
5740
5817
|
}
|
|
5741
5818
|
const parsed = parseJsonResponse(responseText);
|
|
5742
5819
|
if (!parsed) {
|
|
5743
|
-
|
|
5820
|
+
log9.warn("Brand extraction returned unparseable JSON", {
|
|
5744
5821
|
preview: responseText.slice(0, 200)
|
|
5745
5822
|
});
|
|
5746
5823
|
return null;
|
|
@@ -5882,7 +5959,7 @@ function readCache() {
|
|
|
5882
5959
|
}
|
|
5883
5960
|
|
|
5884
5961
|
// src/brandExtraction/trigger.ts
|
|
5885
|
-
var
|
|
5962
|
+
var log10 = createLogger("brandExtraction:trigger");
|
|
5886
5963
|
var inflight = false;
|
|
5887
5964
|
var dirty = false;
|
|
5888
5965
|
function triggerBrandExtraction(apiConfig, model) {
|
|
@@ -5892,7 +5969,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
5892
5969
|
}
|
|
5893
5970
|
inflight = true;
|
|
5894
5971
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
5895
|
-
|
|
5972
|
+
log10.error("Brand extraction failed", { error: err?.message });
|
|
5896
5973
|
}).finally(() => {
|
|
5897
5974
|
inflight = false;
|
|
5898
5975
|
if (dirty) {
|
|
@@ -5905,7 +5982,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
5905
5982
|
// src/session.ts
|
|
5906
5983
|
import fs21 from "fs";
|
|
5907
5984
|
import path11 from "path";
|
|
5908
|
-
var
|
|
5985
|
+
var log11 = createLogger("session");
|
|
5909
5986
|
var SESSION_FILE = ".remy-session.json";
|
|
5910
5987
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
5911
5988
|
function loadSession(state) {
|
|
@@ -5917,7 +5994,7 @@ function loadSession(state) {
|
|
|
5917
5994
|
}
|
|
5918
5995
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
5919
5996
|
state.messages = sanitizeMessages(data.messages);
|
|
5920
|
-
|
|
5997
|
+
log11.info("Session loaded", {
|
|
5921
5998
|
messageCount: state.messages.length,
|
|
5922
5999
|
...state.models && { models: state.models }
|
|
5923
6000
|
});
|
|
@@ -5970,9 +6047,9 @@ function saveSession(state) {
|
|
|
5970
6047
|
payload.models = state.models;
|
|
5971
6048
|
}
|
|
5972
6049
|
fs21.writeFileSync(SESSION_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
5973
|
-
|
|
6050
|
+
log11.info("Session saved", { messageCount: state.messages.length });
|
|
5974
6051
|
} catch (err) {
|
|
5975
|
-
|
|
6052
|
+
log11.warn("Session save failed", { error: err.message });
|
|
5976
6053
|
}
|
|
5977
6054
|
}
|
|
5978
6055
|
function clearSession(state) {
|
|
@@ -5983,10 +6060,10 @@ function clearSession(state) {
|
|
|
5983
6060
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5984
6061
|
const dest = path11.join(ARCHIVE_DIR, `cleared-${ts}.json`);
|
|
5985
6062
|
fs21.renameSync(SESSION_FILE, dest);
|
|
5986
|
-
|
|
6063
|
+
log11.info("Session archived on clear", { dest });
|
|
5987
6064
|
}
|
|
5988
6065
|
} catch (err) {
|
|
5989
|
-
|
|
6066
|
+
log11.warn("Session archive on clear failed, deleting instead", {
|
|
5990
6067
|
error: err.message
|
|
5991
6068
|
});
|
|
5992
6069
|
try {
|
|
@@ -6190,7 +6267,7 @@ function friendlyError(raw) {
|
|
|
6190
6267
|
}
|
|
6191
6268
|
|
|
6192
6269
|
// src/agent.ts
|
|
6193
|
-
var
|
|
6270
|
+
var log12 = createLogger("agent");
|
|
6194
6271
|
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
6195
6272
|
function getTextContent(blocks) {
|
|
6196
6273
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
@@ -6239,7 +6316,7 @@ async function runTurn(params) {
|
|
|
6239
6316
|
} = params;
|
|
6240
6317
|
const tools2 = getToolDefinitions(onboardingState);
|
|
6241
6318
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
6242
|
-
|
|
6319
|
+
log12.info("Turn started", {
|
|
6243
6320
|
requestId,
|
|
6244
6321
|
model,
|
|
6245
6322
|
toolCount: tools2.length,
|
|
@@ -6492,7 +6569,7 @@ async function runTurn(params) {
|
|
|
6492
6569
|
const tool = getToolByName(event.name);
|
|
6493
6570
|
const wasStreamed = acc?.started ?? false;
|
|
6494
6571
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
6495
|
-
|
|
6572
|
+
log12.info("Tool received", {
|
|
6496
6573
|
requestId,
|
|
6497
6574
|
toolCallId: event.id,
|
|
6498
6575
|
name: event.name
|
|
@@ -6606,7 +6683,7 @@ async function runTurn(params) {
|
|
|
6606
6683
|
});
|
|
6607
6684
|
return;
|
|
6608
6685
|
}
|
|
6609
|
-
|
|
6686
|
+
log12.info("Tools executing", {
|
|
6610
6687
|
requestId,
|
|
6611
6688
|
count: toolCalls.length,
|
|
6612
6689
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -6626,7 +6703,7 @@ async function runTurn(params) {
|
|
|
6626
6703
|
const results = await Promise.all(
|
|
6627
6704
|
toolCalls.map(async (tc) => {
|
|
6628
6705
|
if (signal?.aborted) {
|
|
6629
|
-
return { id: tc.id, result:
|
|
6706
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
6630
6707
|
}
|
|
6631
6708
|
const toolStart = Date.now();
|
|
6632
6709
|
let settle;
|
|
@@ -6645,7 +6722,7 @@ async function runTurn(params) {
|
|
|
6645
6722
|
};
|
|
6646
6723
|
const cascadeAbort = () => {
|
|
6647
6724
|
toolAbort.abort();
|
|
6648
|
-
safeSettle(
|
|
6725
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
6649
6726
|
};
|
|
6650
6727
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
6651
6728
|
const run = async (input) => {
|
|
@@ -6653,7 +6730,7 @@ async function runTurn(params) {
|
|
|
6653
6730
|
let result;
|
|
6654
6731
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
6655
6732
|
saveSession(state);
|
|
6656
|
-
|
|
6733
|
+
log12.info("Waiting for external tool result", {
|
|
6657
6734
|
requestId,
|
|
6658
6735
|
toolCallId: tc.id,
|
|
6659
6736
|
name: tc.name
|
|
@@ -6721,7 +6798,7 @@ async function runTurn(params) {
|
|
|
6721
6798
|
if (!tc.input.background) {
|
|
6722
6799
|
toolRegistry?.unregister(tc.id);
|
|
6723
6800
|
}
|
|
6724
|
-
|
|
6801
|
+
log12.info("Tool completed", {
|
|
6725
6802
|
requestId,
|
|
6726
6803
|
toolCallId: tc.id,
|
|
6727
6804
|
name: tc.name,
|
|
@@ -6781,81 +6858,6 @@ async function runTurn(params) {
|
|
|
6781
6858
|
}
|
|
6782
6859
|
}
|
|
6783
6860
|
|
|
6784
|
-
// src/toolRegistry.ts
|
|
6785
|
-
var log12 = createLogger("tool-registry");
|
|
6786
|
-
var ToolRegistry = class {
|
|
6787
|
-
entries = /* @__PURE__ */ new Map();
|
|
6788
|
-
onEvent;
|
|
6789
|
-
register(entry) {
|
|
6790
|
-
this.entries.set(entry.id, entry);
|
|
6791
|
-
}
|
|
6792
|
-
unregister(id) {
|
|
6793
|
-
this.entries.delete(id);
|
|
6794
|
-
}
|
|
6795
|
-
get(id) {
|
|
6796
|
-
return this.entries.get(id);
|
|
6797
|
-
}
|
|
6798
|
-
/**
|
|
6799
|
-
* Stop a running tool.
|
|
6800
|
-
*
|
|
6801
|
-
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
6802
|
-
* - hard: abort and settle with a generic error
|
|
6803
|
-
*
|
|
6804
|
-
* Returns true if the tool was found and stopped.
|
|
6805
|
-
*/
|
|
6806
|
-
stop(id, mode) {
|
|
6807
|
-
const entry = this.entries.get(id);
|
|
6808
|
-
if (!entry) {
|
|
6809
|
-
return false;
|
|
6810
|
-
}
|
|
6811
|
-
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
6812
|
-
entry.abortController.abort(mode);
|
|
6813
|
-
if (mode === "graceful") {
|
|
6814
|
-
const partial = entry.getPartialResult?.() ?? "";
|
|
6815
|
-
const result = partial ? `[INTERRUPTED]
|
|
6816
|
-
|
|
6817
|
-
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
6818
|
-
entry.settle(result, false);
|
|
6819
|
-
} else {
|
|
6820
|
-
entry.settle("Error: tool was cancelled", true);
|
|
6821
|
-
}
|
|
6822
|
-
this.onEvent?.({
|
|
6823
|
-
type: "tool_stopped",
|
|
6824
|
-
id: entry.id,
|
|
6825
|
-
name: entry.name,
|
|
6826
|
-
mode,
|
|
6827
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
6828
|
-
});
|
|
6829
|
-
this.entries.delete(id);
|
|
6830
|
-
return true;
|
|
6831
|
-
}
|
|
6832
|
-
/**
|
|
6833
|
-
* Restart a running tool with the same or patched input.
|
|
6834
|
-
* The original controllable promise stays pending and settles
|
|
6835
|
-
* when the new execution finishes.
|
|
6836
|
-
*
|
|
6837
|
-
* Returns true if the tool was found and restarted.
|
|
6838
|
-
*/
|
|
6839
|
-
restart(id, patchedInput) {
|
|
6840
|
-
const entry = this.entries.get(id);
|
|
6841
|
-
if (!entry) {
|
|
6842
|
-
return false;
|
|
6843
|
-
}
|
|
6844
|
-
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
6845
|
-
entry.abortController.abort("restart");
|
|
6846
|
-
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
6847
|
-
this.onEvent?.({
|
|
6848
|
-
type: "tool_restarted",
|
|
6849
|
-
id: entry.id,
|
|
6850
|
-
name: entry.name,
|
|
6851
|
-
input: newInput,
|
|
6852
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
6853
|
-
});
|
|
6854
|
-
entry.rerun(newInput);
|
|
6855
|
-
return true;
|
|
6856
|
-
}
|
|
6857
|
-
};
|
|
6858
|
-
|
|
6859
6861
|
// src/headless/attachments.ts
|
|
6860
6862
|
import { mkdirSync, existsSync } from "fs";
|
|
6861
6863
|
import { writeFile } from "fs/promises";
|
|
@@ -7804,7 +7806,7 @@ var HeadlessSession = class {
|
|
|
7804
7806
|
}
|
|
7805
7807
|
for (const [id, pending] of this.pendingTools) {
|
|
7806
7808
|
clearTimeout(pending.timeout);
|
|
7807
|
-
pending.resolve(
|
|
7809
|
+
pending.resolve(USER_CANCELLED_RESULT);
|
|
7808
7810
|
this.pendingTools.delete(id);
|
|
7809
7811
|
}
|
|
7810
7812
|
return this.queue.drain();
|
package/dist/index.js
CHANGED
|
@@ -2034,14 +2034,14 @@ var init_surfaces = __esm({
|
|
|
2034
2034
|
"use strict";
|
|
2035
2035
|
MODEL_SURFACES = {
|
|
2036
2036
|
parent: {
|
|
2037
|
-
default: "claude-4-
|
|
2037
|
+
default: "claude-4-8-opus",
|
|
2038
2038
|
label: "Remy",
|
|
2039
2039
|
description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
|
|
2040
2040
|
modelType: "text",
|
|
2041
2041
|
userPickable: true
|
|
2042
2042
|
},
|
|
2043
2043
|
visualDesignExpert: {
|
|
2044
|
-
default: "claude-4-
|
|
2044
|
+
default: "claude-4-8-opus",
|
|
2045
2045
|
label: "Design Agent",
|
|
2046
2046
|
description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
|
|
2047
2047
|
modelType: "text",
|
|
@@ -2108,6 +2108,7 @@ var init_surfaces = __esm({
|
|
|
2108
2108
|
};
|
|
2109
2109
|
ALLOWED_MODELS_BY_TYPE = {
|
|
2110
2110
|
text: [
|
|
2111
|
+
"claude-4-8-opus",
|
|
2111
2112
|
"claude-4-7-opus",
|
|
2112
2113
|
"claude-4-6-opus",
|
|
2113
2114
|
"claude-4-6-sonnet",
|
|
@@ -3319,6 +3320,89 @@ var init_browserLock = __esm({
|
|
|
3319
3320
|
}
|
|
3320
3321
|
});
|
|
3321
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
|
+
|
|
3322
3406
|
// src/statusWatcher.ts
|
|
3323
3407
|
function startStatusWatcher(config) {
|
|
3324
3408
|
const { apiConfig, getContext, onStatus, interval = 5e3, signal } = config;
|
|
@@ -3606,7 +3690,7 @@ async function runSubAgent(config) {
|
|
|
3606
3690
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
3607
3691
|
const agentName = subAgentId || "sub-agent";
|
|
3608
3692
|
const runStart = Date.now();
|
|
3609
|
-
|
|
3693
|
+
log6.info("Sub-agent started", { requestId, parentToolId, agentName });
|
|
3610
3694
|
const emit = (e) => {
|
|
3611
3695
|
onEvent({ ...e, parentToolId });
|
|
3612
3696
|
};
|
|
@@ -3641,7 +3725,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3641
3725
|
messages: thisInvocation()
|
|
3642
3726
|
};
|
|
3643
3727
|
}
|
|
3644
|
-
return { text:
|
|
3728
|
+
return { text: USER_CANCELLED_RESULT, messages: thisInvocation() };
|
|
3645
3729
|
}
|
|
3646
3730
|
let lastToolResult = "";
|
|
3647
3731
|
while (true) {
|
|
@@ -3823,7 +3907,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3823
3907
|
...hasArtifacts ? { artifacts } : {}
|
|
3824
3908
|
};
|
|
3825
3909
|
}
|
|
3826
|
-
|
|
3910
|
+
log6.info("Tools executing", {
|
|
3827
3911
|
requestId,
|
|
3828
3912
|
parentToolId,
|
|
3829
3913
|
count: toolCalls.length,
|
|
@@ -3833,7 +3917,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3833
3917
|
const results = await Promise.all(
|
|
3834
3918
|
toolCalls.map(async (tc) => {
|
|
3835
3919
|
if (signal?.aborted) {
|
|
3836
|
-
return { id: tc.id, result:
|
|
3920
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
3837
3921
|
}
|
|
3838
3922
|
let settle;
|
|
3839
3923
|
const resultPromise = new Promise((res) => {
|
|
@@ -3900,7 +3984,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3900
3984
|
run2(tc.input);
|
|
3901
3985
|
const r = await resultPromise;
|
|
3902
3986
|
toolRegistry?.unregister(tc.id);
|
|
3903
|
-
|
|
3987
|
+
log6.info("Tool completed", {
|
|
3904
3988
|
requestId,
|
|
3905
3989
|
parentToolId,
|
|
3906
3990
|
toolCallId: tc.id,
|
|
@@ -3951,7 +4035,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3951
4035
|
const wrapRun = async () => {
|
|
3952
4036
|
try {
|
|
3953
4037
|
const result = await run();
|
|
3954
|
-
|
|
4038
|
+
log6.info("Sub-agent complete", {
|
|
3955
4039
|
requestId,
|
|
3956
4040
|
parentToolId,
|
|
3957
4041
|
agentName,
|
|
@@ -3960,7 +4044,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3960
4044
|
});
|
|
3961
4045
|
return result;
|
|
3962
4046
|
} catch (err) {
|
|
3963
|
-
|
|
4047
|
+
log6.warn("Sub-agent error", {
|
|
3964
4048
|
requestId,
|
|
3965
4049
|
parentToolId,
|
|
3966
4050
|
agentName,
|
|
@@ -3972,7 +4056,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3972
4056
|
if (!background) {
|
|
3973
4057
|
return wrapRun();
|
|
3974
4058
|
}
|
|
3975
|
-
|
|
4059
|
+
log6.info("Sub-agent backgrounded", { requestId, parentToolId, agentName });
|
|
3976
4060
|
toolRegistry?.register({
|
|
3977
4061
|
id: parentToolId,
|
|
3978
4062
|
name: agentName,
|
|
@@ -3999,16 +4083,17 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3999
4083
|
});
|
|
4000
4084
|
return { text: ack, messages: [], backgrounded: true };
|
|
4001
4085
|
}
|
|
4002
|
-
var
|
|
4086
|
+
var log6;
|
|
4003
4087
|
var init_runner = __esm({
|
|
4004
4088
|
"src/subagents/runner.ts"() {
|
|
4005
4089
|
"use strict";
|
|
4006
4090
|
init_api();
|
|
4007
4091
|
init_logger();
|
|
4008
4092
|
init_usageLedger();
|
|
4093
|
+
init_toolRegistry();
|
|
4009
4094
|
init_statusWatcher();
|
|
4010
4095
|
init_cleanMessages();
|
|
4011
|
-
|
|
4096
|
+
log6 = createLogger("sub-agent");
|
|
4012
4097
|
}
|
|
4013
4098
|
});
|
|
4014
4099
|
|
|
@@ -4268,7 +4353,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
4268
4353
|
}
|
|
4269
4354
|
}
|
|
4270
4355
|
} catch {
|
|
4271
|
-
|
|
4356
|
+
log7.debug("Failed to parse batch analysis result", {
|
|
4272
4357
|
batchResult
|
|
4273
4358
|
});
|
|
4274
4359
|
}
|
|
@@ -4292,7 +4377,7 @@ async function runBrowserAutomation(task, context) {
|
|
|
4292
4377
|
release();
|
|
4293
4378
|
}
|
|
4294
4379
|
}
|
|
4295
|
-
var
|
|
4380
|
+
var log7, browserAutomationTool;
|
|
4296
4381
|
var init_browserAutomation = __esm({
|
|
4297
4382
|
"src/subagents/browserAutomation/index.ts"() {
|
|
4298
4383
|
"use strict";
|
|
@@ -4305,7 +4390,7 @@ var init_browserAutomation = __esm({
|
|
|
4305
4390
|
init_runMindstudioCli();
|
|
4306
4391
|
init_surfaces();
|
|
4307
4392
|
init_logger();
|
|
4308
|
-
|
|
4393
|
+
log7 = createLogger("browser-automation");
|
|
4309
4394
|
browserAutomationTool = {
|
|
4310
4395
|
clearable: true,
|
|
4311
4396
|
definition: {
|
|
@@ -6236,7 +6321,7 @@ function loadSession(state) {
|
|
|
6236
6321
|
}
|
|
6237
6322
|
if (Array.isArray(data.messages) && data.messages.length > 0) {
|
|
6238
6323
|
state.messages = sanitizeMessages(data.messages);
|
|
6239
|
-
|
|
6324
|
+
log8.info("Session loaded", {
|
|
6240
6325
|
messageCount: state.messages.length,
|
|
6241
6326
|
...state.models && { models: state.models }
|
|
6242
6327
|
});
|
|
@@ -6289,9 +6374,9 @@ function saveSession(state) {
|
|
|
6289
6374
|
payload.models = state.models;
|
|
6290
6375
|
}
|
|
6291
6376
|
fs19.writeFileSync(SESSION_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
|
6292
|
-
|
|
6377
|
+
log8.info("Session saved", { messageCount: state.messages.length });
|
|
6293
6378
|
} catch (err) {
|
|
6294
|
-
|
|
6379
|
+
log8.warn("Session save failed", { error: err.message });
|
|
6295
6380
|
}
|
|
6296
6381
|
}
|
|
6297
6382
|
function clearSession(state) {
|
|
@@ -6302,10 +6387,10 @@ function clearSession(state) {
|
|
|
6302
6387
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6303
6388
|
const dest = path9.join(ARCHIVE_DIR, `cleared-${ts}.json`);
|
|
6304
6389
|
fs19.renameSync(SESSION_FILE, dest);
|
|
6305
|
-
|
|
6390
|
+
log8.info("Session archived on clear", { dest });
|
|
6306
6391
|
}
|
|
6307
6392
|
} catch (err) {
|
|
6308
|
-
|
|
6393
|
+
log8.warn("Session archive on clear failed, deleting instead", {
|
|
6309
6394
|
error: err.message
|
|
6310
6395
|
});
|
|
6311
6396
|
try {
|
|
@@ -6314,12 +6399,12 @@ function clearSession(state) {
|
|
|
6314
6399
|
}
|
|
6315
6400
|
}
|
|
6316
6401
|
}
|
|
6317
|
-
var
|
|
6402
|
+
var log8, SESSION_FILE, ARCHIVE_DIR;
|
|
6318
6403
|
var init_session = __esm({
|
|
6319
6404
|
"src/session.ts"() {
|
|
6320
6405
|
"use strict";
|
|
6321
6406
|
init_logger();
|
|
6322
|
-
|
|
6407
|
+
log8 = createLogger("session");
|
|
6323
6408
|
SESSION_FILE = ".remy-session.json";
|
|
6324
6409
|
ARCHIVE_DIR = ".logs/sessions";
|
|
6325
6410
|
}
|
|
@@ -6538,17 +6623,17 @@ async function runExtraction(apiConfig, model) {
|
|
|
6538
6623
|
const inputHash = computeInputHash();
|
|
6539
6624
|
const cached2 = readCache();
|
|
6540
6625
|
if (cached2 && cached2.inputHash === inputHash) {
|
|
6541
|
-
|
|
6626
|
+
log9.debug("Brand inputs unchanged \u2014 skipping extraction", { inputHash });
|
|
6542
6627
|
return null;
|
|
6543
6628
|
}
|
|
6544
|
-
|
|
6629
|
+
log9.info("Extracting brand", { inputHash });
|
|
6545
6630
|
const brand = await extractBrand(apiConfig, model);
|
|
6546
6631
|
if (!brand) {
|
|
6547
|
-
|
|
6632
|
+
log9.warn("Brand extraction failed \u2014 leaving cache untouched");
|
|
6548
6633
|
return null;
|
|
6549
6634
|
}
|
|
6550
6635
|
persistBrand(brand, inputHash);
|
|
6551
|
-
|
|
6636
|
+
log9.info("Brand persisted", { inputHash });
|
|
6552
6637
|
return brand;
|
|
6553
6638
|
}
|
|
6554
6639
|
function computeInputHash() {
|
|
@@ -6614,7 +6699,7 @@ function parseFrontmatter3(filePath) {
|
|
|
6614
6699
|
async function extractBrand(apiConfig, model) {
|
|
6615
6700
|
const corpus = buildCorpus();
|
|
6616
6701
|
if (!corpus.trim()) {
|
|
6617
|
-
|
|
6702
|
+
log9.debug("No spec corpus \u2014 emitting empty brand");
|
|
6618
6703
|
return { version: 1 };
|
|
6619
6704
|
}
|
|
6620
6705
|
let responseText = "";
|
|
@@ -6645,17 +6730,17 @@ async function extractBrand(apiConfig, model) {
|
|
|
6645
6730
|
toolNames: []
|
|
6646
6731
|
});
|
|
6647
6732
|
} else if (event.type === "error") {
|
|
6648
|
-
|
|
6733
|
+
log9.error("Brand extraction stream error", { error: event.error });
|
|
6649
6734
|
return null;
|
|
6650
6735
|
}
|
|
6651
6736
|
}
|
|
6652
6737
|
} catch (err) {
|
|
6653
|
-
|
|
6738
|
+
log9.error("Brand extraction threw", { error: err?.message });
|
|
6654
6739
|
return null;
|
|
6655
6740
|
}
|
|
6656
6741
|
const parsed = parseJsonResponse(responseText);
|
|
6657
6742
|
if (!parsed) {
|
|
6658
|
-
|
|
6743
|
+
log9.warn("Brand extraction returned unparseable JSON", {
|
|
6659
6744
|
preview: responseText.slice(0, 200)
|
|
6660
6745
|
});
|
|
6661
6746
|
return null;
|
|
@@ -6795,7 +6880,7 @@ function readCache() {
|
|
|
6795
6880
|
return null;
|
|
6796
6881
|
}
|
|
6797
6882
|
}
|
|
6798
|
-
var
|
|
6883
|
+
var log9, EXTRACT_PROMPT, BRAND_FILE, CACHE_FILE;
|
|
6799
6884
|
var init_brandExtraction = __esm({
|
|
6800
6885
|
"src/brandExtraction/index.ts"() {
|
|
6801
6886
|
"use strict";
|
|
@@ -6803,7 +6888,7 @@ var init_brandExtraction = __esm({
|
|
|
6803
6888
|
init_assets();
|
|
6804
6889
|
init_logger();
|
|
6805
6890
|
init_usageLedger();
|
|
6806
|
-
|
|
6891
|
+
log9 = createLogger("brandExtraction");
|
|
6807
6892
|
EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
|
|
6808
6893
|
BRAND_FILE = ".remy-brand.json";
|
|
6809
6894
|
CACHE_FILE = ".remy-brand.cache.json";
|
|
@@ -6818,7 +6903,7 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
6818
6903
|
}
|
|
6819
6904
|
inflight = true;
|
|
6820
6905
|
void runExtraction(apiConfig, model).catch((err) => {
|
|
6821
|
-
|
|
6906
|
+
log10.error("Brand extraction failed", { error: err?.message });
|
|
6822
6907
|
}).finally(() => {
|
|
6823
6908
|
inflight = false;
|
|
6824
6909
|
if (dirty) {
|
|
@@ -6827,13 +6912,13 @@ function triggerBrandExtraction(apiConfig, model) {
|
|
|
6827
6912
|
}
|
|
6828
6913
|
});
|
|
6829
6914
|
}
|
|
6830
|
-
var
|
|
6915
|
+
var log10, inflight, dirty;
|
|
6831
6916
|
var init_trigger2 = __esm({
|
|
6832
6917
|
"src/brandExtraction/trigger.ts"() {
|
|
6833
6918
|
"use strict";
|
|
6834
6919
|
init_brandExtraction();
|
|
6835
6920
|
init_logger();
|
|
6836
|
-
|
|
6921
|
+
log10 = createLogger("brandExtraction:trigger");
|
|
6837
6922
|
inflight = false;
|
|
6838
6923
|
dirty = false;
|
|
6839
6924
|
}
|
|
@@ -6871,7 +6956,7 @@ async function runTurn(params) {
|
|
|
6871
6956
|
} = params;
|
|
6872
6957
|
const tools2 = getToolDefinitions(onboardingState);
|
|
6873
6958
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
6874
|
-
|
|
6959
|
+
log11.info("Turn started", {
|
|
6875
6960
|
requestId,
|
|
6876
6961
|
model,
|
|
6877
6962
|
toolCount: tools2.length,
|
|
@@ -7124,7 +7209,7 @@ async function runTurn(params) {
|
|
|
7124
7209
|
const tool = getToolByName(event.name);
|
|
7125
7210
|
const wasStreamed = acc?.started ?? false;
|
|
7126
7211
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
7127
|
-
|
|
7212
|
+
log11.info("Tool received", {
|
|
7128
7213
|
requestId,
|
|
7129
7214
|
toolCallId: event.id,
|
|
7130
7215
|
name: event.name
|
|
@@ -7238,7 +7323,7 @@ async function runTurn(params) {
|
|
|
7238
7323
|
});
|
|
7239
7324
|
return;
|
|
7240
7325
|
}
|
|
7241
|
-
|
|
7326
|
+
log11.info("Tools executing", {
|
|
7242
7327
|
requestId,
|
|
7243
7328
|
count: toolCalls.length,
|
|
7244
7329
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -7258,7 +7343,7 @@ async function runTurn(params) {
|
|
|
7258
7343
|
const results = await Promise.all(
|
|
7259
7344
|
toolCalls.map(async (tc) => {
|
|
7260
7345
|
if (signal?.aborted) {
|
|
7261
|
-
return { id: tc.id, result:
|
|
7346
|
+
return { id: tc.id, result: USER_CANCELLED_RESULT, isError: true };
|
|
7262
7347
|
}
|
|
7263
7348
|
const toolStart = Date.now();
|
|
7264
7349
|
let settle;
|
|
@@ -7277,7 +7362,7 @@ async function runTurn(params) {
|
|
|
7277
7362
|
};
|
|
7278
7363
|
const cascadeAbort = () => {
|
|
7279
7364
|
toolAbort.abort();
|
|
7280
|
-
safeSettle(
|
|
7365
|
+
safeSettle(USER_CANCELLED_RESULT, true);
|
|
7281
7366
|
};
|
|
7282
7367
|
signal?.addEventListener("abort", cascadeAbort, { once: true });
|
|
7283
7368
|
const run = async (input) => {
|
|
@@ -7285,7 +7370,7 @@ async function runTurn(params) {
|
|
|
7285
7370
|
let result;
|
|
7286
7371
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
7287
7372
|
saveSession(state);
|
|
7288
|
-
|
|
7373
|
+
log11.info("Waiting for external tool result", {
|
|
7289
7374
|
requestId,
|
|
7290
7375
|
toolCallId: tc.id,
|
|
7291
7376
|
name: tc.name
|
|
@@ -7353,7 +7438,7 @@ async function runTurn(params) {
|
|
|
7353
7438
|
if (!tc.input.background) {
|
|
7354
7439
|
toolRegistry?.unregister(tc.id);
|
|
7355
7440
|
}
|
|
7356
|
-
|
|
7441
|
+
log11.info("Tool completed", {
|
|
7357
7442
|
requestId,
|
|
7358
7443
|
toolCallId: tc.id,
|
|
7359
7444
|
name: tc.name,
|
|
@@ -7412,7 +7497,7 @@ async function runTurn(params) {
|
|
|
7412
7497
|
}
|
|
7413
7498
|
}
|
|
7414
7499
|
}
|
|
7415
|
-
var
|
|
7500
|
+
var log11, BRAND_TRIGGERING_TOOLS, EXTERNAL_TOOLS, USER_BLOCKING_EXTERNAL_TOOLS;
|
|
7416
7501
|
var init_agent = __esm({
|
|
7417
7502
|
"src/agent.ts"() {
|
|
7418
7503
|
"use strict";
|
|
@@ -7429,7 +7514,8 @@ var init_agent = __esm({
|
|
|
7429
7514
|
init_sentinel();
|
|
7430
7515
|
init_trigger2();
|
|
7431
7516
|
init_surfaces();
|
|
7432
|
-
|
|
7517
|
+
init_toolRegistry();
|
|
7518
|
+
log11 = createLogger("agent");
|
|
7433
7519
|
BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set(["writeSpec", "editSpec"]);
|
|
7434
7520
|
EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
7435
7521
|
"promptUser",
|
|
@@ -7457,10 +7543,10 @@ import os from "os";
|
|
|
7457
7543
|
function loadConfigFile() {
|
|
7458
7544
|
try {
|
|
7459
7545
|
const raw = fs21.readFileSync(CONFIG_PATH, "utf-8");
|
|
7460
|
-
|
|
7546
|
+
log12.debug("Loaded config file", { path: CONFIG_PATH });
|
|
7461
7547
|
return JSON.parse(raw);
|
|
7462
7548
|
} catch (err) {
|
|
7463
|
-
|
|
7549
|
+
log12.debug("No config file found", {
|
|
7464
7550
|
path: CONFIG_PATH,
|
|
7465
7551
|
error: err.message
|
|
7466
7552
|
});
|
|
@@ -7475,13 +7561,13 @@ function resolveConfig(flags2) {
|
|
|
7475
7561
|
const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
|
|
7476
7562
|
const appId = process.env.MINDSTUDIO_APP_ID || void 0;
|
|
7477
7563
|
if (!apiKey) {
|
|
7478
|
-
|
|
7564
|
+
log12.error("No API key found");
|
|
7479
7565
|
throw new Error(
|
|
7480
7566
|
"No API key found. Set MINDSTUDIO_API_KEY or configure ~/.mindstudio-local-tunnel/config.json."
|
|
7481
7567
|
);
|
|
7482
7568
|
}
|
|
7483
7569
|
const keySource = flags2?.apiKey ? "cli flag" : process.env.MINDSTUDIO_API_KEY ? "env var" : "config file";
|
|
7484
|
-
|
|
7570
|
+
log12.info("Config resolved", {
|
|
7485
7571
|
baseUrl: baseUrl2,
|
|
7486
7572
|
keySource,
|
|
7487
7573
|
environment: activeEnv,
|
|
@@ -7489,12 +7575,12 @@ function resolveConfig(flags2) {
|
|
|
7489
7575
|
});
|
|
7490
7576
|
return { apiKey, baseUrl: baseUrl2, appId };
|
|
7491
7577
|
}
|
|
7492
|
-
var
|
|
7578
|
+
var log12, CONFIG_PATH, DEFAULT_BASE_URL;
|
|
7493
7579
|
var init_config = __esm({
|
|
7494
7580
|
"src/config.ts"() {
|
|
7495
7581
|
"use strict";
|
|
7496
7582
|
init_logger();
|
|
7497
|
-
|
|
7583
|
+
log12 = createLogger("config");
|
|
7498
7584
|
CONFIG_PATH = path11.join(
|
|
7499
7585
|
os.homedir(),
|
|
7500
7586
|
".mindstudio-local-tunnel",
|
|
@@ -7504,88 +7590,6 @@ var init_config = __esm({
|
|
|
7504
7590
|
}
|
|
7505
7591
|
});
|
|
7506
7592
|
|
|
7507
|
-
// src/toolRegistry.ts
|
|
7508
|
-
var log12, ToolRegistry;
|
|
7509
|
-
var init_toolRegistry = __esm({
|
|
7510
|
-
"src/toolRegistry.ts"() {
|
|
7511
|
-
"use strict";
|
|
7512
|
-
init_logger();
|
|
7513
|
-
log12 = createLogger("tool-registry");
|
|
7514
|
-
ToolRegistry = class {
|
|
7515
|
-
entries = /* @__PURE__ */ new Map();
|
|
7516
|
-
onEvent;
|
|
7517
|
-
register(entry) {
|
|
7518
|
-
this.entries.set(entry.id, entry);
|
|
7519
|
-
}
|
|
7520
|
-
unregister(id) {
|
|
7521
|
-
this.entries.delete(id);
|
|
7522
|
-
}
|
|
7523
|
-
get(id) {
|
|
7524
|
-
return this.entries.get(id);
|
|
7525
|
-
}
|
|
7526
|
-
/**
|
|
7527
|
-
* Stop a running tool.
|
|
7528
|
-
*
|
|
7529
|
-
* - graceful: abort and settle with [INTERRUPTED] + partial result
|
|
7530
|
-
* - hard: abort and settle with a generic error
|
|
7531
|
-
*
|
|
7532
|
-
* Returns true if the tool was found and stopped.
|
|
7533
|
-
*/
|
|
7534
|
-
stop(id, mode) {
|
|
7535
|
-
const entry = this.entries.get(id);
|
|
7536
|
-
if (!entry) {
|
|
7537
|
-
return false;
|
|
7538
|
-
}
|
|
7539
|
-
log12.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
7540
|
-
entry.abortController.abort(mode);
|
|
7541
|
-
if (mode === "graceful") {
|
|
7542
|
-
const partial = entry.getPartialResult?.() ?? "";
|
|
7543
|
-
const result = partial ? `[INTERRUPTED]
|
|
7544
|
-
|
|
7545
|
-
${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
7546
|
-
entry.settle(result, false);
|
|
7547
|
-
} else {
|
|
7548
|
-
entry.settle("Error: tool was cancelled", true);
|
|
7549
|
-
}
|
|
7550
|
-
this.onEvent?.({
|
|
7551
|
-
type: "tool_stopped",
|
|
7552
|
-
id: entry.id,
|
|
7553
|
-
name: entry.name,
|
|
7554
|
-
mode,
|
|
7555
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
7556
|
-
});
|
|
7557
|
-
this.entries.delete(id);
|
|
7558
|
-
return true;
|
|
7559
|
-
}
|
|
7560
|
-
/**
|
|
7561
|
-
* Restart a running tool with the same or patched input.
|
|
7562
|
-
* The original controllable promise stays pending and settles
|
|
7563
|
-
* when the new execution finishes.
|
|
7564
|
-
*
|
|
7565
|
-
* Returns true if the tool was found and restarted.
|
|
7566
|
-
*/
|
|
7567
|
-
restart(id, patchedInput) {
|
|
7568
|
-
const entry = this.entries.get(id);
|
|
7569
|
-
if (!entry) {
|
|
7570
|
-
return false;
|
|
7571
|
-
}
|
|
7572
|
-
log12.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
7573
|
-
entry.abortController.abort("restart");
|
|
7574
|
-
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
7575
|
-
this.onEvent?.({
|
|
7576
|
-
type: "tool_restarted",
|
|
7577
|
-
id: entry.id,
|
|
7578
|
-
name: entry.name,
|
|
7579
|
-
input: newInput,
|
|
7580
|
-
...entry.parentToolId && { parentToolId: entry.parentToolId }
|
|
7581
|
-
});
|
|
7582
|
-
entry.rerun(newInput);
|
|
7583
|
-
return true;
|
|
7584
|
-
}
|
|
7585
|
-
};
|
|
7586
|
-
}
|
|
7587
|
-
});
|
|
7588
|
-
|
|
7589
7593
|
// src/headless/attachments.ts
|
|
7590
7594
|
import { mkdirSync, existsSync } from "fs";
|
|
7591
7595
|
import { writeFile } from "fs/promises";
|
|
@@ -8593,7 +8597,7 @@ var init_headless = __esm({
|
|
|
8593
8597
|
}
|
|
8594
8598
|
for (const [id, pending] of this.pendingTools) {
|
|
8595
8599
|
clearTimeout(pending.timeout);
|
|
8596
|
-
pending.resolve(
|
|
8600
|
+
pending.resolve(USER_CANCELLED_RESULT);
|
|
8597
8601
|
this.pendingTools.delete(id);
|
|
8598
8602
|
}
|
|
8599
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.
|