@bridge4dev/runner 0.34.0 → 0.35.0
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/adapters/codex.js +28 -8
- package/dist/index.js +13 -1
- package/dist/supervisor.d.ts +14 -0
- package/dist/supervisor.js +48 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -483,21 +483,41 @@ class CodexSession {
|
|
|
483
483
|
// settings/update in this protocol version).
|
|
484
484
|
...(this.effort ? { effort: this.effort } : {}),
|
|
485
485
|
};
|
|
486
|
-
// Collaboration mode is turn-scoped but sticky,
|
|
487
|
-
//
|
|
486
|
+
// Collaboration mode is turn-scoped but sticky, so it is only sent on a real
|
|
487
|
+
// change. What it must NOT carry is our own instructions (ticket #179).
|
|
488
488
|
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
489
|
+
// The line that used to sit here said `settings` REPLACE the thread's
|
|
490
|
+
// developer instructions, and that passing `null` meant «no developer
|
|
491
|
+
// instructions at all». Both halves are false, and the first one cost the
|
|
492
|
+
// product a duplicated system prompt in every Codex session. Measured on the
|
|
493
|
+
// wire — a fake Responses API behind `codex app-server`, reading the developer
|
|
494
|
+
// message verbatim — on codex-cli 0.135.0 AND 0.147.0, which behave alike:
|
|
495
|
+
//
|
|
496
|
+
// thread developerInstructions + collaborationMode carrying the same text
|
|
497
|
+
// → the text appears TWICE in one developer message: once at the top (on
|
|
498
|
+
// 0.135.0, right after `<permissions instructions>`) and again inside
|
|
499
|
+
// `<collaboration_mode>…</collaboration_mode>`. That is exactly what the
|
|
500
|
+
// ticket's screenshots show, and with a real project prompt it is ~27 KB
|
|
501
|
+
// sent twice on every single request.
|
|
502
|
+
// thread developerInstructions + `developer_instructions: null`
|
|
503
|
+
// → the text appears ONCE, and `<collaboration_mode>` carries Codex's own
|
|
504
|
+
// preset for the mode («# Plan Mode (Conversational)…»).
|
|
505
|
+
//
|
|
506
|
+
// So `null` is not a loss, it is the fix — and it repairs a second, quieter
|
|
507
|
+
// defect at the same time: our text was OVERWRITING Codex's built-in plan
|
|
508
|
+
// instructions, i.e. the one mode a user picks to make the agent think before
|
|
509
|
+
// acting was the one mode that never received the instructions for thinking
|
|
510
|
+
// before acting. The thread-level channel (`threadParams`) stays the single
|
|
511
|
+
// source of DevBridge's rules, exactly like `systemPrompt.append` on Claude.
|
|
494
512
|
if (this.lastCollabMode !== wantCollab) {
|
|
495
513
|
params['collaborationMode'] = {
|
|
496
514
|
mode: wantCollab,
|
|
497
515
|
settings: {
|
|
498
516
|
model: this.model ?? this.threadModel ?? 'gpt-5.5',
|
|
499
517
|
reasoning_effort: null,
|
|
500
|
-
|
|
518
|
+
// «Use the built-in instructions for the selected mode» — the app-server
|
|
519
|
+
// schema's own words. Ours already arrived with the thread.
|
|
520
|
+
developer_instructions: null,
|
|
501
521
|
},
|
|
502
522
|
};
|
|
503
523
|
this.lastCollabMode = wantCollab;
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,18 @@ const LAST_EXIT = takeLastExit();
|
|
|
35
35
|
function print(line) {
|
|
36
36
|
process.stdout.write(line + '\n');
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Дата для оператора, который читает вывод у себя в консоли.
|
|
40
|
+
*
|
|
41
|
+
* Раньше здесь стояло `iso.slice(0, 10)` — это UTC-день, а не день того, кто
|
|
42
|
+
* смотрит: к востоку от Гринвича поздним вечером он показывает вчерашнее число.
|
|
43
|
+
* Часы машины оператора — единственный разумный ответ: у раннера нет ни
|
|
44
|
+
* человека с настройкой, ни организации.
|
|
45
|
+
*/
|
|
46
|
+
function formatLocalDay(iso) {
|
|
47
|
+
const date = new Date(iso);
|
|
48
|
+
return Number.isNaN(date.getTime()) ? iso.slice(0, 10) : date.toLocaleDateString();
|
|
49
|
+
}
|
|
38
50
|
function fail(message) {
|
|
39
51
|
process.stderr.write(`error: ${message}\n`);
|
|
40
52
|
process.exit(1);
|
|
@@ -908,7 +920,7 @@ async function agentChecks() {
|
|
|
908
920
|
ok: signedIn,
|
|
909
921
|
name: `${agent} login`,
|
|
910
922
|
detail: (info.detail ?? info.status) +
|
|
911
|
-
(info.expiresAt ? ` · until ${info.expiresAt
|
|
923
|
+
(info.expiresAt ? ` · until ${formatLocalDay(info.expiresAt)}` : ''),
|
|
912
924
|
...(signedIn
|
|
913
925
|
? {}
|
|
914
926
|
: {
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -136,6 +136,20 @@ export declare class Supervisor {
|
|
|
136
136
|
* refuse writes to it: in `workMode: DIRECT` the project folder is the
|
|
137
137
|
* agent's own working directory, so without that rule a session could rewrite
|
|
138
138
|
* the prompt it will itself be started with next time (QA-130 MAJOR-3).
|
|
139
|
+
*
|
|
140
|
+
* @param resuming This launch continues an existing agent conversation. It
|
|
141
|
+
* changes only the WORDS, never the behaviour — and the words matter,
|
|
142
|
+
* because a person who has just watched their session crash reads a second
|
|
143
|
+
* «Project prompt loaded» as «my context was thrown away» (ticket #177).
|
|
144
|
+
* Measured, so the line can say it plainly: a system prompt lives in the
|
|
145
|
+
* agent PROCESS, not in the saved conversation. Resuming Claude Code
|
|
146
|
+
* without re-supplying `--append-system-prompt` restores every message and
|
|
147
|
+
* ZERO bytes of the appended prompt; re-supplying it restores the same
|
|
148
|
+
* messages and exactly ONE copy of the prompt, never two. Codex is the same
|
|
149
|
+
* fact by a different route — it rebuilds the developer message from the
|
|
150
|
+
* current parameters on every request. So re-reading the file here is not
|
|
151
|
+
* waste, and skipping it would silently strip the project's rules (and
|
|
152
|
+
* DevBridge's own) from every session that ever resumed.
|
|
139
153
|
*/
|
|
140
154
|
private resolveAgentPrompt;
|
|
141
155
|
/** Warn the user when this share of the budget is gone. */
|
package/dist/supervisor.js
CHANGED
|
@@ -358,8 +358,19 @@ export class Supervisor {
|
|
|
358
358
|
if (descriptor.epoch > 0) {
|
|
359
359
|
// The API owns the resume transition; the feed marker has to come from
|
|
360
360
|
// here because the runner is the only writer of the event seq.
|
|
361
|
+
//
|
|
362
|
+
// Ticket #177: which of the two sentences is true depends on whether
|
|
363
|
+
// there is a conversation to go back to. `providerSessionId` is the
|
|
364
|
+
// agent's own name for it, and it is what the next launch hands to
|
|
365
|
+
// `--resume` / `thread/resume`. Without it the next process starts the
|
|
366
|
+
// conversation over — which is a real loss, and promising «continue
|
|
367
|
+
// where the agent left off» there is the one thing the feed must not do.
|
|
368
|
+
// It happens for real: a process that dies before it reports its session
|
|
369
|
+
// id (the SIGABRT this ticket came from) leaves the row with none.
|
|
361
370
|
this.sendEvent(running, 'system_note', {
|
|
362
|
-
text:
|
|
371
|
+
text: descriptor.providerSessionId
|
|
372
|
+
? 'Session resumed — the agent still has this conversation. Send a message to continue where it left off.'
|
|
373
|
+
: 'Session resumed, but the agent never got as far as naming its conversation, so it starts this one over. Your files, your branch and everything above are untouched.',
|
|
363
374
|
});
|
|
364
375
|
}
|
|
365
376
|
running.lastReported = descriptor.status === 'REVIEW' ? 'REVIEW' : 'WAITING_INPUT';
|
|
@@ -444,7 +455,14 @@ export class Supervisor {
|
|
|
444
455
|
// Facts about this session only, plus the one file the project named. The
|
|
445
456
|
// rest of the project's documentation is read by each agent itself — see
|
|
446
457
|
// `composeWorkspaceContext`.
|
|
447
|
-
|
|
458
|
+
// Does this launch CONTINUE the agent's conversation or begin a new one?
|
|
459
|
+
// Read before `rewindAnchor` is consumed below, and without consuming it —
|
|
460
|
+
// the prompt notice says different things about the two cases (#177).
|
|
461
|
+
// A rewind resumes a conversation too, but a CUT one — so it gets neither
|
|
462
|
+
// «loaded» nor «unchanged».
|
|
463
|
+
const rewinding = Boolean(running.rewindAnchor?.agentSession);
|
|
464
|
+
const resuming = rewinding || Boolean(resumeId);
|
|
465
|
+
const agentPrompt = this.resolveAgentPrompt(running, resuming && !rewinding);
|
|
448
466
|
const workspaceContext = composeWorkspaceContext(descriptor, agentPrompt?.text);
|
|
449
467
|
const rewind = running.rewindAnchor;
|
|
450
468
|
delete running.rewindAnchor;
|
|
@@ -515,8 +533,22 @@ export class Supervisor {
|
|
|
515
533
|
* refuse writes to it: in `workMode: DIRECT` the project folder is the
|
|
516
534
|
* agent's own working directory, so without that rule a session could rewrite
|
|
517
535
|
* the prompt it will itself be started with next time (QA-130 MAJOR-3).
|
|
536
|
+
*
|
|
537
|
+
* @param resuming This launch continues an existing agent conversation. It
|
|
538
|
+
* changes only the WORDS, never the behaviour — and the words matter,
|
|
539
|
+
* because a person who has just watched their session crash reads a second
|
|
540
|
+
* «Project prompt loaded» as «my context was thrown away» (ticket #177).
|
|
541
|
+
* Measured, so the line can say it plainly: a system prompt lives in the
|
|
542
|
+
* agent PROCESS, not in the saved conversation. Resuming Claude Code
|
|
543
|
+
* without re-supplying `--append-system-prompt` restores every message and
|
|
544
|
+
* ZERO bytes of the appended prompt; re-supplying it restores the same
|
|
545
|
+
* messages and exactly ONE copy of the prompt, never two. Codex is the same
|
|
546
|
+
* fact by a different route — it rebuilds the developer message from the
|
|
547
|
+
* current parameters on every request. So re-reading the file here is not
|
|
548
|
+
* waste, and skipping it would silently strip the project's rules (and
|
|
549
|
+
* DevBridge's own) from every session that ever resumed.
|
|
518
550
|
*/
|
|
519
|
-
resolveAgentPrompt(running) {
|
|
551
|
+
resolveAgentPrompt(running, resuming = false) {
|
|
520
552
|
const { descriptor } = running;
|
|
521
553
|
const configured = descriptor.workspace.agentPromptPath?.trim();
|
|
522
554
|
if (!configured)
|
|
@@ -557,7 +589,9 @@ export class Supervisor {
|
|
|
557
589
|
// mode.
|
|
558
590
|
this.sendEvent(running, 'notice', {
|
|
559
591
|
level: 'info',
|
|
560
|
-
text:
|
|
592
|
+
text: resuming
|
|
593
|
+
? `Project prompt re-applied from ${quotePath(result.relPath)} — ${agentPromptSizeLabel(result.bytes)}, sha ${result.sha}. A new agent process needs it again; the conversation is unchanged.`
|
|
594
|
+
: `Project prompt loaded from ${quotePath(result.relPath)} — ${agentPromptSizeLabel(result.bytes)}, sha ${result.sha}.`,
|
|
561
595
|
});
|
|
562
596
|
return { text: result.text, absPath: result.absPath };
|
|
563
597
|
}
|
|
@@ -2095,7 +2129,13 @@ export class Supervisor {
|
|
|
2095
2129
|
*/
|
|
2096
2130
|
const wasMidTurn = descriptor.status === 'RUNNING' || descriptor.status === 'WAITING_PERMISSION';
|
|
2097
2131
|
const resumeId = descriptor.providerSessionId;
|
|
2098
|
-
|
|
2132
|
+
// Ticket #177: `resumeId` is required, not merely nice to have. Without
|
|
2133
|
+
// it the relaunch starts a FRESH conversation, and `AUTO_RESUME_PROMPT`
|
|
2134
|
+
// — "continue from where you stopped, re-check what you were in the
|
|
2135
|
+
// middle of" — would be addressed to an agent that remembers none of
|
|
2136
|
+
// it. A process killed before it reported its session id (the SIGABRT
|
|
2137
|
+
// this ticket came from) leaves the row in exactly that state.
|
|
2138
|
+
const willContinue = wasMidTurn && Boolean(resumeId) && claimAutoResume(descriptor.id);
|
|
2099
2139
|
// The note stays either way (owner's call): an interruption is a fact
|
|
2100
2140
|
// about the session and must not disappear just because we recovered
|
|
2101
2141
|
// from it. Only the instruction at the end changes — telling someone to
|
|
@@ -2103,7 +2143,9 @@ export class Supervisor {
|
|
|
2103
2143
|
this.sendEvent(running, 'system_note', {
|
|
2104
2144
|
text: willContinue
|
|
2105
2145
|
? 'Runner reconnected. The session was resumed — continuing the interrupted turn.'
|
|
2106
|
-
:
|
|
2146
|
+
: resumeId
|
|
2147
|
+
? 'Runner reconnected. The session was resumed — send a message to continue.'
|
|
2148
|
+
: 'Runner reconnected, but the agent never got as far as naming its conversation, so it starts this one over. Your files, your branch and everything above are untouched.',
|
|
2107
2149
|
});
|
|
2108
2150
|
if (willContinue) {
|
|
2109
2151
|
// Resumed through the PROVIDER session, so the agent keeps its whole
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.35.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED