negotium 0.2.7 → 0.2.9
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/agent-helpers.js +40 -24
- package/dist/agent-helpers.js.map +9 -9
- package/dist/background-bash.js +2 -2
- package/dist/background-bash.js.map +2 -2
- package/dist/browser-runtime.js +4 -3
- package/dist/browser-runtime.js.map +4 -4
- package/dist/{chunk-qmbcbhyy.js → chunk-knv045mn.js} +2 -2
- package/dist/{chunk-qmbcbhyy.js.map → chunk-knv045mn.js.map} +2 -2
- package/dist/hosted-agent.js +8 -4
- package/dist/hosted-agent.js.map +5 -5
- package/dist/main.js +40 -24
- package/dist/main.js.map +9 -9
- package/dist/mcp-factories.js +40 -24
- package/dist/mcp-factories.js.map +9 -9
- package/dist/prompts.js +14 -4
- package/dist/prompts.js.map +4 -4
- package/dist/query-runtime.js +2 -2
- package/dist/query-runtime.js.map +2 -2
- package/dist/registry.js +10 -5
- package/dist/registry.js.map +4 -4
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/agents/claude-provider.ts +7 -0
- package/dist/runtime/src/agents/maestro-registry.ts +37 -2
- package/dist/runtime/src/agents/model-catalog.ts +18 -4
- package/dist/runtime/src/mcp/background-bash-server.ts +194 -17
- package/dist/runtime/src/platform/config.ts +1 -1
- package/dist/runtime/src/platform/playwright/vault-broker.ts +7 -0
- package/dist/runtime/src/runtime/turn-runner.ts +34 -11
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +2 -2
- package/dist/runtime-helpers.js.map +2 -2
- package/dist/types/packages/core/src/agents/model-catalog.d.ts +4 -3
- package/dist/types/packages/core/src/platform/config.d.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +2 -2
- package/dist/vault.js.map +2 -2
- package/install-browser-rs.mjs +3 -3
- package/package.json +2 -2
|
@@ -55,7 +55,8 @@ export interface SelectableModel {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* Pricing and quota observations were checked on 2026-07-19
|
|
58
|
+
* Pricing and quota observations were checked on 2026-07-19, DeepSeek pricing
|
|
59
|
+
* re-checked 2026-08-04 for the new deepseek-v4-flash model.
|
|
59
60
|
* Official references:
|
|
60
61
|
* - https://learn.chatgpt.com/docs/pricing
|
|
61
62
|
* - https://help.openai.com/en/articles/20001106
|
|
@@ -67,9 +68,9 @@ export interface SelectableModel {
|
|
|
67
68
|
* meter cached input, fresh input, output, reasoning, speed, and model choice
|
|
68
69
|
* differently and may change server-side weights without publishing a token cap.
|
|
69
70
|
*/
|
|
70
|
-
export const MODEL_COST_RESEARCHED_AT = "2026-
|
|
71
|
+
export const MODEL_COST_RESEARCHED_AT = "2026-08-04";
|
|
71
72
|
export const MODEL_COST_ROUTING_SUMMARY =
|
|
72
|
-
"Cost basis (2026-
|
|
73
|
+
"Cost basis (2026-08-04): Codex Pro 20x and Claude Max 20x are each $200/month; Maestro models are pay-per-token. DeepSeek Flash is cheapest.";
|
|
73
74
|
|
|
74
75
|
const CODEX_PRO_20X_COST = "ChatGPT Pro 20x subscription: $200/month";
|
|
75
76
|
const CODEX_COMMUNITY_WEEKLY =
|
|
@@ -173,13 +174,26 @@ export const SELECTABLE_MODELS: readonly SelectableModel[] = [
|
|
|
173
174
|
agent: "maestro",
|
|
174
175
|
description: "API-priced Sonnet-level route for cost-efficient everyday work.",
|
|
175
176
|
intelligenceTier: "sonnet",
|
|
176
|
-
routingSummary: "cost-efficient everyday work; pay-per-token
|
|
177
|
+
routingSummary: "cost-efficient everyday work; pay-per-token, pricier than DeepSeek Flash",
|
|
177
178
|
accessCost: "DeepSeek V4 Pro pay-as-you-go API; no monthly subscription required",
|
|
178
179
|
marginalTokenCost:
|
|
179
180
|
"DeepSeek API: $0.435/M uncached input, $0.003625/M cached input, $0.87/M output",
|
|
180
181
|
estimatedUsage:
|
|
181
182
|
"No subscription token cap; pay per token. Official account concurrency limit is 500 requests.",
|
|
182
183
|
},
|
|
184
|
+
{
|
|
185
|
+
model: "deepseek-flash",
|
|
186
|
+
agent: "maestro",
|
|
187
|
+
description:
|
|
188
|
+
"Faster, cheaper DeepSeek V4 variant (DeepSeek-V4-Flash-0731) at similar Sonnet-level quality for high-volume everyday work.",
|
|
189
|
+
intelligenceTier: "sonnet",
|
|
190
|
+
routingSummary: "cheapest overall route; pay-per-token, 1M context, 5x Pro's concurrency limit",
|
|
191
|
+
accessCost: "DeepSeek V4 Flash pay-as-you-go API; no monthly subscription required",
|
|
192
|
+
marginalTokenCost:
|
|
193
|
+
"DeepSeek API: $0.14/M uncached input, $0.0028/M cached input, $0.28/M output",
|
|
194
|
+
estimatedUsage:
|
|
195
|
+
"No subscription token cap; pay per token. Official account concurrency limit is 2500 requests.",
|
|
196
|
+
},
|
|
183
197
|
];
|
|
184
198
|
|
|
185
199
|
const SELECTABLE_MODEL_ALIASES: Readonly<Record<string, string>> = {
|
|
@@ -65,10 +65,29 @@ const COMPLETED_RETENTION_MS = 60 * 60_000;
|
|
|
65
65
|
const MAX_COMPLETED_PROCS = 100;
|
|
66
66
|
/** Count-based pruning ignores jobs this fresh; their turn may still be queued. */
|
|
67
67
|
const PRUNE_GRACE_MS = 5 * 60_000;
|
|
68
|
+
const DEFAULT_WATCH_TIMEOUT_SECONDS = 3_600;
|
|
69
|
+
const MAX_WATCH_TIMEOUT_SECONDS = 86_400;
|
|
68
70
|
|
|
69
71
|
/** Root for full-output spill files, one directory per bash id. */
|
|
70
72
|
const SPILL_ROOT = join(RUN_DIR, "bg-bash-output");
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* One-shot match-and-notify state for `background_bash_watch`. `matched` and
|
|
76
|
+
* `finished` are distinct: `matched` records the specific outcome (a line hit
|
|
77
|
+
* `regex`) for the injected message; `finished` guards against `finishProc`
|
|
78
|
+
* double-delivering once a match or timeout has already sent the one turn a
|
|
79
|
+
* watch job promises.
|
|
80
|
+
*/
|
|
81
|
+
interface WatchState {
|
|
82
|
+
regex: RegExp;
|
|
83
|
+
target: "stdout" | "stderr" | "both";
|
|
84
|
+
matched: boolean;
|
|
85
|
+
finished: boolean;
|
|
86
|
+
timeoutTimer?: ReturnType<typeof setTimeout>;
|
|
87
|
+
stdoutCarry: string;
|
|
88
|
+
stderrCarry: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
interface BgProc {
|
|
73
92
|
bashId: string;
|
|
74
93
|
userId: string;
|
|
@@ -86,6 +105,8 @@ interface BgProc {
|
|
|
86
105
|
startedAt: number;
|
|
87
106
|
killTimer?: ReturnType<typeof setTimeout>;
|
|
88
107
|
cleanupTimer?: ReturnType<typeof setTimeout>;
|
|
108
|
+
/** Present only for jobs started via `background_bash_watch`. */
|
|
109
|
+
watch?: WatchState;
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
const procs = new Map<string, BgProc>();
|
|
@@ -175,30 +196,33 @@ function describeStream(label: string, snapshot: OutputSnapshot): string | undef
|
|
|
175
196
|
return `${label} (${notes.join(" · ")}):\n${body}`;
|
|
176
197
|
}
|
|
177
198
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
*
|
|
181
|
-
* Returns false when the inbox write failed. The tool promises the caller it
|
|
182
|
-
* need not poll, so a dropped completion is the one failure that leaves a
|
|
183
|
-
* background job silently unfinished — the caller must not treat it as done.
|
|
184
|
-
*/
|
|
185
|
-
function injectCompletion(proc: BgProc): boolean {
|
|
199
|
+
/** Render the shared header+output body used by every injected message. */
|
|
200
|
+
function buildOutputMessage(proc: BgProc, header: string): string {
|
|
186
201
|
const parts: string[] = [];
|
|
187
202
|
const stdout = describeStream("stdout", proc.stdout.snapshot());
|
|
188
203
|
const stderr = describeStream("stderr", proc.stderr.snapshot());
|
|
189
204
|
if (stdout) parts.push(stdout);
|
|
190
205
|
if (stderr) parts.push(stderr);
|
|
191
206
|
const output = parts.join("\n") || "(no output)";
|
|
192
|
-
|
|
193
207
|
// English on purpose: this text is injected into the model's context, and
|
|
194
208
|
// every other model-facing string in this server — the tool descriptions it
|
|
195
209
|
// sits alongside — is English. It is also cheaper in tokens.
|
|
196
|
-
|
|
197
|
-
|
|
210
|
+
return (
|
|
211
|
+
`${header}\n` +
|
|
198
212
|
`command: ${proc.command.slice(0, 200)}\n` +
|
|
199
213
|
`exit code: ${proc.exitCode ?? "unknown"}\n` +
|
|
200
|
-
output
|
|
214
|
+
output
|
|
215
|
+
);
|
|
216
|
+
}
|
|
201
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Deliver one turn to the topic's inbox.
|
|
220
|
+
*
|
|
221
|
+
* Returns false when the inbox write failed. Every caller promises the
|
|
222
|
+
* caller it need not poll, so a dropped delivery is the one failure that
|
|
223
|
+
* leaves a background job silently unfinished — treat it as not-done.
|
|
224
|
+
*/
|
|
225
|
+
function injectMessage(proc: BgProc, message: string, label: string): boolean {
|
|
202
226
|
// `proc.topic` is the canonical topic id (see mcp-config background-bash
|
|
203
227
|
// build: `topicId ?? session`). Route through the shared helper so the
|
|
204
228
|
// filename is the `topic-id-{base64url}` form the session-inbox worker
|
|
@@ -212,11 +236,11 @@ function injectCompletion(proc: BgProc): boolean {
|
|
|
212
236
|
depth: 0,
|
|
213
237
|
timestamp: new Date().toISOString(),
|
|
214
238
|
});
|
|
215
|
-
process.stderr.write(`[bg-bash] injected
|
|
239
|
+
process.stderr.write(`[bg-bash] injected ${label} ${proc.bashId} exit=${proc.exitCode}\n`);
|
|
216
240
|
return true;
|
|
217
241
|
} catch (e) {
|
|
218
242
|
process.stderr.write(
|
|
219
|
-
`[bg-bash] FAILED to deliver
|
|
243
|
+
`[bg-bash] FAILED to deliver ${label} ${proc.bashId} (exit=${proc.exitCode}): ${e}\n` +
|
|
220
244
|
`[bg-bash] the topic will never be told this job finished; ` +
|
|
221
245
|
`its output is kept at ${spillDir(proc.bashId)}\n`,
|
|
222
246
|
);
|
|
@@ -224,6 +248,61 @@ function injectCompletion(proc: BgProc): boolean {
|
|
|
224
248
|
}
|
|
225
249
|
}
|
|
226
250
|
|
|
251
|
+
function injectCompletion(proc: BgProc): boolean {
|
|
252
|
+
return injectMessage(
|
|
253
|
+
proc,
|
|
254
|
+
buildOutputMessage(proc, `[background_bash ${proc.bashId} finished]`),
|
|
255
|
+
"completion",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Test newly arrived bytes for `watch.regex`, line by line, carrying any
|
|
261
|
+
* trailing partial line to the next chunk so a match split across two reads
|
|
262
|
+
* is never missed. Returns the first matching line, if any.
|
|
263
|
+
*/
|
|
264
|
+
function checkWatchMatch(
|
|
265
|
+
proc: BgProc,
|
|
266
|
+
stream: "stdout" | "stderr",
|
|
267
|
+
chunk: Buffer,
|
|
268
|
+
): string | undefined {
|
|
269
|
+
const watch = proc.watch;
|
|
270
|
+
if (!watch || watch.matched) return undefined;
|
|
271
|
+
if (watch.target !== "both" && watch.target !== stream) return undefined;
|
|
272
|
+
const carryKey = stream === "stdout" ? "stdoutCarry" : "stderrCarry";
|
|
273
|
+
const lines = (watch[carryKey] + chunk.toString("utf8")).split("\n");
|
|
274
|
+
watch[carryKey] = lines.pop() ?? "";
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
if (watch.regex.test(line)) return line;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** A watch line matched: deliver the one promised turn and stop the process. */
|
|
282
|
+
function handleWatchMatch(proc: BgProc, line: string): void {
|
|
283
|
+
const watch = proc.watch;
|
|
284
|
+
if (!watch || watch.matched) return;
|
|
285
|
+
watch.matched = true;
|
|
286
|
+
watch.finished = true;
|
|
287
|
+
if (watch.timeoutTimer) {
|
|
288
|
+
clearTimeout(watch.timeoutTimer);
|
|
289
|
+
watch.timeoutTimer = undefined;
|
|
290
|
+
}
|
|
291
|
+
const header = `[background_bash_watch ${proc.bashId} matched]\nmatched line: ${line.slice(0, 500)}`;
|
|
292
|
+
proc.delivered = injectMessage(proc, buildOutputMessage(proc, header), "watch match");
|
|
293
|
+
terminateProc(proc);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** No line matched within the deadline: deliver a timeout turn and stop. */
|
|
297
|
+
function handleWatchTimeout(proc: BgProc): void {
|
|
298
|
+
const watch = proc.watch;
|
|
299
|
+
if (!watch || watch.finished) return;
|
|
300
|
+
watch.finished = true;
|
|
301
|
+
const header = `[background_bash_watch ${proc.bashId} timed out without a match]`;
|
|
302
|
+
proc.delivered = injectMessage(proc, buildOutputMessage(proc, header), "watch timeout");
|
|
303
|
+
terminateProc(proc);
|
|
304
|
+
}
|
|
305
|
+
|
|
227
306
|
function signalProcessTree(child: ChildProcess, signal: NodeJS.Signals): boolean {
|
|
228
307
|
if (!child.pid) return child.kill(signal);
|
|
229
308
|
try {
|
|
@@ -288,7 +367,23 @@ function finishProc(proc: BgProc, exitCode: number | null): void {
|
|
|
288
367
|
// paths, so anything reading them sees a closed, complete file.
|
|
289
368
|
proc.stdout.close();
|
|
290
369
|
proc.stderr.close();
|
|
291
|
-
|
|
370
|
+
if (proc.watch) {
|
|
371
|
+
if (proc.watch.timeoutTimer) {
|
|
372
|
+
clearTimeout(proc.watch.timeoutTimer);
|
|
373
|
+
proc.watch.timeoutTimer = undefined;
|
|
374
|
+
}
|
|
375
|
+
if (!proc.watch.finished) {
|
|
376
|
+
// The command exited on its own — no match, no timeout yet. This is
|
|
377
|
+
// still exactly one delivered turn, same one-shot guarantee.
|
|
378
|
+
proc.watch.finished = true;
|
|
379
|
+
const header = `[background_bash_watch ${proc.bashId} exited before matching]`;
|
|
380
|
+
proc.delivered = injectMessage(proc, buildOutputMessage(proc, header), "watch exit");
|
|
381
|
+
}
|
|
382
|
+
// Else: a match or timeout already delivered the one promised turn
|
|
383
|
+
// (`proc.delivered` was set there) — do not inject a second one.
|
|
384
|
+
} else {
|
|
385
|
+
proc.delivered = injectCompletion(proc);
|
|
386
|
+
}
|
|
292
387
|
if (proc.delivered) {
|
|
293
388
|
proc.cleanupTimer = setTimeout(() => forgetProc(proc), COMPLETED_RETENTION_MS);
|
|
294
389
|
proc.cleanupTimer.unref?.();
|
|
@@ -299,10 +394,17 @@ function finishProc(proc: BgProc, exitCode: number | null): void {
|
|
|
299
394
|
pruneCompletedProcs();
|
|
300
395
|
}
|
|
301
396
|
|
|
397
|
+
interface WatchConfig {
|
|
398
|
+
regex: RegExp;
|
|
399
|
+
target: "stdout" | "stderr" | "both";
|
|
400
|
+
timeoutSeconds: number;
|
|
401
|
+
}
|
|
402
|
+
|
|
302
403
|
function spawnBash(
|
|
303
404
|
context: BgContext,
|
|
304
405
|
command: string,
|
|
305
406
|
cwd?: string,
|
|
407
|
+
watchConfig?: WatchConfig,
|
|
306
408
|
): { bashId: string } | { error: string } {
|
|
307
409
|
if (!command.trim()) return { error: "empty command" };
|
|
308
410
|
const bashId = newBashId();
|
|
@@ -337,11 +439,29 @@ function spawnBash(
|
|
|
337
439
|
exitCode: null,
|
|
338
440
|
delivered: false,
|
|
339
441
|
startedAt: Date.now(),
|
|
442
|
+
watch: watchConfig
|
|
443
|
+
? {
|
|
444
|
+
regex: watchConfig.regex,
|
|
445
|
+
target: watchConfig.target,
|
|
446
|
+
matched: false,
|
|
447
|
+
finished: false,
|
|
448
|
+
stdoutCarry: "",
|
|
449
|
+
stderrCarry: "",
|
|
450
|
+
}
|
|
451
|
+
: undefined,
|
|
340
452
|
};
|
|
341
453
|
// Raw Buffers: decoding per chunk would split multi-byte characters that
|
|
342
454
|
// straddle a chunk boundary. The buffer decodes at read time instead.
|
|
343
|
-
child.stdout?.on("data", (c: Buffer) =>
|
|
344
|
-
|
|
455
|
+
child.stdout?.on("data", (c: Buffer) => {
|
|
456
|
+
proc.stdout.append(c);
|
|
457
|
+
const line = checkWatchMatch(proc, "stdout", c);
|
|
458
|
+
if (line !== undefined) handleWatchMatch(proc, line);
|
|
459
|
+
});
|
|
460
|
+
child.stderr?.on("data", (c: Buffer) => {
|
|
461
|
+
proc.stderr.append(c);
|
|
462
|
+
const line = checkWatchMatch(proc, "stderr", c);
|
|
463
|
+
if (line !== undefined) handleWatchMatch(proc, line);
|
|
464
|
+
});
|
|
345
465
|
child.on("close", (code) => {
|
|
346
466
|
finishProc(proc, code);
|
|
347
467
|
});
|
|
@@ -350,6 +470,13 @@ function spawnBash(
|
|
|
350
470
|
finishProc(proc, -1);
|
|
351
471
|
});
|
|
352
472
|
procs.set(bashId, proc);
|
|
473
|
+
if (proc.watch) {
|
|
474
|
+
proc.watch.timeoutTimer = setTimeout(
|
|
475
|
+
() => handleWatchTimeout(proc),
|
|
476
|
+
watchConfig!.timeoutSeconds * 1_000,
|
|
477
|
+
);
|
|
478
|
+
proc.watch.timeoutTimer.unref?.();
|
|
479
|
+
}
|
|
353
480
|
process.stderr.write(`[bg-bash] started ${bashId}: ${command.slice(0, 80)}\n`);
|
|
354
481
|
return { bashId };
|
|
355
482
|
}
|
|
@@ -421,6 +548,56 @@ function buildMcpServer(context: BgContext): McpServer {
|
|
|
421
548
|
},
|
|
422
549
|
);
|
|
423
550
|
|
|
551
|
+
server.tool(
|
|
552
|
+
"background_bash_watch",
|
|
553
|
+
[
|
|
554
|
+
"Start a background shell command and watch its stdout/stderr for a regex match, one line at a time.",
|
|
555
|
+
"The moment a line matches `match`, the process is stopped and the matching line plus buffered",
|
|
556
|
+
"output is injected into this session as a new turn — you do NOT need to poll.",
|
|
557
|
+
"If nothing matches within `timeout_seconds` (default 3600), or the command exits on its own first,",
|
|
558
|
+
"a final status turn is injected instead. Exactly one turn is ever injected per watch — this is",
|
|
559
|
+
"one-shot only, there is no repeat/streaming mode yet.",
|
|
560
|
+
"Prefer this over `background_bash_run` + manual `background_bash_output` polling when you are",
|
|
561
|
+
"waiting for a specific condition to appear (a deploy readiness line, an error) rather than for",
|
|
562
|
+
"the command itself to finish.",
|
|
563
|
+
].join(" "),
|
|
564
|
+
{
|
|
565
|
+
command: z.string().describe("Shell command (executed via bash -c)"),
|
|
566
|
+
match: z.string().describe("Regular expression tested against each output line"),
|
|
567
|
+
cwd: z.string().optional().describe("Working directory (absolute path)"),
|
|
568
|
+
stream: z
|
|
569
|
+
.enum(["stdout", "stderr", "both"])
|
|
570
|
+
.optional()
|
|
571
|
+
.describe("Which stream(s) to test against `match` (default both)"),
|
|
572
|
+
timeout_seconds: z
|
|
573
|
+
.number()
|
|
574
|
+
.int()
|
|
575
|
+
.positive()
|
|
576
|
+
.max(MAX_WATCH_TIMEOUT_SECONDS)
|
|
577
|
+
.optional()
|
|
578
|
+
.describe(
|
|
579
|
+
`Give up waiting for a match after this many seconds (default ${DEFAULT_WATCH_TIMEOUT_SECONDS}, max ${MAX_WATCH_TIMEOUT_SECONDS})`,
|
|
580
|
+
),
|
|
581
|
+
},
|
|
582
|
+
async ({ command, match, cwd, stream, timeout_seconds }) => {
|
|
583
|
+
let regex: RegExp;
|
|
584
|
+
try {
|
|
585
|
+
regex = new RegExp(match);
|
|
586
|
+
} catch (e) {
|
|
587
|
+
return mcpError(
|
|
588
|
+
`invalid regex in \`match\`: ${e instanceof Error ? e.message : String(e)}`,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
const result = spawnBash(context, command, cwd, {
|
|
592
|
+
regex,
|
|
593
|
+
target: stream ?? "both",
|
|
594
|
+
timeoutSeconds: timeout_seconds ?? DEFAULT_WATCH_TIMEOUT_SECONDS,
|
|
595
|
+
});
|
|
596
|
+
if ("error" in result) return mcpError(result.error);
|
|
597
|
+
return mcpOk(JSON.stringify({ bash_id: result.bashId, status: "watching" }));
|
|
598
|
+
},
|
|
599
|
+
);
|
|
600
|
+
|
|
424
601
|
server.tool(
|
|
425
602
|
"background_bash_kill",
|
|
426
603
|
"Terminate a background process (SIGTERM → SIGKILL after 5s). Idempotent.",
|
|
@@ -99,7 +99,7 @@ export function resolveOutputLanguage(): string {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** Browser.rs release tested with this Negotium version. */
|
|
102
|
-
export const BROWSER_RS_VERSION = "v0.1.
|
|
102
|
+
export const BROWSER_RS_VERSION = "v0.1.17";
|
|
103
103
|
/** Require the authenticated listener and the current Browser.rs tool contract. */
|
|
104
104
|
export const BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
105
105
|
|
|
@@ -14,6 +14,13 @@ const BOUNDED_OUTPUTS: Record<string, { argument: string; field: string }> = {
|
|
|
14
14
|
browser_api_request: { argument: "maxBytes", field: "body" },
|
|
15
15
|
browser_get_visible_text: { argument: "maxLength", field: "text" },
|
|
16
16
|
browser_get_visible_html: { argument: "maxLength", field: "html" },
|
|
17
|
+
// browser-rs 0.1.17's own `maxLength` doc comment: "Managed hosts may raise
|
|
18
|
+
// this so secrets are redacted before the caller-visible limit is applied."
|
|
19
|
+
// `field` is unused here (the tool returns a plain string, not `{content}`
|
|
20
|
+
// JSON — see `redactTextEntry`'s non-JSON fallback), but the `argument`
|
|
21
|
+
// override is what matters: it raises the request to MAX_SAFE_INTEGER so
|
|
22
|
+
// browser-rs never truncates a secret mid-string before redaction sees it.
|
|
23
|
+
browser_iframe_read: { argument: "maxLength", field: "content" },
|
|
17
24
|
};
|
|
18
25
|
|
|
19
26
|
interface RedactionBoundary {
|
|
@@ -1423,9 +1423,29 @@ export function startAiTurn(params: StartAiTurnParams): string | null {
|
|
|
1423
1423
|
].join("\n");
|
|
1424
1424
|
}
|
|
1425
1425
|
|
|
1426
|
+
/**
|
|
1427
|
+
* Per-turn `<system-reminder>` blocks, appended to the *user* prompt rather
|
|
1428
|
+
* than the system prompt.
|
|
1429
|
+
*
|
|
1430
|
+
* These facts flip between turns on the same model — whether the browser MCP
|
|
1431
|
+
* came up, whether this turn is an auto-resume. A prompt cache invalidates
|
|
1432
|
+
* from the first diverging byte, so putting them in the system prompt means
|
|
1433
|
+
* every flip re-reads the entire prompt: measured against the live DeepSeek
|
|
1434
|
+
* API, one flip at turn 15 of a 20-turn conversation cost 17,852 uncached
|
|
1435
|
+
* tokens (hit=0) versus 1,215 with the reminder on the user turn — 40% more
|
|
1436
|
+
* uncached tokens across the run. On the user turn the divergence lands at
|
|
1437
|
+
* the tail, where the text is new anyway.
|
|
1438
|
+
*
|
|
1439
|
+
* Applied after the conversation log is written, so a reminder never becomes
|
|
1440
|
+
* part of the recorded user message (it would then persist into synthesized
|
|
1441
|
+
* rollouts and defeat the point).
|
|
1442
|
+
*/
|
|
1443
|
+
const turnReminders: string[] = [];
|
|
1444
|
+
|
|
1426
1445
|
if (params.from === FROM_AUTO_CONTINUE) {
|
|
1427
|
-
|
|
1428
|
-
"
|
|
1446
|
+
turnReminders.push(
|
|
1447
|
+
"<system-reminder>이 턴은 설정 자동 조정(effort/model/agent) 후 자동 재개된 턴이다. effort/model/agent 난이도 재평가 및 설정 변경 없이 즉시 작업을 시작할 것.</system-reminder>",
|
|
1448
|
+
);
|
|
1429
1449
|
}
|
|
1430
1450
|
|
|
1431
1451
|
if (!silent && !sessionRetried) {
|
|
@@ -1538,25 +1558,28 @@ export function startAiTurn(params: StartAiTurnParams): string | null {
|
|
|
1538
1558
|
);
|
|
1539
1559
|
}
|
|
1540
1560
|
}
|
|
1541
|
-
let effectiveSystemPrompt = systemPrompt;
|
|
1542
1561
|
if (playwrightRequested && !browserProfileOwner) {
|
|
1543
|
-
|
|
1544
|
-
"<system-reminder>Browser tools are unavailable in this turn because browser profiles are private to the topic owner. Do not attempt browser calls or ask to reuse the owner's login state.</system-reminder>"
|
|
1545
|
-
|
|
1562
|
+
turnReminders.push(
|
|
1563
|
+
"<system-reminder>Browser tools are unavailable in this turn because browser profiles are private to the topic owner. Do not attempt browser calls or ask to reuse the owner's login state.</system-reminder>",
|
|
1564
|
+
);
|
|
1546
1565
|
}
|
|
1547
1566
|
if (consumePlaywrightUnavailable(userId, topic.title)) {
|
|
1548
|
-
|
|
1549
|
-
"<system-reminder>Playwright browser tools are UNAVAILABLE this turn. The `mcp__playwright__*` tools have been removed from this turn's catalog because the long-lived browser MCP could not be prepared. Do not attempt to call browser tools. If browser interaction is required, ask the user to retry shortly or use a non-browser alternative.</system-reminder>"
|
|
1550
|
-
|
|
1567
|
+
turnReminders.push(
|
|
1568
|
+
"<system-reminder>Playwright browser tools are UNAVAILABLE this turn. The `mcp__playwright__*` tools have been removed from this turn's catalog because the long-lived browser MCP could not be prepared. Do not attempt to call browser tools. If browser interaction is required, ask the user to retry shortly or use a non-browser alternative.</system-reminder>",
|
|
1569
|
+
);
|
|
1551
1570
|
}
|
|
1571
|
+
// The system prompt is now byte-identical across turns of a topic, so the
|
|
1572
|
+
// provider's prefix cache survives; per-turn facts ride the prompt tail.
|
|
1573
|
+
const effectivePrompt =
|
|
1574
|
+
turnReminders.length > 0 ? `${agentPrompt}\n\n${turnReminders.join("\n\n")}` : agentPrompt;
|
|
1552
1575
|
|
|
1553
1576
|
try {
|
|
1554
1577
|
yield* runAgent({
|
|
1555
1578
|
agent: agentKind,
|
|
1556
|
-
prompt:
|
|
1579
|
+
prompt: effectivePrompt,
|
|
1557
1580
|
attachments: promptAttachments,
|
|
1558
1581
|
cwd: workspaceCwd,
|
|
1559
|
-
systemPrompt
|
|
1582
|
+
systemPrompt,
|
|
1560
1583
|
sessionId,
|
|
1561
1584
|
userId,
|
|
1562
1585
|
vaultUserId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.2.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.2.9";
|
package/dist/runtime-helpers.js
CHANGED
|
@@ -451,7 +451,7 @@ var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
|
|
|
451
451
|
var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
452
452
|
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
453
453
|
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
454
|
-
var BROWSER_RS_VERSION = "v0.1.
|
|
454
|
+
var BROWSER_RS_VERSION = "v0.1.17";
|
|
455
455
|
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
456
456
|
function versionAtLeast(actualVersion, minimumVersion) {
|
|
457
457
|
const actual = actualVersion.split(".").map(Number);
|
|
@@ -1132,4 +1132,4 @@ export {
|
|
|
1132
1132
|
CLAUDE_EFFORT_VALUES
|
|
1133
1133
|
};
|
|
1134
1134
|
|
|
1135
|
-
//# debugId=
|
|
1135
|
+
//# debugId=078B7BFBF513B96264756E2164756E21
|