@mindstudio-ai/remy 0.1.331 → 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 +269 -26
- package/dist/index.js +280 -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,
|
|
@@ -1141,6 +1147,33 @@ ${xml}
|
|
|
1141
1147
|
</background_results>`;
|
|
1142
1148
|
return automatedMessage("background_results", body);
|
|
1143
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
|
+
}
|
|
1144
1177
|
function mergeBackgroundResultsMessages(messages) {
|
|
1145
1178
|
const results = [];
|
|
1146
1179
|
const toolRe = /<tool_result id="([^"]+)" name="([^"]+)">\n([\s\S]*?)\n<\/tool_result>/g;
|
|
@@ -3760,6 +3793,7 @@ var INTERNAL_PAYLOAD_MARKERS = [
|
|
|
3760
3793
|
"[INTERRUPTED]",
|
|
3761
3794
|
"[INTERRUPTED - PARTIAL OUTPUT RETRIEVED]",
|
|
3762
3795
|
"<background_results>",
|
|
3796
|
+
"<workspace_status>",
|
|
3763
3797
|
"<tool_result"
|
|
3764
3798
|
];
|
|
3765
3799
|
function sanitizeStatusText(text) {
|
|
@@ -3814,7 +3848,11 @@ function startStatusWatcher(config) {
|
|
|
3814
3848
|
method: "POST",
|
|
3815
3849
|
headers: {
|
|
3816
3850
|
"Content-Type": "application/json",
|
|
3817
|
-
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()
|
|
3818
3856
|
},
|
|
3819
3857
|
body: JSON.stringify({ appId: apiConfig.appId, context }),
|
|
3820
3858
|
signal
|
|
@@ -4831,7 +4869,7 @@ TypeScript running in a sandboxed environment. Any npm package can be installed.
|
|
|
4831
4869
|
- Managed SQLite database with typed schemas and automatic migrations. Define a TypeScript interface, push, and the platform handles diffing and migrating.
|
|
4832
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.
|
|
4833
4871
|
- Encrypted secrets with separate dev/prod values, injected as process.env. For third-party service credentials not covered by the SDK.
|
|
4834
|
-
- 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.
|
|
4835
4873
|
|
|
4836
4874
|
## MindStudio SDK
|
|
4837
4875
|
|
|
@@ -7867,6 +7905,9 @@ function serializeForSummary(messages) {
|
|
|
7867
7905
|
}
|
|
7868
7906
|
continue;
|
|
7869
7907
|
}
|
|
7908
|
+
if (msg.role === "user" && (msg.hidden || typeof msg.content === "string" && isAutomatedMessage(msg.content))) {
|
|
7909
|
+
continue;
|
|
7910
|
+
}
|
|
7870
7911
|
if (typeof msg.content === "string") {
|
|
7871
7912
|
if (msg.content.trim()) {
|
|
7872
7913
|
lines.push(`[${msg.role}]: ${msg.content}`);
|
|
@@ -9026,7 +9067,10 @@ function parsePartialJson(jsonString) {
|
|
|
9026
9067
|
}
|
|
9027
9068
|
|
|
9028
9069
|
// src/automatedActions/resolve.ts
|
|
9029
|
-
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set([
|
|
9070
|
+
var NON_ACTION_SENTINELS = /* @__PURE__ */ new Set([
|
|
9071
|
+
"background_results",
|
|
9072
|
+
"workspace_status"
|
|
9073
|
+
]);
|
|
9030
9074
|
function resolveAction(text) {
|
|
9031
9075
|
const parsed = parseSentinel(text);
|
|
9032
9076
|
if (!parsed) {
|
|
@@ -9402,7 +9446,9 @@ async function runTurn(params) {
|
|
|
9402
9446
|
for (const entry of keptEntries) {
|
|
9403
9447
|
appendEntry(entry);
|
|
9404
9448
|
}
|
|
9405
|
-
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;
|
|
9406
9452
|
const STATUS_EXCLUDED_TOOLS = /* @__PURE__ */ new Set([
|
|
9407
9453
|
"markBuildComplete",
|
|
9408
9454
|
"setProjectMetadata",
|
|
@@ -10296,7 +10342,24 @@ function loadPassiveResults() {
|
|
|
10296
10342
|
}
|
|
10297
10343
|
return [];
|
|
10298
10344
|
}
|
|
10299
|
-
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) {
|
|
10300
10363
|
try {
|
|
10301
10364
|
writeFileAtomicSync(
|
|
10302
10365
|
STATS_FILE,
|
|
@@ -10304,13 +10367,134 @@ function writeStats(stats, queue, passiveResults, suggestCompactAt) {
|
|
|
10304
10367
|
...stats,
|
|
10305
10368
|
suggestCompactAt,
|
|
10306
10369
|
queue,
|
|
10307
|
-
passiveResults
|
|
10370
|
+
passiveResults,
|
|
10371
|
+
workspaceNotice
|
|
10308
10372
|
})
|
|
10309
10373
|
);
|
|
10310
10374
|
} catch {
|
|
10311
10375
|
}
|
|
10312
10376
|
}
|
|
10313
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
|
+
|
|
10314
10498
|
// src/headless/messageQueue.ts
|
|
10315
10499
|
function holdRestoredUserItems(items) {
|
|
10316
10500
|
return items.map(
|
|
@@ -10517,7 +10701,7 @@ var MessageQueue = class {
|
|
|
10517
10701
|
};
|
|
10518
10702
|
|
|
10519
10703
|
// src/headless/index.ts
|
|
10520
|
-
var
|
|
10704
|
+
var log18 = createLogger("headless");
|
|
10521
10705
|
var EXTERNAL_TOOL_TIMEOUT_MS = 3e5;
|
|
10522
10706
|
var LONG_RUNNING_TOOLS = /* @__PURE__ */ new Set(["runMethod", "testJewel"]);
|
|
10523
10707
|
var LONG_RUNNING_TOOL_TIMEOUT_MS = 18e5;
|
|
@@ -10581,6 +10765,15 @@ var HeadlessSession = class {
|
|
|
10581
10765
|
* Persisted to .remy-stats.json alongside the queue.
|
|
10582
10766
|
*/
|
|
10583
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();
|
|
10584
10777
|
// External tool bridge
|
|
10585
10778
|
pendingTools = /* @__PURE__ */ new Map();
|
|
10586
10779
|
earlyResults = /* @__PURE__ */ new Map();
|
|
@@ -10627,6 +10820,8 @@ var HeadlessSession = class {
|
|
|
10627
10820
|
}
|
|
10628
10821
|
);
|
|
10629
10822
|
this.passivePen = loadPassiveResults();
|
|
10823
|
+
this.workspaceNotice = loadWorkspaceNotice();
|
|
10824
|
+
void this.checkUpstream();
|
|
10630
10825
|
this.persistStats();
|
|
10631
10826
|
if (resumed) {
|
|
10632
10827
|
this.emit("session_restored", {
|
|
@@ -10750,7 +10945,7 @@ var HeadlessSession = class {
|
|
|
10750
10945
|
try {
|
|
10751
10946
|
this.handleCancel("shutdown");
|
|
10752
10947
|
} catch (err) {
|
|
10753
|
-
|
|
10948
|
+
log18.warn("Shutdown cancel failed", { error: err?.message });
|
|
10754
10949
|
}
|
|
10755
10950
|
this.emit("stopping");
|
|
10756
10951
|
this.emit("stopped");
|
|
@@ -10766,7 +10961,7 @@ var HeadlessSession = class {
|
|
|
10766
10961
|
}
|
|
10767
10962
|
const line = JSON.stringify(payload) + "\n";
|
|
10768
10963
|
if (event === "history") {
|
|
10769
|
-
|
|
10964
|
+
log18.info("Wrote history event to stdout", {
|
|
10770
10965
|
requestId,
|
|
10771
10966
|
bytes: line.length
|
|
10772
10967
|
});
|
|
@@ -10813,9 +11008,45 @@ var HeadlessSession = class {
|
|
|
10813
11008
|
this.sessionStats,
|
|
10814
11009
|
this.queue.snapshot(),
|
|
10815
11010
|
this.passivePen,
|
|
10816
|
-
suggestCompactAt
|
|
11011
|
+
suggestCompactAt,
|
|
11012
|
+
this.workspaceNotice
|
|
10817
11013
|
);
|
|
10818
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
|
+
}
|
|
10819
11050
|
//////////////////////////////////////////////////////////////////////////////
|
|
10820
11051
|
// Background completions (tool-block mutation; message delivery via queue)
|
|
10821
11052
|
//////////////////////////////////////////////////////////////////////////////
|
|
@@ -10862,7 +11093,7 @@ var HeadlessSession = class {
|
|
|
10862
11093
|
if (this.sessionStats.lastContextSize <= threshold) {
|
|
10863
11094
|
return;
|
|
10864
11095
|
}
|
|
10865
|
-
|
|
11096
|
+
log18.info("Forced compaction gate triggered", {
|
|
10866
11097
|
contextSize: this.sessionStats.lastContextSize,
|
|
10867
11098
|
threshold,
|
|
10868
11099
|
model: parentModel,
|
|
@@ -10882,7 +11113,7 @@ var HeadlessSession = class {
|
|
|
10882
11113
|
onBackgroundComplete = (toolCallId, name, result, subAgentMessages) => {
|
|
10883
11114
|
const notify = getToolByName(name)?.backgroundNotify ?? "wake";
|
|
10884
11115
|
this.pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
10885
|
-
|
|
11116
|
+
log18.info("Background complete", {
|
|
10886
11117
|
toolCallId,
|
|
10887
11118
|
name,
|
|
10888
11119
|
notify,
|
|
@@ -11164,7 +11395,7 @@ var HeadlessSession = class {
|
|
|
11164
11395
|
const { documents, images } = await persistAttachments(attachments);
|
|
11165
11396
|
return buildUploadHeader(documents, images) || void 0;
|
|
11166
11397
|
} catch (err) {
|
|
11167
|
-
|
|
11398
|
+
log18.warn("Attachment persistence failed", { error: err.message });
|
|
11168
11399
|
return void 0;
|
|
11169
11400
|
}
|
|
11170
11401
|
}
|
|
@@ -11224,7 +11455,7 @@ var HeadlessSession = class {
|
|
|
11224
11455
|
}
|
|
11225
11456
|
if (batch.length === 0) {
|
|
11226
11457
|
if (landings > 0) {
|
|
11227
|
-
|
|
11458
|
+
log18.info("promptUser store landings passed through", { landings });
|
|
11228
11459
|
}
|
|
11229
11460
|
return raw;
|
|
11230
11461
|
}
|
|
@@ -11232,7 +11463,7 @@ var HeadlessSession = class {
|
|
|
11232
11463
|
try {
|
|
11233
11464
|
results = await persistAttachmentList(batch);
|
|
11234
11465
|
} catch (err) {
|
|
11235
|
-
|
|
11466
|
+
log18.warn("promptUser upload persistence failed", {
|
|
11236
11467
|
error: err.message
|
|
11237
11468
|
});
|
|
11238
11469
|
results = batch.map(() => null);
|
|
@@ -11244,7 +11475,7 @@ var HeadlessSession = class {
|
|
|
11244
11475
|
return r.localPath;
|
|
11245
11476
|
}
|
|
11246
11477
|
const att = batch[cursor + i];
|
|
11247
|
-
|
|
11478
|
+
log18.warn("promptUser upload not persisted; falling back to url", {
|
|
11248
11479
|
filename: att.filename
|
|
11249
11480
|
});
|
|
11250
11481
|
return att.url;
|
|
@@ -11252,7 +11483,7 @@ var HeadlessSession = class {
|
|
|
11252
11483
|
cursor += slot.count;
|
|
11253
11484
|
answers[slot.id] = slot.isArray ? paths : paths[0];
|
|
11254
11485
|
}
|
|
11255
|
-
|
|
11486
|
+
log18.info("promptUser uploads persisted", { count: batch.length });
|
|
11256
11487
|
return JSON.stringify(answers);
|
|
11257
11488
|
}
|
|
11258
11489
|
/**
|
|
@@ -11275,7 +11506,7 @@ var HeadlessSession = class {
|
|
|
11275
11506
|
async runSingleTurn(parsed, requestId, fromChain = false, queued = false) {
|
|
11276
11507
|
const attachments = parsed.attachments;
|
|
11277
11508
|
if (attachments?.length) {
|
|
11278
|
-
|
|
11509
|
+
log18.info("Message has attachments", {
|
|
11279
11510
|
count: attachments.length,
|
|
11280
11511
|
urls: attachments.map((a) => a.url)
|
|
11281
11512
|
});
|
|
@@ -11423,6 +11654,18 @@ var HeadlessSession = class {
|
|
|
11423
11654
|
hidden: true
|
|
11424
11655
|
});
|
|
11425
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
|
+
}
|
|
11426
11669
|
const consumedRids = [];
|
|
11427
11670
|
const takeSteering = async () => {
|
|
11428
11671
|
const items = this.queue.removeWhere(
|
|
@@ -11475,7 +11718,7 @@ var HeadlessSession = class {
|
|
|
11475
11718
|
error: "Turn ended unexpectedly"
|
|
11476
11719
|
});
|
|
11477
11720
|
}
|
|
11478
|
-
|
|
11721
|
+
log18.info("Turn complete", {
|
|
11479
11722
|
requestId,
|
|
11480
11723
|
durationMs: Date.now() - this.turnStart
|
|
11481
11724
|
});
|
|
@@ -11487,7 +11730,7 @@ var HeadlessSession = class {
|
|
|
11487
11730
|
error: err.message
|
|
11488
11731
|
});
|
|
11489
11732
|
}
|
|
11490
|
-
|
|
11733
|
+
log18.warn("Command failed", {
|
|
11491
11734
|
action: "message",
|
|
11492
11735
|
requestId,
|
|
11493
11736
|
error: err.message
|
|
@@ -11794,7 +12037,7 @@ var HeadlessSession = class {
|
|
|
11794
12037
|
try {
|
|
11795
12038
|
parsed = JSON.parse(line);
|
|
11796
12039
|
} catch (err) {
|
|
11797
|
-
|
|
12040
|
+
log18.warn("Invalid JSON on stdin", {
|
|
11798
12041
|
error: err.message,
|
|
11799
12042
|
lineLength: line.length,
|
|
11800
12043
|
preview: line.slice(0, 200)
|
|
@@ -11803,7 +12046,7 @@ var HeadlessSession = class {
|
|
|
11803
12046
|
return;
|
|
11804
12047
|
}
|
|
11805
12048
|
const { action, requestId } = parsed;
|
|
11806
|
-
|
|
12049
|
+
log18.info("Command received", { action, requestId });
|
|
11807
12050
|
if (action === "tool_result" && parsed.id) {
|
|
11808
12051
|
const id = parsed.id;
|
|
11809
12052
|
const result = parsed.result ?? "";
|
|
@@ -11812,7 +12055,7 @@ var HeadlessSession = class {
|
|
|
11812
12055
|
this.pendingTools.delete(id);
|
|
11813
12056
|
pending2.resolve(result);
|
|
11814
12057
|
} else if (!this.running) {
|
|
11815
|
-
|
|
12058
|
+
log18.info("Late tool_result while idle, dismissing", { id });
|
|
11816
12059
|
this.emit("completed", { success: true }, requestId);
|
|
11817
12060
|
} else {
|
|
11818
12061
|
this.earlyResults.set(id, result);
|
|
@@ -11825,7 +12068,7 @@ var HeadlessSession = class {
|
|
|
11825
12068
|
...typeof parsed.before === "number" ? { before: parsed.before } : {},
|
|
11826
12069
|
...typeof parsed.limit === "number" ? { limit: parsed.limit } : {}
|
|
11827
12070
|
});
|
|
11828
|
-
|
|
12071
|
+
log18.info("History response", {
|
|
11829
12072
|
requestId,
|
|
11830
12073
|
startIndex: page.startIndex,
|
|
11831
12074
|
endIndex: page.endIndex,
|