@mindstudio-ai/remy 0.1.269 → 0.1.271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -18
- package/dist/headless.js +107 -68
- package/dist/index.js +121 -69
- package/dist/prompt/compiled/auth.md +20 -415
- package/dist/prompt/compiled/design.md +4 -2
- package/dist/prompt/compiled/dev-and-deploy.md +1 -8
- package/dist/prompt/compiled/interfaces.md +9 -27
- package/dist/prompt/compiled/methods.md +3 -37
- package/dist/prompt/compiled/msfm.md +1 -16
- package/dist/prompt/compiled/platform.md +7 -17
- package/dist/prompt/skills/auth.md +443 -0
- package/dist/prompt/{compiled → skills}/files.md +6 -0
- package/dist/prompt/{compiled → skills}/scenarios.md +6 -0
- package/dist/prompt/{compiled → skills}/secrets.md +6 -0
- package/dist/prompt/skills/voiceInterfaces.md +15 -9
- package/dist/prompt/static/authoring.md +1 -1
- package/dist/prompt/static/coding.md +4 -2
- package/dist/prompt/static/intake.md +1 -4
- package/dist/prompt/static/spec-maintenance.md +41 -0
- package/dist/prompt/static/team.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,13 +43,13 @@ Remy saves conversation history to `.remy-session.json` in the working directory
|
|
|
43
43
|
|
|
44
44
|
## Tools
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
The full tool set is always available — it is deliberately invariant (no onboarding-state gating) so the provider's tools-tier cache prefix stays identical across turns and sessions. The sections below are thematic groupings only.
|
|
47
47
|
|
|
48
|
-
### Common Tools
|
|
48
|
+
### Common Tools
|
|
49
49
|
|
|
50
50
|
| Tool | Description |
|
|
51
51
|
|------|-------------|
|
|
52
|
-
| `setProjectOnboardingState` | Advance the onboarding flow (intake →
|
|
52
|
+
| `setProjectOnboardingState` | Advance the onboarding flow (intake → building → buildComplete → onboardingFinished) |
|
|
53
53
|
| `setProjectName` | Set the project name |
|
|
54
54
|
| `promptUser` | Ask the user structured questions (form or inline display) |
|
|
55
55
|
| `confirmDestructiveAction` | Confirm a destructive or irreversible action with the user |
|
|
@@ -74,8 +74,6 @@ Available in all onboarding states. Used for authoring and editing MSFM specs in
|
|
|
74
74
|
|
|
75
75
|
### Code Tools
|
|
76
76
|
|
|
77
|
-
Available from `initialCodegen` onward.
|
|
78
|
-
|
|
79
77
|
| Tool | Description |
|
|
80
78
|
|------|-------------|
|
|
81
79
|
| `readFile` | Read a file with line numbers |
|
|
@@ -100,9 +98,7 @@ Available when `--lsp-url` is passed.
|
|
|
100
98
|
| `lspDiagnostics` | Type errors and warnings for a file, with suggested quick fixes |
|
|
101
99
|
| `restartProcess` | Restart a managed sandbox process (e.g., dev server after npm install) |
|
|
102
100
|
|
|
103
|
-
###
|
|
104
|
-
|
|
105
|
-
Available only when `onboardingState` is `onboardingFinished`.
|
|
101
|
+
### Development & Publishing Tools
|
|
106
102
|
|
|
107
103
|
| Tool | Description |
|
|
108
104
|
|------|-------------|
|
|
@@ -174,11 +170,7 @@ src/
|
|
|
174
170
|
logger.ts Structured logging
|
|
175
171
|
|
|
176
172
|
prompt/
|
|
177
|
-
index.ts System prompt builder (
|
|
178
|
-
actions/ Built-in prompts for runCommand actions
|
|
179
|
-
sync.md
|
|
180
|
-
publish.md
|
|
181
|
-
buildFromInitialSpec.md
|
|
173
|
+
index.ts System prompt builder (static prefix + small dynamic tail)
|
|
182
174
|
static/ Behavioral instruction fragments
|
|
183
175
|
identity.md
|
|
184
176
|
intake.md
|
|
@@ -187,7 +179,7 @@ src/
|
|
|
187
179
|
instructions.md
|
|
188
180
|
team.md
|
|
189
181
|
lsp.md
|
|
190
|
-
projectContext.ts Reads
|
|
182
|
+
projectContext.ts Reads project root, app identity, plan status at runtime
|
|
191
183
|
compiled/ Platform docs distilled for agent consumption
|
|
192
184
|
sources/ Prompt source material (hand-maintained)
|
|
193
185
|
|
|
@@ -267,7 +259,7 @@ The headless IPC protocol uses request correlation and a unified response patter
|
|
|
267
259
|
- Messages sent while a turn is running are queued. When the turn ends, all contiguous queued user messages and background results are delivered together as **one merged turn**: the first queued message's `requestId` becomes the turn's primary id (stamped on `turn_started` and all streaming events), each absorbed message echoes its own `user_message` with its original `requestId` and `queued: true`, and at turn end the primary `completed` is emitted first, followed immediately by one `completed {…same outcome, absorbed: true}` per other absorbed `requestId`. Automated-action (`@@automated::…@@`) messages and chain steps never merge — they always run one turn each.
|
|
268
260
|
- A queued **user** message can be promoted to ASAP delivery via `setQueuedDelivery`. ASAP items are pulled into the **running** turn at its next tool boundary (injected as plain user messages — no abort, no restart), echo `user_message` with `queued: true` and their own `requestId`, and get a `{…, absorbed: true}` completed with the turn's outcome at turn end. Promotion deliberately jumps ahead of anything else in the queue, including chain steps. If the turn ends before injection, the tag is ignored and the item drains in normal FIFO order.
|
|
269
261
|
- Background tool completions have three delivery classes (per-tool `backgroundNotify` on the tool definition). `wake` (default): the result is queued as a `background_results` message and may start a turn when the agent is idle. `passive` (e.g. `specSync`): the result never enters the queue and never wakes the agent — it parks in a persisted holding pen and rides the next real turn as a hidden `background_results` entry (so it never appears in `queuedMessages`, never affects queue-derived busy state, and never triggers resume-on-restart). `silent` (e.g. `compactConversation`): the tool block is updated for the UI and the model is never told — its outcome reaches the model by another mechanism. All classes emit `tool_background_complete` immediately. `specSync` also takes a `refreshBuildOverview` flag (set post-deploy / post-milestone, prompted via the publish flow): the run gains the `writeBuildOverview` tool and re-authors the Build Overview from the freshly-reconciled spec — the design-expert render runs foreground within specSync's already-detached run, never nested-background.
|
|
270
|
-
- Compaction lifecycle rides `compaction_started {blocking}` / `compaction_complete {error?}` system events. Every compaction also renders as a normal `compactConversation` tool call
|
|
262
|
+
- Compaction lifecycle rides `compaction_started {blocking}` / `compaction_complete {error?}` system events. Every compaction also renders as a normal `compactConversation` tool call. The **model-invoked** tool is a real background tool (its block completes via `tool_background_complete`; the summary lands in `backgroundResult`). User (`/compact`) and gate-initiated compactions instead get a **synthesized UI-only foreground block** in history — the user is actively waiting on these, so they follow the normal foreground lifecycle: `tool_start` with no result, then a (late) `tool_done {result, isError}` carrying the summary or the error when the compaction finishes. Either way the block is excluded from every API payload — the model receives the summary via the checkpoint prefix instead. Every turn passes a **compaction gate**: it waits for any in-flight compaction and applies the finished checkpoint before running, so no turn ever bills at the uncompacted context. Messages received while a compaction is in flight are queued exactly like mid-turn messages (they appear in `queuedMessages` and drain — with merged-turn semantics — when the compaction completes, on success and failure alike). A `compact` command received **mid-turn** queues too: it's acked immediately with `completed {success: true, queued: true}` (duplicate clicks coalesce onto the queued item), appears in `queuedMessages` as a removable `@@automated::compact@@` user item, and runs as its own drain step when the turn ends — everything queued behind it runs against the compacted history.
|
|
271
263
|
- The caller distinguishes command responses from system events with a single check: `if (msg.requestId)`
|
|
272
264
|
|
|
273
265
|
This enables a simple promise-based RPC layer: send a command with a unique ID, store a pending promise keyed by that ID, resolve it when you see `completed` with the matching ID.
|
|
@@ -287,8 +279,7 @@ Send a user message to the agent.
|
|
|
287
279
|
Fields:
|
|
288
280
|
- `requestId` — caller-provided correlation ID (echoed on all response events)
|
|
289
281
|
- `text` — the user message (required unless `runCommand` is set)
|
|
290
|
-
- `onboardingState` —
|
|
291
|
-
- `viewContext` — `{ mode, openFiles?, activeFile? }` for prompt context
|
|
282
|
+
- `onboardingState` — the project's onboarding phase, reflected in the system prompt's dynamic tail and plan-status behavior. One of: `intake`, `building`, `buildComplete`, `onboardingFinished` (default: `onboardingFinished`)
|
|
292
283
|
- `attachments` — array of `{ url, extractedTextUrl? }` for file attachments
|
|
293
284
|
- `runCommand` — triggers a built-in action prompt (`"sync"`, `"publish"`, `"buildFromInitialSpec"`)
|
|
294
285
|
|
|
@@ -379,7 +370,8 @@ All command responses include the `requestId` from the originating command.
|
|
|
379
370
|
| `user_message` | `text`, `attachments?`, `queued?`, `hidden?` | Echo of a user message entering the turn. Queue-delivered messages (including ASAP items injected mid-turn) carry `queued: true` and their own original `requestId` (a merged turn emits one per absorbed message); idle sends echo with the turn's requestId and no `queued` flag. `hidden: true` marks internal entries (e.g. passive background results) that should not render. |
|
|
380
371
|
| `tool_start` | `id`, `name`, `input`, `partial?`, `parentToolId?` | Tool execution started. `partial: true` means more `tool_start` events will follow for this id (progressive input streaming). |
|
|
381
372
|
| `tool_input_delta` | `id`, `name`, `result`, `parentToolId?` | Progressive tool content (streaming tools only) |
|
|
382
|
-
| `tool_done` | `id`, `name`, `result`, `isError`, `parentToolId?` | Tool execution completed |
|
|
373
|
+
| `tool_done` | `id`, `name`, `result`, `isError`, `parentToolId?` | Tool execution completed. For a background tool this carries the synchronous ack; for a synthesized user/gate compaction block it arrives late, when the compaction finishes, carrying the summary (or error). |
|
|
374
|
+
| `tool_background_complete` | `id`, `name`, `result`, `parentToolId?` | A background tool's detached work finished; `result` belongs in the block's `backgroundResult`. Emitted for all `backgroundNotify` classes. |
|
|
383
375
|
| `status` | `message` | Contextual status label (e.g., "Writing files...") |
|
|
384
376
|
| `error` | `error` | Error message (may precede `completed`) |
|
|
385
377
|
| `history` | `messages` | Response to `get_history` |
|
package/dist/headless.js
CHANGED
|
@@ -628,7 +628,7 @@ function loadProjectRoot() {
|
|
|
628
628
|
## Project Root
|
|
629
629
|
\`${PROJECT_ROOT}\`
|
|
630
630
|
|
|
631
|
-
File paths are relative to this directory. Every tool operates here and bash commands run
|
|
631
|
+
File paths are relative to this directory. Every tool operates here and bash commands run in it.`;
|
|
632
632
|
}
|
|
633
633
|
function loadAppIdentity() {
|
|
634
634
|
try {
|
|
@@ -797,21 +797,9 @@ function buildSystemPrompt(onboardingState) {
|
|
|
797
797
|
{{compiled/design.md}}
|
|
798
798
|
</design>
|
|
799
799
|
|
|
800
|
-
<app_files>
|
|
801
|
-
{{compiled/files.md}}
|
|
802
|
-
</app_files>
|
|
803
|
-
|
|
804
800
|
<interfaces>
|
|
805
801
|
{{compiled/interfaces.md}}
|
|
806
802
|
</interfaces>
|
|
807
|
-
|
|
808
|
-
<scenarios>
|
|
809
|
-
{{compiled/scenarios.md}}
|
|
810
|
-
</scenarios>
|
|
811
|
-
|
|
812
|
-
<secrets>
|
|
813
|
-
{{compiled/secrets.md}}
|
|
814
|
-
</secrets>
|
|
815
803
|
</platform_docs>
|
|
816
804
|
|
|
817
805
|
${loadSkillsCatalog()}
|
|
@@ -824,12 +812,12 @@ ${loadSkillsCatalog()}
|
|
|
824
812
|
{{compiled/msfm.md}}
|
|
825
813
|
</mindstudio_flavored_markdown_spec_docs>
|
|
826
814
|
|
|
827
|
-
|
|
815
|
+
${onboardingState === "intake" ? `<intake_mode_instructions>
|
|
828
816
|
{{static/intake.md}}
|
|
829
|
-
</intake_mode_instructions
|
|
817
|
+
</intake_mode_instructions>` : ""}
|
|
830
818
|
|
|
831
819
|
<spec_authoring_instructions>
|
|
832
|
-
{{static/authoring.md}}
|
|
820
|
+
${onboardingState !== void 0 && onboardingState !== "onboardingFinished" ? "{{static/authoring.md}}" : "{{static/spec-maintenance.md}}"}
|
|
833
821
|
</spec_authoring_instructions>
|
|
834
822
|
|
|
835
823
|
<team>
|
|
@@ -851,7 +839,7 @@ Tool results generally persist in the conversation until a compaction summarizes
|
|
|
851
839
|
</conversation_summaries>
|
|
852
840
|
|
|
853
841
|
<project_onboarding>
|
|
854
|
-
New projects progress through
|
|
842
|
+
New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
|
|
855
843
|
|
|
856
844
|
- **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code. Intake ends with a plan proposal via writePlan.
|
|
857
845
|
- **building**: The user approved the initial plan. The agent is writing the spec and building the app. This can take a while and involves heavy tool use (spec authoring, design expert consultation, code generation, verification, polishing).
|
|
@@ -1378,8 +1366,33 @@ var presentPublishPlanTool = {
|
|
|
1378
1366
|
}
|
|
1379
1367
|
};
|
|
1380
1368
|
|
|
1369
|
+
// src/atomicWrite.ts
|
|
1370
|
+
import fs10 from "fs";
|
|
1371
|
+
import fsp from "fs/promises";
|
|
1372
|
+
var writeFileAtomicSync = (file, data) => {
|
|
1373
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1374
|
+
const fd2 = fs10.openSync(tmp, "w");
|
|
1375
|
+
try {
|
|
1376
|
+
fs10.writeSync(fd2, data);
|
|
1377
|
+
fs10.fsyncSync(fd2);
|
|
1378
|
+
} finally {
|
|
1379
|
+
fs10.closeSync(fd2);
|
|
1380
|
+
}
|
|
1381
|
+
fs10.renameSync(tmp, file);
|
|
1382
|
+
};
|
|
1383
|
+
var writeFileAtomic = async (file, data) => {
|
|
1384
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1385
|
+
const handle = await fsp.open(tmp, "w");
|
|
1386
|
+
try {
|
|
1387
|
+
await handle.writeFile(data);
|
|
1388
|
+
await handle.sync();
|
|
1389
|
+
} finally {
|
|
1390
|
+
await handle.close();
|
|
1391
|
+
}
|
|
1392
|
+
await fsp.rename(tmp, file);
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1381
1395
|
// src/tools/spec/writePlan.ts
|
|
1382
|
-
import fs10 from "fs/promises";
|
|
1383
1396
|
var PLAN_FILE = ".remy-plan.md";
|
|
1384
1397
|
var writePlanTool = {
|
|
1385
1398
|
definition: {
|
|
@@ -1403,7 +1416,7 @@ status: pending
|
|
|
1403
1416
|
---
|
|
1404
1417
|
|
|
1405
1418
|
${content}`;
|
|
1406
|
-
await
|
|
1419
|
+
await writeFileAtomic(PLAN_FILE, file);
|
|
1407
1420
|
return "Plan written to .remy-plan.md. Waiting for user approval.";
|
|
1408
1421
|
}
|
|
1409
1422
|
};
|
|
@@ -1439,13 +1452,13 @@ var updatePlanStatusTool = {
|
|
|
1439
1452
|
return "No plan file found.";
|
|
1440
1453
|
}
|
|
1441
1454
|
if (status === "rejected") {
|
|
1442
|
-
await fs11.unlink(PLAN_FILE2)
|
|
1455
|
+
await fs11.unlink(PLAN_FILE2).catch(() => {
|
|
1456
|
+
});
|
|
1443
1457
|
return "Plan rejected and removed.";
|
|
1444
1458
|
}
|
|
1445
|
-
await
|
|
1459
|
+
await writeFileAtomic(
|
|
1446
1460
|
PLAN_FILE2,
|
|
1447
|
-
content.replace(/^status:\s*\w+/m, `status: ${status}`)
|
|
1448
|
-
"utf-8"
|
|
1461
|
+
content.replace(/^status:\s*\w+/m, `status: ${status}`)
|
|
1449
1462
|
);
|
|
1450
1463
|
return "Plan approved. Proceeding with implementation.";
|
|
1451
1464
|
}
|
|
@@ -2975,7 +2988,8 @@ var CONTENT_TYPES = {
|
|
|
2975
2988
|
".jpg": "image/jpeg",
|
|
2976
2989
|
".jpeg": "image/jpeg",
|
|
2977
2990
|
".gif": "image/gif",
|
|
2978
|
-
".webp": "image/webp"
|
|
2991
|
+
".webp": "image/webp",
|
|
2992
|
+
".svg": "image/svg+xml"
|
|
2979
2993
|
};
|
|
2980
2994
|
var hosted = /* @__PURE__ */ new Map();
|
|
2981
2995
|
function isFetchableUrl(ref) {
|
|
@@ -3582,7 +3596,8 @@ async function runSubAgent(config) {
|
|
|
3582
3596
|
background,
|
|
3583
3597
|
acquireLock,
|
|
3584
3598
|
onBackgroundComplete,
|
|
3585
|
-
captureArtifacts
|
|
3599
|
+
captureArtifacts,
|
|
3600
|
+
cachePolicy = "run"
|
|
3586
3601
|
} = config;
|
|
3587
3602
|
const artifacts = {};
|
|
3588
3603
|
const bgAbort = background ? new AbortController() : null;
|
|
@@ -3677,6 +3692,7 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3677
3692
|
system: fullSystem,
|
|
3678
3693
|
messages: cleanMessagesForApi(messages),
|
|
3679
3694
|
tools: tools2,
|
|
3695
|
+
cachePolicy,
|
|
3680
3696
|
signal
|
|
3681
3697
|
},
|
|
3682
3698
|
{
|
|
@@ -5243,7 +5259,7 @@ function load() {
|
|
|
5243
5259
|
}
|
|
5244
5260
|
function save(indices) {
|
|
5245
5261
|
try {
|
|
5246
|
-
|
|
5262
|
+
writeFileAtomicSync(SAMPLE_FILE, JSON.stringify(indices));
|
|
5247
5263
|
} catch {
|
|
5248
5264
|
}
|
|
5249
5265
|
}
|
|
@@ -5494,6 +5510,8 @@ async function runDesignExpert(opts, context) {
|
|
|
5494
5510
|
}),
|
|
5495
5511
|
task: opts.task,
|
|
5496
5512
|
history: history.length > 0 ? history : void 0,
|
|
5513
|
+
// History-carrying thread re-sent across parent turns — 1h-TTL profile.
|
|
5514
|
+
cachePolicy: "conversation",
|
|
5497
5515
|
tools: opts.render ? DESIGN_EXPERT_RENDER_TOOLS : DESIGN_EXPERT_TOOLS,
|
|
5498
5516
|
externalTools: /* @__PURE__ */ new Set(),
|
|
5499
5517
|
executeTool: (name, input, toolCallId, onLog, sams) => {
|
|
@@ -5761,6 +5779,8 @@ var productVisionTool = {
|
|
|
5761
5779
|
system: getProductVisionPrompt(),
|
|
5762
5780
|
task: input.task,
|
|
5763
5781
|
history: history.length > 0 ? history : void 0,
|
|
5782
|
+
// History-carrying thread re-sent across parent turns — 1h-TTL profile.
|
|
5783
|
+
cachePolicy: "conversation",
|
|
5764
5784
|
tools: VISION_TOOLS,
|
|
5765
5785
|
externalTools: /* @__PURE__ */ new Set(),
|
|
5766
5786
|
executeTool: (name, input2, toolCallId, _onLog, sams) => {
|
|
@@ -6228,7 +6248,7 @@ var SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6228
6248
|
"runAutomatedBrowserTest",
|
|
6229
6249
|
"askMindStudioSdk"
|
|
6230
6250
|
]);
|
|
6231
|
-
function getToolDefinitions(
|
|
6251
|
+
function getToolDefinitions() {
|
|
6232
6252
|
return ALL_TOOLS.map((t) => t.definition);
|
|
6233
6253
|
}
|
|
6234
6254
|
function getToolByName(name) {
|
|
@@ -6612,7 +6632,11 @@ Write the summary of the conversation above, following your instructions.`;
|
|
|
6612
6632
|
messages: [{ role: "user", content: userContent }],
|
|
6613
6633
|
// Always empty. With a toolset available the model picks `tool_use` over
|
|
6614
6634
|
// producing a summary, leaving summaryText empty.
|
|
6615
|
-
tools: []
|
|
6635
|
+
tools: [],
|
|
6636
|
+
// Each summary call carries a unique 100-200KB chunk that is never
|
|
6637
|
+
// re-read (parallel siblings can't read each other's in-flight writes
|
|
6638
|
+
// either) — a cache write here is pure waste.
|
|
6639
|
+
cachePolicy: "oneshot"
|
|
6616
6640
|
})) {
|
|
6617
6641
|
if (event.type === "text") {
|
|
6618
6642
|
summaryText += event.text;
|
|
@@ -6648,11 +6672,6 @@ import path11 from "path";
|
|
|
6648
6672
|
var log10 = createLogger("session");
|
|
6649
6673
|
var SESSION_FILE = ".remy-session.json";
|
|
6650
6674
|
var ARCHIVE_DIR = ".logs/sessions";
|
|
6651
|
-
function writeFileAtomicSync(file, data) {
|
|
6652
|
-
const tmp = `${file}.tmp`;
|
|
6653
|
-
fs21.writeFileSync(tmp, data, "utf-8");
|
|
6654
|
-
fs21.renameSync(tmp, file);
|
|
6655
|
-
}
|
|
6656
6675
|
var ROTATE_THRESHOLD_BYTES = 32 * 1024 * 1024;
|
|
6657
6676
|
var RETAIN_TAIL_BYTES = 16 * 1024 * 1024;
|
|
6658
6677
|
var ARCHIVE_RETENTION_BYTES = 64 * 1024 * 1024;
|
|
@@ -6681,6 +6700,12 @@ function loadSession(state) {
|
|
|
6681
6700
|
return true;
|
|
6682
6701
|
}
|
|
6683
6702
|
} catch {
|
|
6703
|
+
try {
|
|
6704
|
+
const quarantine = `${SESSION_FILE}.corrupt-${Date.now()}`;
|
|
6705
|
+
fs21.renameSync(SESSION_FILE, quarantine);
|
|
6706
|
+
log10.warn(`Session file unreadable \u2014 quarantined to ${quarantine}`);
|
|
6707
|
+
} catch {
|
|
6708
|
+
}
|
|
6684
6709
|
}
|
|
6685
6710
|
return false;
|
|
6686
6711
|
}
|
|
@@ -7119,13 +7144,10 @@ function isDedicatedBrandFile(filePath) {
|
|
|
7119
7144
|
const { type } = parseFrontmatter3(filePath);
|
|
7120
7145
|
return type.startsWith("design/color") || type.startsWith("design/typography");
|
|
7121
7146
|
}
|
|
7122
|
-
function isBrandRelevant(filePath) {
|
|
7123
|
-
return filePath === path12.join("src", "app.md") || isDedicatedBrandFile(filePath);
|
|
7124
|
-
}
|
|
7125
7147
|
function computeInputHash() {
|
|
7126
7148
|
const entries = [];
|
|
7127
7149
|
for (const filePath of walkMdFiles2("src")) {
|
|
7128
|
-
if (
|
|
7150
|
+
if (isDedicatedBrandFile(filePath)) {
|
|
7129
7151
|
entries.push({ path: filePath, content: readSafe(filePath) });
|
|
7130
7152
|
}
|
|
7131
7153
|
}
|
|
@@ -7210,7 +7232,10 @@ async function extractBrand(apiConfig, model) {
|
|
|
7210
7232
|
subAgentId: "brandExtractor",
|
|
7211
7233
|
system: EXTRACT_PROMPT,
|
|
7212
7234
|
messages: [{ role: "user", content: corpus }],
|
|
7213
|
-
tools: []
|
|
7235
|
+
tools: [],
|
|
7236
|
+
// One call per extraction over a corpus that changes with every spec
|
|
7237
|
+
// edit — never re-read, so a cache write is pure waste.
|
|
7238
|
+
cachePolicy: "oneshot"
|
|
7214
7239
|
})) {
|
|
7215
7240
|
if (event.type === "text") {
|
|
7216
7241
|
responseText += event.text;
|
|
@@ -7251,8 +7276,8 @@ var BRAND_CORPUS_CHAR_LIMIT = 24e5;
|
|
|
7251
7276
|
function buildCorpus() {
|
|
7252
7277
|
const all = walkMdFiles2("src");
|
|
7253
7278
|
const ordered = [
|
|
7254
|
-
...all.filter(
|
|
7255
|
-
...all.filter((f) => !
|
|
7279
|
+
...all.filter(isDedicatedBrandFile),
|
|
7280
|
+
...all.filter((f) => !isDedicatedBrandFile(f))
|
|
7256
7281
|
];
|
|
7257
7282
|
const files = [];
|
|
7258
7283
|
const manifest = readBrandManifest();
|
|
@@ -7392,11 +7417,9 @@ function pickFont(raw) {
|
|
|
7392
7417
|
return out;
|
|
7393
7418
|
}
|
|
7394
7419
|
function persistBrand(brand, inputHash) {
|
|
7395
|
-
|
|
7396
|
-
fs22.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
|
|
7397
|
-
fs22.renameSync(tmp, BRAND_FILE);
|
|
7420
|
+
writeFileAtomicSync(BRAND_FILE, JSON.stringify(brand, null, 2));
|
|
7398
7421
|
const cache = { inputHash, generatedAt: Date.now() };
|
|
7399
|
-
|
|
7422
|
+
writeFileAtomicSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
7400
7423
|
}
|
|
7401
7424
|
function readCache() {
|
|
7402
7425
|
try {
|
|
@@ -7687,7 +7710,11 @@ function friendlyError(raw) {
|
|
|
7687
7710
|
|
|
7688
7711
|
// src/agent.ts
|
|
7689
7712
|
var log14 = createLogger("agent");
|
|
7690
|
-
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
|
|
7713
|
+
var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
|
|
7714
|
+
"writeSpec",
|
|
7715
|
+
"editSpec",
|
|
7716
|
+
"setProjectMetadata"
|
|
7717
|
+
]);
|
|
7691
7718
|
function getTextContent(blocks) {
|
|
7692
7719
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
7693
7720
|
}
|
|
@@ -7737,7 +7764,7 @@ async function runTurn(params) {
|
|
|
7737
7764
|
toolRegistry,
|
|
7738
7765
|
onBackgroundComplete
|
|
7739
7766
|
} = params;
|
|
7740
|
-
const tools2 = getToolDefinitions(
|
|
7767
|
+
const tools2 = getToolDefinitions();
|
|
7741
7768
|
const buildModelOverride = buildModel ? filterModelPicks({ parent: buildModel }).parent : void 0;
|
|
7742
7769
|
const baseline = resolveModel("parent", state.models, model);
|
|
7743
7770
|
const parentModel = buildModelOverride ?? baseline;
|
|
@@ -7945,6 +7972,9 @@ async function runTurn(params) {
|
|
|
7945
7972
|
system,
|
|
7946
7973
|
messages: cleanMessagesForApi(state.messages),
|
|
7947
7974
|
tools: tools2,
|
|
7975
|
+
// The parent is one long-lived conversation re-read across turns
|
|
7976
|
+
// with multi-minute gaps — the 1h-TTL profile.
|
|
7977
|
+
cachePolicy: "conversation",
|
|
7948
7978
|
signal
|
|
7949
7979
|
},
|
|
7950
7980
|
{
|
|
@@ -8481,16 +8511,15 @@ ${lines.join("\n")}`;
|
|
|
8481
8511
|
}
|
|
8482
8512
|
|
|
8483
8513
|
// src/headless/planFile.ts
|
|
8484
|
-
import { readFileSync,
|
|
8514
|
+
import { readFileSync, unlinkSync } from "fs";
|
|
8485
8515
|
var PLAN_FILE3 = ".remy-plan.md";
|
|
8486
8516
|
function applyPlanFileSideEffect(rawText) {
|
|
8487
8517
|
if (hasSentinel(rawText, "approvePlan") || hasSentinel(rawText, "approveInitialPlan")) {
|
|
8488
8518
|
try {
|
|
8489
8519
|
const plan = readFileSync(PLAN_FILE3, "utf-8");
|
|
8490
|
-
|
|
8520
|
+
writeFileAtomicSync(
|
|
8491
8521
|
PLAN_FILE3,
|
|
8492
|
-
plan.replace(/^status:\s*pending/m, "status: approved")
|
|
8493
|
-
"utf-8"
|
|
8522
|
+
plan.replace(/^status:\s*pending/m, "status: approved")
|
|
8494
8523
|
);
|
|
8495
8524
|
} catch {
|
|
8496
8525
|
}
|
|
@@ -8503,7 +8532,7 @@ function applyPlanFileSideEffect(rawText) {
|
|
|
8503
8532
|
}
|
|
8504
8533
|
|
|
8505
8534
|
// src/headless/stats.ts
|
|
8506
|
-
import { readFileSync as readFileSync2
|
|
8535
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8507
8536
|
var STATS_FILE = ".remy-stats.json";
|
|
8508
8537
|
function createSessionStats() {
|
|
8509
8538
|
return {
|
|
@@ -8540,7 +8569,7 @@ function loadPassiveResults() {
|
|
|
8540
8569
|
}
|
|
8541
8570
|
function writeStats(stats, queue, passiveResults) {
|
|
8542
8571
|
try {
|
|
8543
|
-
|
|
8572
|
+
writeFileAtomicSync(
|
|
8544
8573
|
STATS_FILE,
|
|
8545
8574
|
JSON.stringify({
|
|
8546
8575
|
...stats,
|
|
@@ -8769,9 +8798,7 @@ var HeadlessSession = class {
|
|
|
8769
8798
|
name: "compactConversation",
|
|
8770
8799
|
input: {},
|
|
8771
8800
|
startedAt: Date.now(),
|
|
8772
|
-
|
|
8773
|
-
uiOnly: true,
|
|
8774
|
-
result: "Compaction started in the background."
|
|
8801
|
+
uiOnly: true
|
|
8775
8802
|
}
|
|
8776
8803
|
]
|
|
8777
8804
|
});
|
|
@@ -8780,15 +8807,7 @@ var HeadlessSession = class {
|
|
|
8780
8807
|
type: "tool_start",
|
|
8781
8808
|
id,
|
|
8782
8809
|
name: "compactConversation",
|
|
8783
|
-
input: {}
|
|
8784
|
-
background: true
|
|
8785
|
-
});
|
|
8786
|
-
this.onEvent({
|
|
8787
|
-
type: "tool_done",
|
|
8788
|
-
id,
|
|
8789
|
-
name: "compactConversation",
|
|
8790
|
-
result: "Compaction started in the background.",
|
|
8791
|
-
isError: false
|
|
8810
|
+
input: {}
|
|
8792
8811
|
});
|
|
8793
8812
|
}
|
|
8794
8813
|
} else {
|
|
@@ -8797,11 +8816,31 @@ var HeadlessSession = class {
|
|
|
8797
8816
|
if (this.syntheticCompactionId) {
|
|
8798
8817
|
const id = this.syntheticCompactionId;
|
|
8799
8818
|
this.syntheticCompactionId = null;
|
|
8800
|
-
|
|
8819
|
+
const result = event.error ? `Error: ${event.error}` : formatSummariesResult(event.summaries ?? []);
|
|
8820
|
+
const isError = !!event.error;
|
|
8821
|
+
for (let i = this.state.messages.length - 1; i >= 0; i--) {
|
|
8822
|
+
const msg = this.state.messages[i];
|
|
8823
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
8824
|
+
continue;
|
|
8825
|
+
}
|
|
8826
|
+
const block = msg.content.find(
|
|
8827
|
+
(b) => b.type === "tool" && b.id === id
|
|
8828
|
+
);
|
|
8829
|
+
if (block && block.type === "tool") {
|
|
8830
|
+
block.result = result;
|
|
8831
|
+
block.isError = isError;
|
|
8832
|
+
block.completedAt = Date.now();
|
|
8833
|
+
saveSession(this.state);
|
|
8834
|
+
break;
|
|
8835
|
+
}
|
|
8836
|
+
}
|
|
8837
|
+
this.onEvent({
|
|
8838
|
+
type: "tool_done",
|
|
8801
8839
|
id,
|
|
8802
|
-
"compactConversation",
|
|
8803
|
-
|
|
8804
|
-
|
|
8840
|
+
name: "compactConversation",
|
|
8841
|
+
result,
|
|
8842
|
+
isError
|
|
8843
|
+
});
|
|
8805
8844
|
}
|
|
8806
8845
|
this.sessionStats.compactionInProgress = false;
|
|
8807
8846
|
if (!event.error) {
|