@mindstudio-ai/remy 0.1.330 → 0.1.332
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 +3 -4
- package/dist/headless.d.ts +22 -0
- package/dist/headless.js +270 -26
- package/dist/index.js +281 -27
- package/dist/prompt/compiled/dev-and-deploy.md +9 -0
- package/dist/prompt/compiled/platform.md +1 -1
- package/dist/prompt/skills/dataSources.md +11 -2
- package/dist/prompt/skills/publishing.md +60 -6
- package/dist/prompt/static/instructions.md +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,12 +279,11 @@ Send a user message to the agent.
|
|
|
279
279
|
|
|
280
280
|
Fields:
|
|
281
281
|
- `requestId` — caller-provided correlation ID (echoed on all response events)
|
|
282
|
-
- `text` — the user message
|
|
282
|
+
- `text` — the user message
|
|
283
283
|
- `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`)
|
|
284
284
|
- `attachments` — array of `{ url, extractedTextUrl? }` for file attachments
|
|
285
|
-
- `runCommand` — triggers a built-in action prompt (`"sync"`, `"publish"`, `"buildFromInitialSpec"`)
|
|
286
285
|
|
|
287
|
-
|
|
286
|
+
Built-in actions are triggered by sending their sentinel as the `text` — `@@automated::<name>@@` — which resolves to the action's prompt. See `src/automatedActions/`.
|
|
288
287
|
|
|
289
288
|
#### `tool_result`
|
|
290
289
|
|
|
@@ -302,7 +301,7 @@ Return the full conversation history.
|
|
|
302
301
|
{"action": "get_history", "requestId": "r2"}
|
|
303
302
|
```
|
|
304
303
|
|
|
305
|
-
Messages with `hidden: true`
|
|
304
|
+
Messages with `hidden: true` are internal plumbing and must not be displayed: the platform's passive sweeps (background sub-agent results, the workspace-behind note) and the loop guard's nudges to the model. They are sent to the LLM and are not anything a person said, whatever role they carry. Automated actions are *not* hidden — clients render those from their `@@automated::` prefix.
|
|
306
305
|
|
|
307
306
|
The response also includes `queuedMessages` — the current pending-queue snapshot (possibly empty) — so a client can render the queue immediately on connect/reconnect. Live changes after that arrive via `queue_changed`.
|
|
308
307
|
|
package/dist/headless.d.ts
CHANGED
|
@@ -93,6 +93,15 @@ declare class HeadlessSession {
|
|
|
93
93
|
* Persisted to .remy-stats.json alongside the queue.
|
|
94
94
|
*/
|
|
95
95
|
private passivePen;
|
|
96
|
+
/**
|
|
97
|
+
* The workspace-behind note, and the upstream tip it was raised for.
|
|
98
|
+
*
|
|
99
|
+
* Rides the same sweep as the passive pen and for the same reason: a
|
|
100
|
+
* workspace that is behind is worth knowing about before the next piece of
|
|
101
|
+
* work, and worth nothing at all if nobody is working — so it must never
|
|
102
|
+
* initiate a turn of its own.
|
|
103
|
+
*/
|
|
104
|
+
private workspaceNotice;
|
|
96
105
|
private pendingTools;
|
|
97
106
|
private earlyResults;
|
|
98
107
|
private pendingBlockUpdates;
|
|
@@ -119,6 +128,19 @@ declare class HeadlessSession {
|
|
|
119
128
|
private dispatchSimple;
|
|
120
129
|
/** Persist sessionStats + queue snapshot + passive pen to .remy-stats.json. */
|
|
121
130
|
private persistStats;
|
|
131
|
+
/**
|
|
132
|
+
* Park a note if this workspace is missing work that is already in production.
|
|
133
|
+
*
|
|
134
|
+
* Once per upstream tip rather than once per boot. Remy restarts for reasons
|
|
135
|
+
* that have nothing to do with the repo — a pod recycle, a crash, a new
|
|
136
|
+
* session — and re-raising the same note on each of those turns a useful
|
|
137
|
+
* signal into nagging. A colleague publishing again moves the tip, which
|
|
138
|
+
* re-arms it; bringing the workspace current means there is nothing to raise.
|
|
139
|
+
*
|
|
140
|
+
* Never throws: this is called unawaited from the boot path, so an unhandled
|
|
141
|
+
* rejection here would be a crash at the least recoverable moment.
|
|
142
|
+
*/
|
|
143
|
+
private checkUpstream;
|
|
122
144
|
/** Apply queued tool block updates to state.messages. Safe to call any time. */
|
|
123
145
|
private applyPendingBlockUpdates;
|
|
124
146
|
/**
|
package/dist/headless.js
CHANGED
|
@@ -183,6 +183,10 @@ var RepetitionDetector = class {
|
|
|
183
183
|
|
|
184
184
|
// src/api.ts
|
|
185
185
|
var log2 = createLogger("api");
|
|
186
|
+
function sandboxSessionHeader() {
|
|
187
|
+
const sessionId = process.env.MINDSTUDIO_SESSION_ID;
|
|
188
|
+
return sessionId ? { "x-sandbox-session": sessionId } : {};
|
|
189
|
+
}
|
|
186
190
|
async function* streamChat(params) {
|
|
187
191
|
const { baseUrl: baseUrl2, apiKey, signal, requestId, model, ...rest } = params;
|
|
188
192
|
const url = `${baseUrl2}/_internal/v2/agent/remy/chat`;
|
|
@@ -202,7 +206,8 @@ async function* streamChat(params) {
|
|
|
202
206
|
method: "POST",
|
|
203
207
|
headers: {
|
|
204
208
|
"Content-Type": "application/json",
|
|
205
|
-
Authorization: `Bearer ${apiKey}
|
|
209
|
+
Authorization: `Bearer ${apiKey}`,
|
|
210
|
+
...sandboxSessionHeader()
|
|
206
211
|
},
|
|
207
212
|
body: JSON.stringify(requestBody),
|
|
208
213
|
signal
|
|
@@ -466,7 +471,8 @@ async function generateBackgroundAck(params) {
|
|
|
466
471
|
method: "POST",
|
|
467
472
|
headers: {
|
|
468
473
|
"Content-Type": "application/json",
|
|
469
|
-
Authorization: `Bearer ${params.apiConfig.apiKey}
|
|
474
|
+
Authorization: `Bearer ${params.apiConfig.apiKey}`,
|
|
475
|
+
...sandboxSessionHeader()
|
|
470
476
|
},
|
|
471
477
|
body: JSON.stringify({
|
|
472
478
|
appId: params.apiConfig.appId,
|
|
@@ -658,6 +664,7 @@ var TEXT_MODELS = {
|
|
|
658
664
|
"kimi-k3": { forceCompactAt: 85e4 },
|
|
659
665
|
"deepseek-v4-flash-0731": { forceCompactAt: 85e4 },
|
|
660
666
|
"deepseek-v4-pro": { forceCompactAt: 85e4 },
|
|
667
|
+
"deepseek-v4.1-flash": { forceCompactAt: 85e4 },
|
|
661
668
|
"qwen3.8-2.4t-a95b-deepinfra": { forceCompactAt: 2e5 },
|
|
662
669
|
// 262K window
|
|
663
670
|
"qwen3.8-27b-deepinfra": { forceCompactAt: 2e5 },
|
|
@@ -1140,6 +1147,33 @@ ${xml}
|
|
|
1140
1147
|
</background_results>`;
|
|
1141
1148
|
return automatedMessage("background_results", body);
|
|
1142
1149
|
}
|
|
1150
|
+
function buildWorkspaceStatusMessage(status) {
|
|
1151
|
+
const lines = [
|
|
1152
|
+
`Default branch tip: ${status.upstream}`,
|
|
1153
|
+
`Checked at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1154
|
+
`Behind by: ${status.behind === null ? "unknown (could not count \u2014 treat the list below as what is missing)" : `${status.behind} commit(s)`}`,
|
|
1155
|
+
`Unpushed commits here: ${status.ahead === null ? "unknown" : status.ahead}`,
|
|
1156
|
+
`Uncommitted changes here: ${status.dirty === "unknown" ? "could not tell \u2014 check with `git status` before relying on it" : status.dirty ? "yes" : "no"}`
|
|
1157
|
+
];
|
|
1158
|
+
if (status.incoming.length > 0) {
|
|
1159
|
+
lines.push(
|
|
1160
|
+
"",
|
|
1161
|
+
status.incomingTruncated ? `Landed on the default branch since, newest first (first ${status.incoming.length}; there are more):` : "Landed on the default branch since, newest first:",
|
|
1162
|
+
...status.incoming.map((line) => `- ${escapeForEnvelope(line)}`)
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const body = `<workspace_status>
|
|
1166
|
+
Automated note about this workspace, checked when Remy started \u2014 the timestamp below says when, and it may have been a while. This block is not from the user; anything outside it is. Nobody has seen it, and nobody can see it; it is context for you.
|
|
1167
|
+
|
|
1168
|
+
This copy of the app does not have everything on the branch production builds from.
|
|
1169
|
+
|
|
1170
|
+
${lines.join("\n")}
|
|
1171
|
+
</workspace_status>`;
|
|
1172
|
+
return automatedMessage("workspace_status", body);
|
|
1173
|
+
}
|
|
1174
|
+
function escapeForEnvelope(text) {
|
|
1175
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/@@automated/g, "@ @automated");
|
|
1176
|
+
}
|
|
1143
1177
|
function mergeBackgroundResultsMessages(messages) {
|
|
1144
1178
|
const results = [];
|
|
1145
1179
|
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
@@ -3759,6 +3793,7 @@ var INTERNAL_PAYLOAD_MARKERS = [
|
|
|
3759
3793
|
"[INTERRUPTED]",
|
|
3760
3794
|
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
3761
3795
|
"<background_results>",
|
|
3796
|
+
"<workspace_status>",
|
|
3762
3797
|
"<tool_result"
|
|
3763
3798
|
];
|
|
3764
3799
|
function sanitizeStatusText(text) {
|
|
@@ -3813,7 +3848,11 @@ function startStatusWatcher(config) {
|
|
|
3813
3848
|
method: "POST",
|
|
3814
3849
|
headers: {
|
|
3815
3850
|
"Content-Type": "application/json",
|
|
3816
|
-
Authorization: `Bearer ${apiConfig.apiKey}
|
|
3851
|
+
Authorization: `Bearer ${apiConfig.apiKey}`,
|
|
3852
|
+
// Also a liveness signal for this box, and the most frequent one there is — this ticks
|
|
3853
|
+
// every few seconds for as long as the agent is working, which is exactly the window in
|
|
3854
|
+
// which the user may have backgrounded the tab and stopped its own keepalive.
|
|
3855
|
+
...sandboxSessionHeader()
|
|
3817
3856
|
},
|
|
3818
3857
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
3819
3858
|
signal
|
|
@@ -4830,7 +4869,7 @@ TypeScript running in a sandboxed environment. Any npm package can be installed.
|
|
|
4830
4869
|
- Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
|
|
4831
4870
|
- Built-in app-managed auth. Opt-in via manifest \u2014 developer builds login UI, platform handles verification codes (email-code, sms-code) and cookie sessions. API key auth for programmatic access. No OAuth, no social login (no Apple, Google, Facebook, or GitHub sign-in). Backend methods use auth.requireRole() for access control.
|
|
4832
4871
|
- Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
|
|
4833
|
-
- Git-native deployment.
|
|
4872
|
+
- Git-native deployment. Each person with edit access works in their own copy of the app, and everyone publishes to the same default branch, which is what deploys. Pushing any other branch builds a private preview instead. Rollback is a git revert.
|
|
4834
4873
|
|
|
4835
4874
|
## MindStudio SDK
|
|
4836
4875
|
|
|
@@ -7866,6 +7905,9 @@ function serializeForSummary(messages) {
|
|
|
7866
7905
|
}
|
|
7867
7906
|
continue;
|
|
7868
7907
|
}
|
|
7908
|
+
if (msg.role === "user" && (msg.hidden || typeof msg.content === "string" && isAutomatedMessage(msg.content))) {
|
|
7909
|
+
continue;
|
|
7910
|
+
}
|
|
7869
7911
|
if (typeof msg.content === "string") {
|
|
7870
7912
|
if (msg.content.trim()) {
|
|
7871
7913
|
lines.push(`[${msg.role}]: ${msg.content}`);
|
|
@@ -9025,7 +9067,10 @@ function parsePartialJson(jsonString) {
|
|
|
9025
9067
|
}
|
|
9026
9068
|
|
|
9027
9069
|
// src/automatedActions/resolve.ts
|
|
9028
|
-
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set([
|
|
9070
|
+
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set([
|
|
9071
|
+
"background_results",
|
|
9072
|
+
"workspace_status"
|
|
9073
|
+
]);
|
|
9029
9074
|
function resolveAction(text) {
|
|
9030
9075
|
const parsed = parseSentinel(text);
|
|
9031
9076
|
if (!parsed) {
|
|
@@ -9401,7 +9446,9 @@ async function runTurn(params) {
|
|
|
9401
9446
|
for (const entry of keptEntries) {
|
|
9402
9447
|
appendEntry(entry);
|
|
9403
9448
|
}
|
|
9404
|
-
const isFirstMessage = state.messages.filter(
|
|
9449
|
+
const isFirstMessage = state.messages.filter(
|
|
9450
|
+
(m) => m.role === "user" && !m.hidden && !(typeof m.content === "string" && isAutomatedMessage(m.content))
|
|
9451
|
+
).length === 1;
|
|
9405
9452
|
const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
9406
9453
|
"markBuildComplete",
|
|
9407
9454
|
"setProjectMetadata",
|
|
@@ -10295,7 +10342,24 @@ function loadPassiveResults() {
|
|
|
10295
10342
|
}
|
|
10296
10343
|
return [];
|
|
10297
10344
|
}
|
|
10298
|
-
function
|
|
10345
|
+
function emptyWorkspaceNotice() {
|
|
10346
|
+
return { pendingNote: null, lastNotedUpstream: null };
|
|
10347
|
+
}
|
|
10348
|
+
function loadWorkspaceNotice() {
|
|
10349
|
+
try {
|
|
10350
|
+
const stats = JSON.parse(readFileSync2(STATS_FILE, "utf-8"));
|
|
10351
|
+
const notice = stats.workspaceNotice;
|
|
10352
|
+
if (notice && typeof notice === "object") {
|
|
10353
|
+
return {
|
|
10354
|
+
pendingNote: typeof notice.pendingNote === "string" ? notice.pendingNote : null,
|
|
10355
|
+
lastNotedUpstream: typeof notice.lastNotedUpstream === "string" ? notice.lastNotedUpstream : null
|
|
10356
|
+
};
|
|
10357
|
+
}
|
|
10358
|
+
} catch {
|
|
10359
|
+
}
|
|
10360
|
+
return emptyWorkspaceNotice();
|
|
10361
|
+
}
|
|
10362
|
+
function writeStats(stats, queue, passiveResults, suggestCompactAt, workspaceNotice) {
|
|
10299
10363
|
try {
|
|
10300
10364
|
writeFileAtomicSync(
|
|
10301
10365
|
STATS_FILE,
|
|
@@ -10303,13 +10367,134 @@ function writeStats(stats, queue, passiveResults, suggestCompactAt) {
|
|
|
10303
10367
|
...stats,
|
|
10304
10368
|
suggestCompactAt,
|
|
10305
10369
|
queue,
|
|
10306
|
-
passiveResults
|
|
10370
|
+
passiveResults,
|
|
10371
|
+
workspaceNotice
|
|
10307
10372
|
})
|
|
10308
10373
|
);
|
|
10309
10374
|
} catch {
|
|
10310
10375
|
}
|
|
10311
10376
|
}
|
|
10312
10377
|
|
|
10378
|
+
// src/git/upstreamStatus.ts
|
|
10379
|
+
import { execFile as execFile2 } from "child_process";
|
|
10380
|
+
var log17 = createLogger("upstream");
|
|
10381
|
+
var DEFAULT_BRANCH = "main";
|
|
10382
|
+
var MAX_INCOMING = 20;
|
|
10383
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
10384
|
+
var GIT_TIMEOUT_MS = 1e4;
|
|
10385
|
+
var MAX_BUFFER_BYTES = 32 * 1024 * 1024;
|
|
10386
|
+
function git(args, timeout = GIT_TIMEOUT_MS) {
|
|
10387
|
+
return new Promise((resolve4) => {
|
|
10388
|
+
const child = execFile2(
|
|
10389
|
+
"git",
|
|
10390
|
+
args,
|
|
10391
|
+
{
|
|
10392
|
+
cwd: PROJECT_ROOT,
|
|
10393
|
+
encoding: "utf-8",
|
|
10394
|
+
timeout,
|
|
10395
|
+
maxBuffer: MAX_BUFFER_BYTES,
|
|
10396
|
+
// No controlling tty in a box, but an inherited one anywhere else would
|
|
10397
|
+
// let a credential prompt hold the whole timeout open.
|
|
10398
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
10399
|
+
// execFile's `timeout` sends SIGTERM, which a wedged `git-remote-https`
|
|
10400
|
+
// can ignore. Escalate so the boot path cannot be held by one.
|
|
10401
|
+
killSignal: "SIGKILL"
|
|
10402
|
+
},
|
|
10403
|
+
(err, stdout, stderr) => {
|
|
10404
|
+
resolve4({
|
|
10405
|
+
ok: !err,
|
|
10406
|
+
stdout: (stdout ?? "").trim(),
|
|
10407
|
+
// Kept so a failure can say WHY. For a probe whose entire failure
|
|
10408
|
+
// model is silence, this is the only line that makes it debuggable.
|
|
10409
|
+
error: err ? (stderr || "").trim() || err.message : null
|
|
10410
|
+
});
|
|
10411
|
+
}
|
|
10412
|
+
);
|
|
10413
|
+
child.on("error", () => {
|
|
10414
|
+
});
|
|
10415
|
+
});
|
|
10416
|
+
}
|
|
10417
|
+
async function readUpstreamStatus() {
|
|
10418
|
+
const inRepo = await git(["rev-parse", "--git-dir"]);
|
|
10419
|
+
if (!inRepo.ok) {
|
|
10420
|
+
return null;
|
|
10421
|
+
}
|
|
10422
|
+
const fetched = await git(
|
|
10423
|
+
["fetch", "origin", DEFAULT_BRANCH],
|
|
10424
|
+
FETCH_TIMEOUT_MS
|
|
10425
|
+
);
|
|
10426
|
+
if (!fetched.ok) {
|
|
10427
|
+
log17.info(
|
|
10428
|
+
`fetch failed, falling back to the origin/${DEFAULT_BRANCH} on disk: ${fetched.error}`
|
|
10429
|
+
);
|
|
10430
|
+
}
|
|
10431
|
+
const [upstreamRef, headRef] = await Promise.all([
|
|
10432
|
+
git(["rev-parse", `origin/${DEFAULT_BRANCH}`]),
|
|
10433
|
+
git(["rev-parse", "HEAD"])
|
|
10434
|
+
]);
|
|
10435
|
+
if (!upstreamRef.ok || !headRef.ok) {
|
|
10436
|
+
return null;
|
|
10437
|
+
}
|
|
10438
|
+
const upstream = upstreamRef.stdout;
|
|
10439
|
+
if (upstream === headRef.stdout) {
|
|
10440
|
+
return null;
|
|
10441
|
+
}
|
|
10442
|
+
const incomingResult = await git([
|
|
10443
|
+
"log",
|
|
10444
|
+
`--max-count=${MAX_INCOMING + 1}`,
|
|
10445
|
+
"--format=%an: %s",
|
|
10446
|
+
`HEAD..origin/${DEFAULT_BRANCH}`
|
|
10447
|
+
]);
|
|
10448
|
+
const incomingLines = incomingResult.ok ? incomingResult.stdout.split("\n").filter((line) => line.length > 0) : [];
|
|
10449
|
+
if (incomingResult.ok && incomingLines.length === 0) {
|
|
10450
|
+
return null;
|
|
10451
|
+
}
|
|
10452
|
+
let behind = null;
|
|
10453
|
+
let ahead = null;
|
|
10454
|
+
const counts = await git([
|
|
10455
|
+
"rev-list",
|
|
10456
|
+
"--left-right",
|
|
10457
|
+
"--count",
|
|
10458
|
+
`origin/${DEFAULT_BRANCH}...HEAD`
|
|
10459
|
+
]);
|
|
10460
|
+
if (counts.ok) {
|
|
10461
|
+
const [left, right] = counts.stdout.split(/\s+/);
|
|
10462
|
+
behind = Number(left);
|
|
10463
|
+
ahead = Number(right);
|
|
10464
|
+
if (!Number.isFinite(behind) || behind <= 0) {
|
|
10465
|
+
return null;
|
|
10466
|
+
}
|
|
10467
|
+
if (!Number.isFinite(ahead)) {
|
|
10468
|
+
ahead = null;
|
|
10469
|
+
}
|
|
10470
|
+
} else if (!incomingResult.ok) {
|
|
10471
|
+
const contained = await git([
|
|
10472
|
+
"merge-base",
|
|
10473
|
+
"--is-ancestor",
|
|
10474
|
+
`origin/${DEFAULT_BRANCH}`,
|
|
10475
|
+
"HEAD"
|
|
10476
|
+
]);
|
|
10477
|
+
if (contained.ok) {
|
|
10478
|
+
return null;
|
|
10479
|
+
}
|
|
10480
|
+
log17.info(
|
|
10481
|
+
`behind by an unknown amount: rev-list and log both failed (${counts.error})`
|
|
10482
|
+
);
|
|
10483
|
+
}
|
|
10484
|
+
const status = await git(["status", "--porcelain"]);
|
|
10485
|
+
if (!status.ok) {
|
|
10486
|
+
log17.info(`could not read the working tree state: ${status.error}`);
|
|
10487
|
+
}
|
|
10488
|
+
return {
|
|
10489
|
+
upstream,
|
|
10490
|
+
behind,
|
|
10491
|
+
ahead,
|
|
10492
|
+
dirty: status.ok ? status.stdout.length > 0 : "unknown",
|
|
10493
|
+
incoming: incomingLines.slice(0, MAX_INCOMING),
|
|
10494
|
+
incomingTruncated: incomingLines.length > MAX_INCOMING
|
|
10495
|
+
};
|
|
10496
|
+
}
|
|
10497
|
+
|
|
10313
10498
|
// src/headless/messageQueue.ts
|
|
10314
10499
|
function holdRestoredUserItems(items) {
|
|
10315
10500
|
return items.map(
|
|
@@ -10516,7 +10701,7 @@ var MessageQueue = class {
|
|
|
10516
10701
|
};
|
|
10517
10702
|
|
|
10518
10703
|
// src/headless/index.ts
|
|
10519
|
-
var
|
|
10704
|
+
var log18 = createLogger("headless");
|
|
10520
10705
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
10521
10706
|
var LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set(["runMethod", "testJewel"]);
|
|
10522
10707
|
var LONG_RUNNING_TOOL_TIMEOUT_MS = 18e5;
|
|
@@ -10580,6 +10765,15 @@ var HeadlessSession = class {
|
|
|
10580
10765
|
* Persisted to .remy-stats.json alongside the queue.
|
|
10581
10766
|
*/
|
|
10582
10767
|
passivePen = [];
|
|
10768
|
+
/**
|
|
10769
|
+
* The workspace-behind note, and the upstream tip it was raised for.
|
|
10770
|
+
*
|
|
10771
|
+
* Rides the same sweep as the passive pen and for the same reason: a
|
|
10772
|
+
* workspace that is behind is worth knowing about before the next piece of
|
|
10773
|
+
* work, and worth nothing at all if nobody is working — so it must never
|
|
10774
|
+
* initiate a turn of its own.
|
|
10775
|
+
*/
|
|
10776
|
+
workspaceNotice = emptyWorkspaceNotice();
|
|
10583
10777
|
// External tool bridge
|
|
10584
10778
|
pendingTools = /* @__PURE__ */ new Map();
|
|
10585
10779
|
earlyResults = /* @__PURE__ */ new Map();
|
|
@@ -10626,6 +10820,8 @@ var HeadlessSession = class {
|
|
|
10626
10820
|
}
|
|
10627
10821
|
);
|
|
10628
10822
|
this.passivePen = loadPassiveResults();
|
|
10823
|
+
this.workspaceNotice = loadWorkspaceNotice();
|
|
10824
|
+
void this.checkUpstream();
|
|
10629
10825
|
this.persistStats();
|
|
10630
10826
|
if (resumed) {
|
|
10631
10827
|
this.emit("session_restored", {
|
|
@@ -10749,7 +10945,7 @@ var HeadlessSession = class {
|
|
|
10749
10945
|
try {
|
|
10750
10946
|
this.handleCancel("shutdown");
|
|
10751
10947
|
} catch (err) {
|
|
10752
|
-
|
|
10948
|
+
log18.warn("Shutdown cancel failed", { error: err?.message });
|
|
10753
10949
|
}
|
|
10754
10950
|
this.emit("stopping");
|
|
10755
10951
|
this.emit("stopped");
|
|
@@ -10765,7 +10961,7 @@ var HeadlessSession = class {
|
|
|
10765
10961
|
}
|
|
10766
10962
|
const line = JSON.stringify(payload) + "\n";
|
|
10767
10963
|
if (event === "history") {
|
|
10768
|
-
|
|
10964
|
+
log18.info("Wrote history event to stdout", {
|
|
10769
10965
|
requestId,
|
|
10770
10966
|
bytes: line.length
|
|
10771
10967
|
});
|
|
@@ -10812,9 +11008,45 @@ var HeadlessSession = class {
|
|
|
10812
11008
|
this.sessionStats,
|
|
10813
11009
|
this.queue.snapshot(),
|
|
10814
11010
|
this.passivePen,
|
|
10815
|
-
suggestCompactAt
|
|
11011
|
+
suggestCompactAt,
|
|
11012
|
+
this.workspaceNotice
|
|
10816
11013
|
);
|
|
10817
11014
|
}
|
|
11015
|
+
/**
|
|
11016
|
+
* Park a note if this workspace is missing work that is already in production.
|
|
11017
|
+
*
|
|
11018
|
+
* Once per upstream tip rather than once per boot. Remy restarts for reasons
|
|
11019
|
+
* that have nothing to do with the repo — a pod recycle, a crash, a new
|
|
11020
|
+
* session — and re-raising the same note on each of those turns a useful
|
|
11021
|
+
* signal into nagging. A colleague publishing again moves the tip, which
|
|
11022
|
+
* re-arms it; bringing the workspace current means there is nothing to raise.
|
|
11023
|
+
*
|
|
11024
|
+
* Never throws: this is called unawaited from the boot path, so an unhandled
|
|
11025
|
+
* rejection here would be a crash at the least recoverable moment.
|
|
11026
|
+
*/
|
|
11027
|
+
async checkUpstream() {
|
|
11028
|
+
try {
|
|
11029
|
+
const status = await readUpstreamStatus();
|
|
11030
|
+
if (!status || status.upstream === this.workspaceNotice.lastNotedUpstream) {
|
|
11031
|
+
return;
|
|
11032
|
+
}
|
|
11033
|
+
this.workspaceNotice = {
|
|
11034
|
+
pendingNote: buildWorkspaceStatusMessage(status),
|
|
11035
|
+
lastNotedUpstream: status.upstream
|
|
11036
|
+
};
|
|
11037
|
+
this.persistStats();
|
|
11038
|
+
log18.info("workspace behind upstream; note parked for the next turn", {
|
|
11039
|
+
// The upstream sha is the dedupe key, so it is what makes "why did I
|
|
11040
|
+
// not get a note" answerable from the log alone.
|
|
11041
|
+
upstream: status.upstream,
|
|
11042
|
+
behind: status.behind,
|
|
11043
|
+
ahead: status.ahead,
|
|
11044
|
+
dirty: status.dirty
|
|
11045
|
+
});
|
|
11046
|
+
} catch (err) {
|
|
11047
|
+
log18.info(`upstream check failed: ${String(err)}`);
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
10818
11050
|
//////////////////////////////////////////////////////////////////////////////
|
|
10819
11051
|
// Background completions (tool-block mutation; message delivery via queue)
|
|
10820
11052
|
//////////////////////////////////////////////////////////////////////////////
|
|
@@ -10861,7 +11093,7 @@ var HeadlessSession = class {
|
|
|
10861
11093
|
if (this.sessionStats.lastContextSize <= threshold) {
|
|
10862
11094
|
return;
|
|
10863
11095
|
}
|
|
10864
|
-
|
|
11096
|
+
log18.info("Forced compaction gate triggered", {
|
|
10865
11097
|
contextSize: this.sessionStats.lastContextSize,
|
|
10866
11098
|
threshold,
|
|
10867
11099
|
model: parentModel,
|
|
@@ -10881,7 +11113,7 @@ var HeadlessSession = class {
|
|
|
10881
11113
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
10882
11114
|
const notify = getToolByName(name)?.backgroundNotify ?? "wake";
|
|
10883
11115
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
10884
|
-
|
|
11116
|
+
log18.info("Background complete", {
|
|
10885
11117
|
toolCallId,
|
|
10886
11118
|
name,
|
|
10887
11119
|
notify,
|
|
@@ -11163,7 +11395,7 @@ var HeadlessSession = class {
|
|
|
11163
11395
|
const { documents, images } = await persistAttachments(attachments);
|
|
11164
11396
|
return buildUploadHeader(documents, images) || void 0;
|
|
11165
11397
|
} catch (err) {
|
|
11166
|
-
|
|
11398
|
+
log18.warn("Attachment persistence failed", { error: err.message });
|
|
11167
11399
|
return void 0;
|
|
11168
11400
|
}
|
|
11169
11401
|
}
|
|
@@ -11223,7 +11455,7 @@ var HeadlessSession = class {
|
|
|
11223
11455
|
}
|
|
11224
11456
|
if (batch.length === 0) {
|
|
11225
11457
|
if (landings > 0) {
|
|
11226
|
-
|
|
11458
|
+
log18.info("promptUser store landings passed through", { landings });
|
|
11227
11459
|
}
|
|
11228
11460
|
return raw;
|
|
11229
11461
|
}
|
|
@@ -11231,7 +11463,7 @@ var HeadlessSession = class {
|
|
|
11231
11463
|
try {
|
|
11232
11464
|
results = await persistAttachmentList(batch);
|
|
11233
11465
|
} catch (err) {
|
|
11234
|
-
|
|
11466
|
+
log18.warn("promptUser upload persistence failed", {
|
|
11235
11467
|
error: err.message
|
|
11236
11468
|
});
|
|
11237
11469
|
results = batch.map(() => null);
|
|
@@ -11243,7 +11475,7 @@ var HeadlessSession = class {
|
|
|
11243
11475
|
return r.localPath;
|
|
11244
11476
|
}
|
|
11245
11477
|
const att = batch[cursor + i];
|
|
11246
|
-
|
|
11478
|
+
log18.warn("promptUser upload not persisted; falling back to url", {
|
|
11247
11479
|
filename: att.filename
|
|
11248
11480
|
});
|
|
11249
11481
|
return att.url;
|
|
@@ -11251,7 +11483,7 @@ var HeadlessSession = class {
|
|
|
11251
11483
|
cursor += slot.count;
|
|
11252
11484
|
answers[slot.id] = slot.isArray ? paths : paths[0];
|
|
11253
11485
|
}
|
|
11254
|
-
|
|
11486
|
+
log18.info("promptUser uploads persisted", { count: batch.length });
|
|
11255
11487
|
return JSON.stringify(answers);
|
|
11256
11488
|
}
|
|
11257
11489
|
/**
|
|
@@ -11274,7 +11506,7 @@ var HeadlessSession = class {
|
|
|
11274
11506
|
async runSingleTurn(parsed, requestId, fromChain = false, queued = false) {
|
|
11275
11507
|
const attachments = parsed.attachments;
|
|
11276
11508
|
if (attachments?.length) {
|
|
11277
|
-
|
|
11509
|
+
log18.info("Message has attachments", {
|
|
11278
11510
|
count: attachments.length,
|
|
11279
11511
|
urls: attachments.map((a) => a.url)
|
|
11280
11512
|
});
|
|
@@ -11422,6 +11654,18 @@ var HeadlessSession = class {
|
|
|
11422
11654
|
hidden: true
|
|
11423
11655
|
});
|
|
11424
11656
|
}
|
|
11657
|
+
const hasUserWords = entries.some(
|
|
11658
|
+
(entry) => !entry.hidden && !isAutomatedMessage(entry.text)
|
|
11659
|
+
);
|
|
11660
|
+
if (this.workspaceNotice.pendingNote && hasUserWords) {
|
|
11661
|
+
const note = this.workspaceNotice.pendingNote;
|
|
11662
|
+
this.workspaceNotice = {
|
|
11663
|
+
...this.workspaceNotice,
|
|
11664
|
+
pendingNote: null
|
|
11665
|
+
};
|
|
11666
|
+
this.persistStats();
|
|
11667
|
+
entries.unshift({ text: note, hidden: true });
|
|
11668
|
+
}
|
|
11425
11669
|
const consumedRids = [];
|
|
11426
11670
|
const takeSteering = async () => {
|
|
11427
11671
|
const items = this.queue.removeWhere(
|
|
@@ -11474,7 +11718,7 @@ var HeadlessSession = class {
|
|
|
11474
11718
|
error: "Turn ended unexpectedly"
|
|
11475
11719
|
});
|
|
11476
11720
|
}
|
|
11477
|
-
|
|
11721
|
+
log18.info("Turn complete", {
|
|
11478
11722
|
requestId,
|
|
11479
11723
|
durationMs: Date.now() - this.turnStart
|
|
11480
11724
|
});
|
|
@@ -11486,7 +11730,7 @@ var HeadlessSession = class {
|
|
|
11486
11730
|
error: err.message
|
|
11487
11731
|
});
|
|
11488
11732
|
}
|
|
11489
|
-
|
|
11733
|
+
log18.warn("Command failed", {
|
|
11490
11734
|
action: "message",
|
|
11491
11735
|
requestId,
|
|
11492
11736
|
error: err.message
|
|
@@ -11793,7 +12037,7 @@ var HeadlessSession = class {
|
|
|
11793
12037
|
try {
|
|
11794
12038
|
parsed = JSON.parse(line);
|
|
11795
12039
|
} catch (err) {
|
|
11796
|
-
|
|
12040
|
+
log18.warn("Invalid JSON on stdin", {
|
|
11797
12041
|
error: err.message,
|
|
11798
12042
|
lineLength: line.length,
|
|
11799
12043
|
preview: line.slice(0, 200)
|
|
@@ -11802,7 +12046,7 @@ var HeadlessSession = class {
|
|
|
11802
12046
|
return;
|
|
11803
12047
|
}
|
|
11804
12048
|
const { action, requestId } = parsed;
|
|
11805
|
-
|
|
12049
|
+
log18.info("Command received", { action, requestId });
|
|
11806
12050
|
if (action === "tool_result" && parsed.id) {
|
|
11807
12051
|
const id = parsed.id;
|
|
11808
12052
|
const result = parsed.result ?? "";
|
|
@@ -11811,7 +12055,7 @@ var HeadlessSession = class {
|
|
|
11811
12055
|
this.pendingTools.delete(id);
|
|
11812
12056
|
pending2.resolve(result);
|
|
11813
12057
|
} else if (!this.running) {
|
|
11814
|
-
|
|
12058
|
+
log18.info("Late tool_result while idle, dismissing", { id });
|
|
11815
12059
|
this.emit("completed", { success: true }, requestId);
|
|
11816
12060
|
} else {
|
|
11817
12061
|
this.earlyResults.set(id, result);
|
|
@@ -11824,7 +12068,7 @@ var HeadlessSession = class {
|
|
|
11824
12068
|
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
11825
12069
|
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
11826
12070
|
});
|
|
11827
|
-
|
|
12071
|
+
log18.info("History response", {
|
|
11828
12072
|
requestId,
|
|
11829
12073
|
startIndex: page.startIndex,
|
|
11830
12074
|
endIndex: page.endIndex,
|