@khalilgharbaoui/opencode-claude-code-plugin 0.57.2 → 0.59.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/index.d.ts +22 -4
- package/dist/index.js +1043 -279
- package/dist/index.js.map +1 -1
- package/dist/tui.js +586 -85
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +23 -12
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/claude-code-language-model.ts
|
|
2
|
-
import { join as
|
|
2
|
+
import { join as join19 } from "path";
|
|
3
3
|
|
|
4
4
|
// src/ids.ts
|
|
5
5
|
import { randomInt } from "crypto";
|
|
@@ -272,6 +272,8 @@ var MAX_TOASTED_MESSAGES = 200;
|
|
|
272
272
|
var toastBurstLimit = 3;
|
|
273
273
|
var toastBurstWindowMs = 2e3;
|
|
274
274
|
var activeSink = null;
|
|
275
|
+
var activeNotifier = null;
|
|
276
|
+
var bufferedNotices = [];
|
|
275
277
|
var bufferedEntries = [];
|
|
276
278
|
var droppedBeforeSink = 0;
|
|
277
279
|
var toastedMessages = /* @__PURE__ */ new Set();
|
|
@@ -296,6 +298,13 @@ function setLogSink(sink) {
|
|
|
296
298
|
});
|
|
297
299
|
}
|
|
298
300
|
}
|
|
301
|
+
function setToastNotifier(notifier) {
|
|
302
|
+
activeNotifier = notifier;
|
|
303
|
+
if (!notifier) return;
|
|
304
|
+
const pending2 = bufferedNotices;
|
|
305
|
+
bufferedNotices = [];
|
|
306
|
+
for (const entry of pending2) maybeToast(entry, notifier);
|
|
307
|
+
}
|
|
299
308
|
function routeToSink(entry) {
|
|
300
309
|
if (!activeSink) {
|
|
301
310
|
if (bufferedEntries.length >= MAX_BUFFERED_SINK_ENTRIES) {
|
|
@@ -314,18 +323,34 @@ function deliverToSink(entry) {
|
|
|
314
323
|
sink.log(entry);
|
|
315
324
|
} catch {
|
|
316
325
|
}
|
|
326
|
+
maybeToast(entry, (toast) => sink.toast(toast));
|
|
327
|
+
}
|
|
328
|
+
function maybeToast(entry, send) {
|
|
317
329
|
if (entry.level !== "warn" && entry.level !== "error") return;
|
|
318
330
|
if (toastedMessages.has(entry.message)) return;
|
|
319
331
|
if (toastedMessages.size < MAX_TOASTED_MESSAGES) toastedMessages.add(entry.message);
|
|
320
332
|
if (!allowToastNow()) return;
|
|
321
|
-
sendToast(
|
|
333
|
+
sendToast(send, entry);
|
|
322
334
|
}
|
|
323
|
-
function sendToast(
|
|
335
|
+
function sendToast(send, entry) {
|
|
324
336
|
try {
|
|
325
|
-
|
|
337
|
+
send(entry);
|
|
326
338
|
} catch {
|
|
327
339
|
}
|
|
328
340
|
}
|
|
341
|
+
function toastTarget() {
|
|
342
|
+
const sink = activeSink;
|
|
343
|
+
if (sink) return (entry) => sink.toast(entry);
|
|
344
|
+
return activeNotifier;
|
|
345
|
+
}
|
|
346
|
+
function routeToNotifier(entry) {
|
|
347
|
+
const notifier = activeNotifier;
|
|
348
|
+
if (notifier) {
|
|
349
|
+
maybeToast(entry, notifier);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (bufferedNotices.length < MAX_BUFFERED_SINK_ENTRIES) bufferedNotices.push(entry);
|
|
353
|
+
}
|
|
329
354
|
function allowToastNow() {
|
|
330
355
|
const now = Date.now();
|
|
331
356
|
if (now - burstWindowStart >= toastBurstWindowMs) {
|
|
@@ -347,10 +372,11 @@ function armBurstSummary() {
|
|
|
347
372
|
const suppressed = burstSuppressed;
|
|
348
373
|
burstSuppressed = 0;
|
|
349
374
|
if (suppressed <= 0) return;
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
352
|
-
const
|
|
353
|
-
|
|
375
|
+
const target = toastTarget();
|
|
376
|
+
if (!target) return;
|
|
377
|
+
const where = activeSink ? "opencode's log" : "the plugin log file";
|
|
378
|
+
const message = `${suppressed} more claude-code warning(s) were not shown; the full text is in ${where}`;
|
|
379
|
+
sendToast(target, { level: "warn", message, line: fmt("WARN", message) });
|
|
354
380
|
}, toastBurstWindowMs);
|
|
355
381
|
burstSummaryTimer.unref?.();
|
|
356
382
|
}
|
|
@@ -366,6 +392,7 @@ function emit(level, msg, data) {
|
|
|
366
392
|
routeToSink({ level, message: msg, data, line });
|
|
367
393
|
} else {
|
|
368
394
|
console.error(line);
|
|
395
|
+
if (level === "warn" || level === "error") routeToNotifier({ level, message: msg, data, line });
|
|
369
396
|
}
|
|
370
397
|
}
|
|
371
398
|
writeToFile(line);
|
|
@@ -6374,10 +6401,71 @@ function watchChildSession(sessionID) {
|
|
|
6374
6401
|
});
|
|
6375
6402
|
return () => answer;
|
|
6376
6403
|
}
|
|
6404
|
+
var eventRunStates = /* @__PURE__ */ new Map();
|
|
6405
|
+
var MAX_EVENT_RUN_STATES = 512;
|
|
6406
|
+
var openEventFeeds = 0;
|
|
6407
|
+
function openSessionRunEventFeed() {
|
|
6408
|
+
openEventFeeds += 1;
|
|
6409
|
+
let closed = false;
|
|
6410
|
+
return () => {
|
|
6411
|
+
if (closed) return;
|
|
6412
|
+
closed = true;
|
|
6413
|
+
openEventFeeds = Math.max(0, openEventFeeds - 1);
|
|
6414
|
+
eventRunStates.clear();
|
|
6415
|
+
};
|
|
6416
|
+
}
|
|
6417
|
+
function noteSessionRunEvent(event) {
|
|
6418
|
+
if (openEventFeeds === 0 || !event || typeof event !== "object") return;
|
|
6419
|
+
const { type, data } = event;
|
|
6420
|
+
if (typeof type !== "string" || !data || typeof data !== "object") return;
|
|
6421
|
+
const sessionID = data.sessionID;
|
|
6422
|
+
if (typeof sessionID !== "string" || !sessionID || sessionID === "default") return;
|
|
6423
|
+
if (type === "session.deleted") {
|
|
6424
|
+
eventRunStates.delete(sessionID);
|
|
6425
|
+
return;
|
|
6426
|
+
}
|
|
6427
|
+
let next;
|
|
6428
|
+
switch (type) {
|
|
6429
|
+
case "session.execution.started":
|
|
6430
|
+
next = "busy";
|
|
6431
|
+
break;
|
|
6432
|
+
case "session.execution.succeeded":
|
|
6433
|
+
case "session.execution.failed":
|
|
6434
|
+
case "session.execution.interrupted":
|
|
6435
|
+
case "session.idle":
|
|
6436
|
+
next = "idle";
|
|
6437
|
+
break;
|
|
6438
|
+
case "session.status": {
|
|
6439
|
+
const status = data.status?.type;
|
|
6440
|
+
if (typeof status === "string") next = status === "idle" ? "idle" : "busy";
|
|
6441
|
+
break;
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
if (!next) return;
|
|
6445
|
+
eventRunStates.delete(sessionID);
|
|
6446
|
+
eventRunStates.set(sessionID, next);
|
|
6447
|
+
while (eventRunStates.size > MAX_EVENT_RUN_STATES) {
|
|
6448
|
+
const oldest = eventRunStates.keys().next().value;
|
|
6449
|
+
if (oldest === void 0) break;
|
|
6450
|
+
eventRunStates.delete(oldest);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
function sessionRunStateFromEvents(sessionID) {
|
|
6454
|
+
if (openEventFeeds === 0) return "unknown";
|
|
6455
|
+
return eventRunStates.get(sessionID) ?? "unknown";
|
|
6456
|
+
}
|
|
6457
|
+
function busySessionsFromEvents() {
|
|
6458
|
+
if (openEventFeeds === 0) return void 0;
|
|
6459
|
+
const busy = {};
|
|
6460
|
+
for (const [sessionID, state] of eventRunStates) {
|
|
6461
|
+
if (state === "busy") busy[sessionID] = { type: "busy" };
|
|
6462
|
+
}
|
|
6463
|
+
return busy;
|
|
6464
|
+
}
|
|
6377
6465
|
async function fetchSessionRunState(sessionID, directory) {
|
|
6378
6466
|
if (!sessionID || sessionID === "default") return "unknown";
|
|
6379
6467
|
const status = opencodeClient?.session?.status;
|
|
6380
|
-
if (!status) return
|
|
6468
|
+
if (!status) return sessionRunStateFromEvents(sessionID);
|
|
6381
6469
|
try {
|
|
6382
6470
|
const dir = directory ?? await fetchSessionDirectory(sessionID);
|
|
6383
6471
|
const res = await status.call(
|
|
@@ -7734,6 +7822,27 @@ function deleteActiveProcessesForSession(sessionID) {
|
|
|
7734
7822
|
forgetDispatchChoicesForSession(sessionID);
|
|
7735
7823
|
return released;
|
|
7736
7824
|
}
|
|
7825
|
+
var STOPPED_TURN_MESSAGE = "opencode stopped the turn while it was running its proxy tool calls";
|
|
7826
|
+
function releaseParkedProxyCallsForSession(sessionID, reason = STOPPED_TURN_MESSAGE) {
|
|
7827
|
+
if (!sessionID || sessionID === "default") return [];
|
|
7828
|
+
const released = [];
|
|
7829
|
+
for (const [key, ap] of [...activeProcesses]) {
|
|
7830
|
+
const owned = ap.opencodeSessionID === sessionID || describeSessionKey(key).session === sessionID;
|
|
7831
|
+
if (!owned) continue;
|
|
7832
|
+
if (ap.lineEmitter.listenerCount("line") > 0) continue;
|
|
7833
|
+
if (getPendingProxyCalls(key).length === 0) continue;
|
|
7834
|
+
log.info("opencode stopped the turn at a proxy tool boundary; releasing pending calls", {
|
|
7835
|
+
sessionKey: key,
|
|
7836
|
+
sessionID
|
|
7837
|
+
});
|
|
7838
|
+
void interruptTurn(ap).then((idle) => {
|
|
7839
|
+
log.info("interrupt sent for aborted turn", { sk: key, idle });
|
|
7840
|
+
});
|
|
7841
|
+
rejectAllPendingProxyCallsForSession(key, new Error(reason));
|
|
7842
|
+
released.push(key);
|
|
7843
|
+
}
|
|
7844
|
+
return released;
|
|
7845
|
+
}
|
|
7737
7846
|
function killAllActiveProcesses() {
|
|
7738
7847
|
const keys = [...activeProcesses.keys()];
|
|
7739
7848
|
for (const key of keys) deleteActiveProcess(key);
|
|
@@ -8373,17 +8482,14 @@ async function fetchAsideHistory(client, sessionID, question) {
|
|
|
8373
8482
|
return [];
|
|
8374
8483
|
}
|
|
8375
8484
|
}
|
|
8376
|
-
async function
|
|
8485
|
+
async function runBtwCommand(client, input, options = {}) {
|
|
8377
8486
|
const question = input.arguments.trim();
|
|
8378
|
-
if (!question)
|
|
8379
|
-
showToast(client, { title: "btw", message: SIDE_QUESTION_USAGE, variant: "warning", duration: 6e3 });
|
|
8380
|
-
throw new BtwHandledError("/btw needs a question.");
|
|
8381
|
-
}
|
|
8487
|
+
if (!question) return "bare";
|
|
8382
8488
|
const active = await waitForAsideProcess(client, input.sessionID, options);
|
|
8383
8489
|
const transport = active?.asideTransport;
|
|
8384
8490
|
if (!active || !transport) {
|
|
8385
8491
|
if (active) log.info("btw: process has no aside transport, leaving it to the turn", { sessionID: input.sessionID });
|
|
8386
|
-
return;
|
|
8492
|
+
return "release";
|
|
8387
8493
|
}
|
|
8388
8494
|
let busy = false;
|
|
8389
8495
|
let inlineDone = false;
|
|
@@ -8444,7 +8550,7 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
8444
8550
|
}
|
|
8445
8551
|
);
|
|
8446
8552
|
}
|
|
8447
|
-
if (!busy) return;
|
|
8553
|
+
if (!busy) return "release";
|
|
8448
8554
|
const started2 = Date.now();
|
|
8449
8555
|
const outcome = await Promise.race([
|
|
8450
8556
|
inlineDelivered,
|
|
@@ -8457,18 +8563,46 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
8457
8563
|
sessionID: input.sessionID,
|
|
8458
8564
|
waitedMs: Date.now() - started2
|
|
8459
8565
|
});
|
|
8460
|
-
|
|
8566
|
+
return "inline";
|
|
8461
8567
|
}
|
|
8462
8568
|
log.info("btw: turn over, releasing the /btw message", {
|
|
8463
8569
|
sessionID: input.sessionID,
|
|
8464
8570
|
idle: outcome === "idle",
|
|
8465
8571
|
waitedMs: Date.now() - started2
|
|
8466
8572
|
});
|
|
8573
|
+
return outcome === "timeout" ? "timeout" : "release";
|
|
8574
|
+
}
|
|
8575
|
+
async function handleBtwCommand(client, input, options = {}) {
|
|
8576
|
+
const outcome = await runBtwCommand(client, input, options);
|
|
8577
|
+
if (outcome === "bare") {
|
|
8578
|
+
showToast(client, { title: "btw", message: SIDE_QUESTION_USAGE, variant: "warning", duration: 6e3 });
|
|
8579
|
+
throw new BtwHandledError("/btw needs a question.");
|
|
8580
|
+
}
|
|
8581
|
+
if (outcome === "inline") throw new BtwHandledError(BTW_INLINE_HANDLED_MESSAGE);
|
|
8467
8582
|
if (outcome === "timeout") {
|
|
8468
8583
|
showToast(client, { title: "btw", message: BTW_TURN_TOO_LONG_MESSAGE, variant: "warning", duration: 8e3 });
|
|
8469
8584
|
throw new BtwHandledError(BTW_TURN_TOO_LONG_MESSAGE);
|
|
8470
8585
|
}
|
|
8471
8586
|
}
|
|
8587
|
+
async function askBtwThenRelease(client, input, release2, options = {}) {
|
|
8588
|
+
let outcome;
|
|
8589
|
+
try {
|
|
8590
|
+
outcome = await runBtwCommand(client, input, options);
|
|
8591
|
+
} catch (error) {
|
|
8592
|
+
log.warn("btw: the early aside failed; sending the /btw message as it is", {
|
|
8593
|
+
sessionID: input.sessionID,
|
|
8594
|
+
error: errorText(error)
|
|
8595
|
+
});
|
|
8596
|
+
outcome = "failed";
|
|
8597
|
+
}
|
|
8598
|
+
if (outcome === "inline") return outcome;
|
|
8599
|
+
try {
|
|
8600
|
+
await release2();
|
|
8601
|
+
} catch (error) {
|
|
8602
|
+
log.warn("btw: could not send the /btw message", { sessionID: input.sessionID, error: errorText(error) });
|
|
8603
|
+
}
|
|
8604
|
+
return outcome;
|
|
8605
|
+
}
|
|
8472
8606
|
|
|
8473
8607
|
// src/budget-ledger.ts
|
|
8474
8608
|
var BUDGET_MARKER = "\u258C **budget:**";
|
|
@@ -8638,7 +8772,9 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
8638
8772
|
"automatic account switch skipped an account whose runtime could not be prepared",
|
|
8639
8773
|
"background dispatch refused by maxBackgroundSubagents",
|
|
8640
8774
|
"btw: a turn is running but no claude process appeared for it",
|
|
8775
|
+
"btw: could not send the /btw message",
|
|
8641
8776
|
"btw: early aside failed; the message will ask again",
|
|
8777
|
+
"btw: the early aside failed; sending the /btw message as it is",
|
|
8642
8778
|
"budget option refused: not a positive number of dollars",
|
|
8643
8779
|
"budgetWarnAt refused: expected percentages between 0 and 100",
|
|
8644
8780
|
"cannot interrupt this transport; waiting for the turn to end",
|
|
@@ -8727,7 +8863,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
8727
8863
|
"no configured account could take this turn; it ends here",
|
|
8728
8864
|
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
8729
8865
|
"no stdout after envelope write; respawning claude process to resume conversation",
|
|
8730
|
-
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction",
|
|
8866
|
+
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction, and stopped turns keep their proxy calls until the next message",
|
|
8731
8867
|
"opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
|
|
8732
8868
|
"pending proxy call arrived after stream close; rejecting",
|
|
8733
8869
|
'permissionMode "plan" is enforced: claude cannot edit files or run commands, and --dangerously-skip-permissions is deliberately not passed so it stays that way. Headless Claude Code is not offered an ExitPlanMode tool, so nothing releases plan mode mid-session; approving a plan in chat does not unlock writes. Leaving plan mode means changing the config and restarting opencode.',
|
|
@@ -9635,6 +9771,625 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
9635
9771
|
}
|
|
9636
9772
|
}
|
|
9637
9773
|
|
|
9774
|
+
// src/turn-history.ts
|
|
9775
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, renameSync as renameSync6, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
9776
|
+
import { homedir as homedir11 } from "os";
|
|
9777
|
+
import { join as join11 } from "path";
|
|
9778
|
+
|
|
9779
|
+
// src/turn-progress-store.ts
|
|
9780
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync5, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
9781
|
+
import { homedir as homedir10 } from "os";
|
|
9782
|
+
import { join as join10 } from "path";
|
|
9783
|
+
var MAX_TURN_PROGRESS = 64;
|
|
9784
|
+
var TURN_PROGRESS_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
9785
|
+
var TAG = /^[0-9a-f]{8}$/;
|
|
9786
|
+
var pathOverride4 = null;
|
|
9787
|
+
function turnProgressStorePath() {
|
|
9788
|
+
if (pathOverride4) return pathOverride4;
|
|
9789
|
+
const stateRoot = process.env.XDG_STATE_HOME || join10(homedir10(), ".local", "state");
|
|
9790
|
+
return join10(stateRoot, "opencode-claude-code-plugin", "turn-progress.json");
|
|
9791
|
+
}
|
|
9792
|
+
function isProgress(value) {
|
|
9793
|
+
const record = value;
|
|
9794
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) return false;
|
|
9795
|
+
const count = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0;
|
|
9796
|
+
return count(record.turn) && count(record.out) && count(record.at);
|
|
9797
|
+
}
|
|
9798
|
+
function readEntries(file, now) {
|
|
9799
|
+
const out = /* @__PURE__ */ new Map();
|
|
9800
|
+
try {
|
|
9801
|
+
const parsed = JSON.parse(readFileSync6(file, "utf8"));
|
|
9802
|
+
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
9803
|
+
if (!entries || typeof entries !== "object") return out;
|
|
9804
|
+
const cutoff = now - TURN_PROGRESS_MAX_AGE_MS;
|
|
9805
|
+
const valid = Object.entries(entries).filter((entry) => TAG.test(entry[0]) && isProgress(entry[1])).filter(([, record]) => record.at >= cutoff).sort((a, b) => a[1].at - b[1].at).slice(-MAX_TURN_PROGRESS);
|
|
9806
|
+
for (const [tag, record] of valid) out.set(tag, { turn: record.turn, out: Math.round(record.out), at: record.at });
|
|
9807
|
+
} catch {
|
|
9808
|
+
}
|
|
9809
|
+
return out;
|
|
9810
|
+
}
|
|
9811
|
+
function recordTurnProgress(tag, progress, now = Date.now()) {
|
|
9812
|
+
const record = { turn: progress.turn, out: progress.out, at: now };
|
|
9813
|
+
if (!TAG.test(tag) || !isProgress(record)) return false;
|
|
9814
|
+
const file = turnProgressStorePath();
|
|
9815
|
+
const merged = readEntries(file, now);
|
|
9816
|
+
merged.delete(tag);
|
|
9817
|
+
merged.set(tag, { ...record, out: Math.round(record.out) });
|
|
9818
|
+
const kept = [...merged.entries()].slice(-MAX_TURN_PROGRESS);
|
|
9819
|
+
try {
|
|
9820
|
+
mkdirSync6(join10(file, ".."), { recursive: true, mode: 448 });
|
|
9821
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
9822
|
+
writeFileSync6(tmp, JSON.stringify({ version: 1, turns: Object.fromEntries(kept) }), { mode: 384 });
|
|
9823
|
+
renameSync5(tmp, file);
|
|
9824
|
+
} catch {
|
|
9825
|
+
return false;
|
|
9826
|
+
}
|
|
9827
|
+
return true;
|
|
9828
|
+
}
|
|
9829
|
+
|
|
9830
|
+
// src/turn-history.ts
|
|
9831
|
+
var MAX_TURN_SAMPLES = 48;
|
|
9832
|
+
var MAX_TURN_KEYS = 64;
|
|
9833
|
+
var TURN_SAMPLE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
9834
|
+
var SESSION_TAG = /^[0-9a-f]{8}$/;
|
|
9835
|
+
var pathOverride5 = null;
|
|
9836
|
+
function turnHistoryStorePath() {
|
|
9837
|
+
if (pathOverride5) return pathOverride5;
|
|
9838
|
+
const stateRoot = process.env.XDG_STATE_HOME || join11(homedir11(), ".local", "state");
|
|
9839
|
+
return join11(stateRoot, "opencode-claude-code-plugin", "turn-history.json");
|
|
9840
|
+
}
|
|
9841
|
+
function turnHistoryKey(agent, model) {
|
|
9842
|
+
return `${agent || "main"}/${model}`;
|
|
9843
|
+
}
|
|
9844
|
+
function sessionTag(sessionID) {
|
|
9845
|
+
if (!sessionID || sessionID === "default") return void 0;
|
|
9846
|
+
let hash = 2166136261;
|
|
9847
|
+
for (let index = 0; index < sessionID.length; index += 1) {
|
|
9848
|
+
hash ^= sessionID.charCodeAt(index);
|
|
9849
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
9850
|
+
}
|
|
9851
|
+
return hash.toString(16).padStart(8, "0");
|
|
9852
|
+
}
|
|
9853
|
+
var INJECTED_BLOCKS = [
|
|
9854
|
+
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
9855
|
+
/<dcp-system-reminder>[\s\S]*?<\/dcp-system-reminder>/g,
|
|
9856
|
+
/<dcp-message-id>[\s\S]*?<\/dcp-message-id>/g
|
|
9857
|
+
];
|
|
9858
|
+
function promptChars(texts) {
|
|
9859
|
+
if (texts.length === 0) return void 0;
|
|
9860
|
+
let total = 0;
|
|
9861
|
+
for (const text of texts) {
|
|
9862
|
+
let stripped = text;
|
|
9863
|
+
for (const block of INJECTED_BLOCKS) stripped = stripped.replace(block, "");
|
|
9864
|
+
total += stripped.trim().length;
|
|
9865
|
+
}
|
|
9866
|
+
return total;
|
|
9867
|
+
}
|
|
9868
|
+
function textsOf(content) {
|
|
9869
|
+
if (typeof content === "string") return [content];
|
|
9870
|
+
if (!Array.isArray(content)) return [];
|
|
9871
|
+
const texts = [];
|
|
9872
|
+
for (const part of content) {
|
|
9873
|
+
if (!part || typeof part !== "object" || part.type !== "text") continue;
|
|
9874
|
+
const text = part.text;
|
|
9875
|
+
if (typeof text === "string") texts.push(text);
|
|
9876
|
+
}
|
|
9877
|
+
return texts;
|
|
9878
|
+
}
|
|
9879
|
+
function newestPromptChars(prompt) {
|
|
9880
|
+
for (let index = prompt.length - 1; index >= 0; index -= 1) {
|
|
9881
|
+
if (prompt[index].role === "user") return promptChars(textsOf(prompt[index].content));
|
|
9882
|
+
}
|
|
9883
|
+
return void 0;
|
|
9884
|
+
}
|
|
9885
|
+
function toolResultParts(message) {
|
|
9886
|
+
if (!Array.isArray(message.content)) return [];
|
|
9887
|
+
return message.content.filter(
|
|
9888
|
+
(part) => !!part && typeof part === "object" && part.type === "tool-result"
|
|
9889
|
+
);
|
|
9890
|
+
}
|
|
9891
|
+
function startsTurn(prompt, pendingToolCallIds) {
|
|
9892
|
+
const last = prompt[prompt.length - 1];
|
|
9893
|
+
if (!last || last.role !== "user") return false;
|
|
9894
|
+
const before = prompt[prompt.length - 2];
|
|
9895
|
+
if (before === void 0 || before.role !== "tool") return true;
|
|
9896
|
+
if (!pendingToolCallIds) return false;
|
|
9897
|
+
return toolResultParts(before).every((part) => typeof part.toolCallId !== "string" || !pendingToolCallIds.has(part.toolCallId));
|
|
9898
|
+
}
|
|
9899
|
+
function turnToolCalls(prompt, from) {
|
|
9900
|
+
let start = 0;
|
|
9901
|
+
if (from !== void 0 && from >= 0 && from <= prompt.length) {
|
|
9902
|
+
start = from;
|
|
9903
|
+
} else {
|
|
9904
|
+
for (let index = prompt.length - 1; index >= 0; index -= 1) {
|
|
9905
|
+
if (prompt[index].role !== "user") continue;
|
|
9906
|
+
const before = prompt[index - 1];
|
|
9907
|
+
if (before === void 0 || before.role !== "tool") {
|
|
9908
|
+
start = index;
|
|
9909
|
+
break;
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
let calls = 0;
|
|
9914
|
+
for (let index = start; index < prompt.length; index += 1) {
|
|
9915
|
+
const message = prompt[index];
|
|
9916
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
9917
|
+
for (const part of message.content) {
|
|
9918
|
+
if (part && typeof part === "object" && part.type === "tool-call") calls += 1;
|
|
9919
|
+
}
|
|
9920
|
+
}
|
|
9921
|
+
return calls;
|
|
9922
|
+
}
|
|
9923
|
+
function lastToolResultFailed(prompt, from) {
|
|
9924
|
+
for (let index = prompt.length - 1; index >= Math.max(0, from); index -= 1) {
|
|
9925
|
+
const message = prompt[index];
|
|
9926
|
+
if (message.role === "user") continue;
|
|
9927
|
+
if (message.role !== "tool") return false;
|
|
9928
|
+
const parts = toolResultParts(message);
|
|
9929
|
+
const newest = parts[parts.length - 1];
|
|
9930
|
+
const output = newest?.output;
|
|
9931
|
+
const type = output && typeof output === "object" ? output.type : void 0;
|
|
9932
|
+
return type === "error-text" || type === "error-json" || type === "execution-denied";
|
|
9933
|
+
}
|
|
9934
|
+
return false;
|
|
9935
|
+
}
|
|
9936
|
+
var MAX_TURN_STARTS = 256;
|
|
9937
|
+
var turnStarts = /* @__PURE__ */ new Map();
|
|
9938
|
+
function markTurnStart(key, start) {
|
|
9939
|
+
turnStarts.delete(key);
|
|
9940
|
+
if (turnStarts.size >= MAX_TURN_STARTS) {
|
|
9941
|
+
const oldest = turnStarts.keys().next().value;
|
|
9942
|
+
if (oldest !== void 0) turnStarts.delete(oldest);
|
|
9943
|
+
}
|
|
9944
|
+
turnStarts.set(key, {
|
|
9945
|
+
at: start.at,
|
|
9946
|
+
...start.chars !== void 0 ? { chars: start.chars } : {},
|
|
9947
|
+
...start.promptLength !== void 0 ? { promptLength: start.promptLength } : {},
|
|
9948
|
+
...start.session !== void 0 ? { session: start.session } : {}
|
|
9949
|
+
});
|
|
9950
|
+
}
|
|
9951
|
+
function noteTurnOutput(key, tokens, now = Date.now()) {
|
|
9952
|
+
const start = turnStarts.get(key);
|
|
9953
|
+
if (!start || !Number.isFinite(tokens) || tokens < 0) return;
|
|
9954
|
+
start.out = (start.out ?? 0) + tokens;
|
|
9955
|
+
if (start.session) recordTurnProgress(start.session, { turn: start.at, out: start.out }, now);
|
|
9956
|
+
}
|
|
9957
|
+
function takeTurnStart(key) {
|
|
9958
|
+
const start = turnStarts.get(key);
|
|
9959
|
+
turnStarts.delete(key);
|
|
9960
|
+
return start;
|
|
9961
|
+
}
|
|
9962
|
+
function isSample(value) {
|
|
9963
|
+
const sample = value;
|
|
9964
|
+
if (!sample || typeof sample !== "object" || Array.isArray(sample)) return false;
|
|
9965
|
+
if (typeof sample.ms !== "number" || !Number.isFinite(sample.ms) || sample.ms < 0) return false;
|
|
9966
|
+
if (typeof sample.at !== "number" || !Number.isFinite(sample.at)) return false;
|
|
9967
|
+
if (sample.chars !== void 0 && (typeof sample.chars !== "number" || !Number.isFinite(sample.chars) || sample.chars < 0)) {
|
|
9968
|
+
return false;
|
|
9969
|
+
}
|
|
9970
|
+
if (sample.effort !== void 0 && typeof sample.effort !== "string") return false;
|
|
9971
|
+
if (sample.calls !== void 0 && (typeof sample.calls !== "number" || !Number.isFinite(sample.calls) || sample.calls < 0)) {
|
|
9972
|
+
return false;
|
|
9973
|
+
}
|
|
9974
|
+
if (sample.out !== void 0 && (typeof sample.out !== "number" || !Number.isFinite(sample.out) || sample.out < 0)) {
|
|
9975
|
+
return false;
|
|
9976
|
+
}
|
|
9977
|
+
if (sample.failed !== void 0 && typeof sample.failed !== "boolean") return false;
|
|
9978
|
+
if (sample.session !== void 0 && (typeof sample.session !== "string" || !SESSION_TAG.test(sample.session))) {
|
|
9979
|
+
return false;
|
|
9980
|
+
}
|
|
9981
|
+
return true;
|
|
9982
|
+
}
|
|
9983
|
+
function cleanSample(sample) {
|
|
9984
|
+
return {
|
|
9985
|
+
ms: Math.round(sample.ms),
|
|
9986
|
+
...sample.chars !== void 0 ? { chars: Math.round(sample.chars) } : {},
|
|
9987
|
+
...sample.effort ? { effort: sample.effort } : {},
|
|
9988
|
+
...sample.calls !== void 0 ? { calls: Math.round(sample.calls) } : {},
|
|
9989
|
+
...sample.out !== void 0 ? { out: Math.round(sample.out) } : {},
|
|
9990
|
+
...sample.failed ? { failed: true } : {},
|
|
9991
|
+
...sample.session ? { session: sample.session } : {},
|
|
9992
|
+
at: sample.at
|
|
9993
|
+
};
|
|
9994
|
+
}
|
|
9995
|
+
function readEntries2(file, now) {
|
|
9996
|
+
const out = /* @__PURE__ */ new Map();
|
|
9997
|
+
try {
|
|
9998
|
+
const parsed = JSON.parse(readFileSync7(file, "utf8"));
|
|
9999
|
+
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
10000
|
+
if (!entries || typeof entries !== "object") return out;
|
|
10001
|
+
const cutoff = now - TURN_SAMPLE_MAX_AGE_MS;
|
|
10002
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
10003
|
+
if (!key || !Array.isArray(value)) continue;
|
|
10004
|
+
const samples = value.filter(isSample).filter((sample) => sample.at >= cutoff).sort((a, b) => a.at - b.at).slice(-MAX_TURN_SAMPLES).map(cleanSample);
|
|
10005
|
+
if (samples.length > 0) out.set(key, samples);
|
|
10006
|
+
}
|
|
10007
|
+
} catch {
|
|
10008
|
+
}
|
|
10009
|
+
return out;
|
|
10010
|
+
}
|
|
10011
|
+
function bounded2(history) {
|
|
10012
|
+
const newest = (samples) => samples[samples.length - 1]?.at ?? 0;
|
|
10013
|
+
return new Map([...history.entries()].sort((a, b) => newest(a[1]) - newest(b[1])).slice(-MAX_TURN_KEYS));
|
|
10014
|
+
}
|
|
10015
|
+
function recordTurnSample(key, sample) {
|
|
10016
|
+
if (!key || !isSample(sample)) return false;
|
|
10017
|
+
const file = turnHistoryStorePath();
|
|
10018
|
+
const merged = readEntries2(file, sample.at);
|
|
10019
|
+
const samples = [...merged.get(key) ?? [], cleanSample(sample)].sort((a, b) => a.at - b.at).slice(-MAX_TURN_SAMPLES);
|
|
10020
|
+
merged.set(key, samples);
|
|
10021
|
+
const kept = bounded2(merged);
|
|
10022
|
+
try {
|
|
10023
|
+
mkdirSync7(join11(file, ".."), { recursive: true, mode: 448 });
|
|
10024
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
10025
|
+
writeFileSync7(tmp, JSON.stringify({ version: 1, turns: Object.fromEntries(kept) }), { mode: 384 });
|
|
10026
|
+
renameSync6(tmp, file);
|
|
10027
|
+
} catch {
|
|
10028
|
+
return false;
|
|
10029
|
+
}
|
|
10030
|
+
return true;
|
|
10031
|
+
}
|
|
10032
|
+
function createTurnHistoryReader(file = turnHistoryStorePath) {
|
|
10033
|
+
let identity = "";
|
|
10034
|
+
let cached2 = /* @__PURE__ */ new Map();
|
|
10035
|
+
return {
|
|
10036
|
+
read(now = Date.now()) {
|
|
10037
|
+
const path15 = file();
|
|
10038
|
+
let next = "";
|
|
10039
|
+
try {
|
|
10040
|
+
const stats = statSync6(path15);
|
|
10041
|
+
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
10042
|
+
} catch {
|
|
10043
|
+
next = "missing";
|
|
10044
|
+
}
|
|
10045
|
+
if (next !== identity) {
|
|
10046
|
+
identity = next;
|
|
10047
|
+
cached2 = next === "missing" ? /* @__PURE__ */ new Map() : readEntries2(path15, now);
|
|
10048
|
+
}
|
|
10049
|
+
return cached2;
|
|
10050
|
+
}
|
|
10051
|
+
};
|
|
10052
|
+
}
|
|
10053
|
+
|
|
10054
|
+
// src/turn-model.ts
|
|
10055
|
+
var MIN_TURN_SAMPLES = 3;
|
|
10056
|
+
var CHARS_BANDWIDTH = Math.log(4);
|
|
10057
|
+
var OTHER_EFFORT_WEIGHT = 0.5;
|
|
10058
|
+
var MIN_EFFORT_RATIO_SAMPLES = 2;
|
|
10059
|
+
var RECENCY_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
10060
|
+
var MIN_CONDITIONED_SAMPLES = 1;
|
|
10061
|
+
var SHRINKAGE_KAPPA = 2;
|
|
10062
|
+
var CONDITIONED_KAPPA = 1;
|
|
10063
|
+
var DEFAULT_LOG_SIGMA = 0.8;
|
|
10064
|
+
var MIN_LOG_SIGMA = 0.2;
|
|
10065
|
+
var MAX_LOG_SIGMA = 1.6;
|
|
10066
|
+
var CALIBRATION_PRIOR_RESIDUALS = 12;
|
|
10067
|
+
var MAX_ASSESSED_PER_KEY = 16;
|
|
10068
|
+
var MAX_CALIBRATION_RESIDUALS = 512;
|
|
10069
|
+
var GRID_LOW = -8;
|
|
10070
|
+
var GRID_HIGH = 8;
|
|
10071
|
+
var GRID_STEP = 0.04;
|
|
10072
|
+
var CHARS_FLOOR = 50;
|
|
10073
|
+
function normalCdf(z) {
|
|
10074
|
+
if (!Number.isFinite(z)) return z > 0 ? 1 : 0;
|
|
10075
|
+
const x = Math.abs(z);
|
|
10076
|
+
const t = 1 / (1 + 0.2316419 * x);
|
|
10077
|
+
const poly = t * (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
|
|
10078
|
+
const tail = Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI) * poly;
|
|
10079
|
+
return z >= 0 ? 1 - tail : tail;
|
|
10080
|
+
}
|
|
10081
|
+
function normalQuantile(p) {
|
|
10082
|
+
const q = Math.min(1 - 1e-12, Math.max(1e-12, p));
|
|
10083
|
+
const a = [-39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239];
|
|
10084
|
+
const b = [-54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572];
|
|
10085
|
+
const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783];
|
|
10086
|
+
const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416];
|
|
10087
|
+
const low = 0.02425;
|
|
10088
|
+
if (q < low || q > 1 - low) {
|
|
10089
|
+
const r2 = Math.sqrt(-2 * Math.log(q < low ? q : 1 - q));
|
|
10090
|
+
const x = (((((c[0] * r2 + c[1]) * r2 + c[2]) * r2 + c[3]) * r2 + c[4]) * r2 + c[5]) / ((((d[0] * r2 + d[1]) * r2 + d[2]) * r2 + d[3]) * r2 + 1);
|
|
10091
|
+
return q < low ? x : -x;
|
|
10092
|
+
}
|
|
10093
|
+
const r = q - 0.5;
|
|
10094
|
+
const s = r * r;
|
|
10095
|
+
return (((((a[0] * s + a[1]) * s + a[2]) * s + a[3]) * s + a[4]) * s + a[5]) * r / (((((b[0] * s + b[1]) * s + b[2]) * s + b[3]) * s + b[4]) * s + 1);
|
|
10096
|
+
}
|
|
10097
|
+
function charsWeight(a, b) {
|
|
10098
|
+
if (a === void 0 || b === void 0) return 1;
|
|
10099
|
+
const distance = Math.log((a + CHARS_FLOOR) / (b + CHARS_FLOOR)) / CHARS_BANDWIDTH;
|
|
10100
|
+
return Math.exp(-0.5 * distance * distance);
|
|
10101
|
+
}
|
|
10102
|
+
function effortWeight(a, b) {
|
|
10103
|
+
return a === void 0 || b === void 0 || a === b ? 1 : OTHER_EFFORT_WEIGHT;
|
|
10104
|
+
}
|
|
10105
|
+
function recencyWeight(now, at) {
|
|
10106
|
+
const age = Math.max(0, now - at);
|
|
10107
|
+
return Math.pow(0.5, age / RECENCY_HALF_LIFE_MS);
|
|
10108
|
+
}
|
|
10109
|
+
function weightedMedian(values, weights) {
|
|
10110
|
+
const order = values.map((_, index) => index).sort((a, b) => values[a] - values[b]);
|
|
10111
|
+
const total = order.reduce((sum, index) => sum + weights[index], 0);
|
|
10112
|
+
let cumulative = 0;
|
|
10113
|
+
for (const index of order) {
|
|
10114
|
+
cumulative += weights[index];
|
|
10115
|
+
if (cumulative >= total / 2) return values[index];
|
|
10116
|
+
}
|
|
10117
|
+
return values[order[order.length - 1]];
|
|
10118
|
+
}
|
|
10119
|
+
function effortScales(samples, weights, effort) {
|
|
10120
|
+
const scales = /* @__PURE__ */ new Map();
|
|
10121
|
+
if (effort === void 0) return scales;
|
|
10122
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10123
|
+
samples.forEach((sample, index) => {
|
|
10124
|
+
if (sample.effort === void 0) return;
|
|
10125
|
+
const group = groups.get(sample.effort) ?? [];
|
|
10126
|
+
group.push(index);
|
|
10127
|
+
groups.set(sample.effort, group);
|
|
10128
|
+
});
|
|
10129
|
+
const own = groups.get(effort);
|
|
10130
|
+
if (!own || own.length < MIN_EFFORT_RATIO_SAMPLES) return scales;
|
|
10131
|
+
const medianOf = (indices) => weightedMedian(
|
|
10132
|
+
indices.map((index) => samples[index].ms),
|
|
10133
|
+
indices.map((index) => weights[index])
|
|
10134
|
+
);
|
|
10135
|
+
const ownMedian = medianOf(own);
|
|
10136
|
+
for (const [other, indices] of groups) {
|
|
10137
|
+
if (other === effort || indices.length < MIN_EFFORT_RATIO_SAMPLES) continue;
|
|
10138
|
+
const otherMedian = medianOf(indices);
|
|
10139
|
+
if (ownMedian > 0 && otherMedian > 0) scales.set(other, ownMedian / otherMedian);
|
|
10140
|
+
}
|
|
10141
|
+
return scales;
|
|
10142
|
+
}
|
|
10143
|
+
function usableSamples(samples) {
|
|
10144
|
+
return samples.filter((sample) => Number.isFinite(sample.ms) && sample.ms >= 0 && !sample.failed);
|
|
10145
|
+
}
|
|
10146
|
+
function prepare(samples, input, now) {
|
|
10147
|
+
const usable = usableSamples(samples);
|
|
10148
|
+
const base = usable.map((sample) => charsWeight(input.chars, sample.chars) * recencyWeight(now, sample.at));
|
|
10149
|
+
const scales = effortScales(usable, base, input.effort);
|
|
10150
|
+
return usable.map((sample, index) => {
|
|
10151
|
+
const ms = sample.ms * (sample.effort !== void 0 ? scales.get(sample.effort) ?? 1 : 1);
|
|
10152
|
+
return {
|
|
10153
|
+
ms,
|
|
10154
|
+
y: Math.log(Math.max(1, ms)),
|
|
10155
|
+
weight: base[index] * effortWeight(input.effort, sample.effort),
|
|
10156
|
+
calls: sample.calls,
|
|
10157
|
+
out: sample.out,
|
|
10158
|
+
sameEffort: input.effort === void 0 || sample.effort === void 0 || input.effort === sample.effort,
|
|
10159
|
+
session: sample.session
|
|
10160
|
+
};
|
|
10161
|
+
});
|
|
10162
|
+
}
|
|
10163
|
+
function moments(set) {
|
|
10164
|
+
let sumW = 0;
|
|
10165
|
+
let sumW2 = 0;
|
|
10166
|
+
let sumWY = 0;
|
|
10167
|
+
for (const sample of set) {
|
|
10168
|
+
sumW += sample.weight;
|
|
10169
|
+
sumW2 += sample.weight * sample.weight;
|
|
10170
|
+
sumWY += sample.weight * sample.y;
|
|
10171
|
+
}
|
|
10172
|
+
if (!(sumW > 0)) return void 0;
|
|
10173
|
+
const mean = sumWY / sumW;
|
|
10174
|
+
let sumWD = 0;
|
|
10175
|
+
for (const sample of set) sumWD += sample.weight * (sample.y - mean) * (sample.y - mean);
|
|
10176
|
+
const n = sumW * sumW / sumW2;
|
|
10177
|
+
const variance = n > 1 ? sumWD / sumW * (n / (n - 1)) : 0;
|
|
10178
|
+
return { mean, variance, n };
|
|
10179
|
+
}
|
|
10180
|
+
function shrink(own, prior, kappa = SHRINKAGE_KAPPA, meanKappa = kappa) {
|
|
10181
|
+
if (!own) return prior;
|
|
10182
|
+
const priorMu = prior?.mu ?? own.mean;
|
|
10183
|
+
const priorVariance = prior ? prior.sigma * prior.sigma : DEFAULT_LOG_SIGMA * DEFAULT_LOG_SIGMA;
|
|
10184
|
+
const mu = (own.n * own.mean + meanKappa * priorMu) / (own.n + meanKappa);
|
|
10185
|
+
const variance = (own.n * own.variance + kappa * priorVariance) / (own.n + kappa);
|
|
10186
|
+
return { mu, sigma: Math.min(MAX_LOG_SIGMA, Math.max(MIN_LOG_SIGMA, Math.sqrt(Math.max(0, variance)))) };
|
|
10187
|
+
}
|
|
10188
|
+
function agentPool(history, key, effort, now) {
|
|
10189
|
+
const slash = key.indexOf("/");
|
|
10190
|
+
if (slash < 0) return { count: 0, n: 0 };
|
|
10191
|
+
const prefix = key.slice(0, slash + 1);
|
|
10192
|
+
const pool = [];
|
|
10193
|
+
for (const [other, samples] of history) {
|
|
10194
|
+
if (other === key || !other.startsWith(prefix)) continue;
|
|
10195
|
+
pool.push(...prepare(samples, { effort }, now));
|
|
10196
|
+
}
|
|
10197
|
+
const measured = moments(pool);
|
|
10198
|
+
return { fit: shrink(measured, void 0), count: pool.length, n: measured?.n ?? 0 };
|
|
10199
|
+
}
|
|
10200
|
+
function fitKey(own, pool, input) {
|
|
10201
|
+
if (own.length + pool.count < MIN_TURN_SAMPLES) return void 0;
|
|
10202
|
+
const poolPull = pool.fit ? SHRINKAGE_KAPPA * pool.n / (pool.n + SHRINKAGE_KAPPA) : SHRINKAGE_KAPPA;
|
|
10203
|
+
const key = shrink(moments(own), pool.fit, SHRINKAGE_KAPPA, poolPull);
|
|
10204
|
+
if (!key) return void 0;
|
|
10205
|
+
const mine = input.session === void 0 ? [] : own.filter((sample) => sample.session === input.session);
|
|
10206
|
+
const usual = mine.length > 0 ? shrink(moments(mine), key) ?? key : key;
|
|
10207
|
+
const calls = input.calls !== void 0 && input.calls > 0 ? input.calls : void 0;
|
|
10208
|
+
const out = input.out !== void 0 && input.out > 0 ? input.out : void 0;
|
|
10209
|
+
let fit = usual;
|
|
10210
|
+
if (calls !== void 0 || out !== void 0) {
|
|
10211
|
+
const kept = own.filter(
|
|
10212
|
+
(sample) => (calls === void 0 || sample.calls === void 0 || sample.calls >= calls) && (out === void 0 || sample.out === void 0 || !sample.sameEffort || sample.out >= out)
|
|
10213
|
+
);
|
|
10214
|
+
if (kept.length < own.length && kept.length >= MIN_CONDITIONED_SAMPLES) {
|
|
10215
|
+
fit = shrink(moments(kept), usual, CONDITIONED_KAPPA) ?? usual;
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
const maxMs = own.length > 0 ? own.reduce((longest, sample) => Math.max(longest, sample.ms), 0) : Number.POSITIVE_INFINITY;
|
|
10219
|
+
return { fit, usual, maxMs };
|
|
10220
|
+
}
|
|
10221
|
+
var NORMAL_CALIBRATION = { count: 0, weight: 0, cdf: normalCdf, quantile: normalQuantile };
|
|
10222
|
+
function weightedQuantile(sorted, total, q) {
|
|
10223
|
+
let cumulative = 0;
|
|
10224
|
+
let previous;
|
|
10225
|
+
for (const entry of sorted) {
|
|
10226
|
+
const at = (cumulative + entry.weight / 2) / total;
|
|
10227
|
+
cumulative += entry.weight;
|
|
10228
|
+
if (at >= q) {
|
|
10229
|
+
if (!previous) return entry.value;
|
|
10230
|
+
return previous.value + (q - previous.at) / (at - previous.at) * (entry.value - previous.value);
|
|
10231
|
+
}
|
|
10232
|
+
previous = { value: entry.value, at };
|
|
10233
|
+
}
|
|
10234
|
+
return sorted[sorted.length - 1].value;
|
|
10235
|
+
}
|
|
10236
|
+
function calibrationFrom(residuals, weights) {
|
|
10237
|
+
const entries = residuals.map((value, index) => ({ value, weight: weights?.[index] ?? 1 })).filter((entry) => Number.isFinite(entry.value) && Number.isFinite(entry.weight) && entry.weight > 0).slice(-MAX_CALIBRATION_RESIDUALS);
|
|
10238
|
+
if (entries.length === 0) return NORMAL_CALIBRATION;
|
|
10239
|
+
let total = 0;
|
|
10240
|
+
let squares = 0;
|
|
10241
|
+
let sum = 0;
|
|
10242
|
+
for (const entry of entries) {
|
|
10243
|
+
total += entry.weight;
|
|
10244
|
+
squares += entry.weight * entry.weight;
|
|
10245
|
+
sum += entry.weight * entry.value;
|
|
10246
|
+
}
|
|
10247
|
+
const n = total * total / squares;
|
|
10248
|
+
const mean = sum / total;
|
|
10249
|
+
let deviation = 0;
|
|
10250
|
+
for (const entry of entries) deviation += entry.weight * (entry.value - mean) * (entry.value - mean);
|
|
10251
|
+
const sd = n > 1 ? Math.sqrt(deviation / total * (n / (n - 1))) : 0;
|
|
10252
|
+
const sorted = [...entries].sort((a, b) => a.value - b.value);
|
|
10253
|
+
const iqr = weightedQuantile(sorted, total, 0.75) - weightedQuantile(sorted, total, 0.25);
|
|
10254
|
+
const spreads = [sd, iqr / 1.349].filter((spread2) => spread2 > 0);
|
|
10255
|
+
const spread = spreads.length > 0 ? Math.min(...spreads) : 1;
|
|
10256
|
+
const width = Math.max(0.1, 0.9 * spread * Math.pow(n, -0.2));
|
|
10257
|
+
const weight = n / (n + CALIBRATION_PRIOR_RESIDUALS);
|
|
10258
|
+
const size = Math.round((GRID_HIGH - GRID_LOW) / GRID_STEP) + 1;
|
|
10259
|
+
const table = new Float64Array(size);
|
|
10260
|
+
for (let index = 0; index < size; index += 1) {
|
|
10261
|
+
const z = GRID_LOW + index * GRID_STEP;
|
|
10262
|
+
let kernel = 0;
|
|
10263
|
+
for (const entry of entries) kernel += entry.weight * normalCdf((z - entry.value) / width);
|
|
10264
|
+
table[index] = weight * (kernel / total) + (1 - weight) * normalCdf(z);
|
|
10265
|
+
}
|
|
10266
|
+
return {
|
|
10267
|
+
count: entries.length,
|
|
10268
|
+
weight,
|
|
10269
|
+
cdf(z) {
|
|
10270
|
+
if (Number.isNaN(z)) return 0.5;
|
|
10271
|
+
const position = (Math.min(GRID_HIGH, Math.max(GRID_LOW, z)) - GRID_LOW) / GRID_STEP;
|
|
10272
|
+
const index = Math.min(size - 2, Math.floor(position));
|
|
10273
|
+
return table[index] + (position - index) * (table[index + 1] - table[index]);
|
|
10274
|
+
},
|
|
10275
|
+
quantile(p) {
|
|
10276
|
+
if (!(p > table[0])) return GRID_LOW;
|
|
10277
|
+
if (!(p < table[size - 1])) return GRID_HIGH;
|
|
10278
|
+
let low = 0;
|
|
10279
|
+
let high = size - 1;
|
|
10280
|
+
while (high - low > 1) {
|
|
10281
|
+
const mid = low + high >> 1;
|
|
10282
|
+
if (table[mid] < p) low = mid;
|
|
10283
|
+
else high = mid;
|
|
10284
|
+
}
|
|
10285
|
+
const span = table[high] - table[low];
|
|
10286
|
+
return GRID_LOW + (low + (span > 0 ? (p - table[low]) / span : 1)) * GRID_STEP;
|
|
10287
|
+
}
|
|
10288
|
+
};
|
|
10289
|
+
}
|
|
10290
|
+
function quantileMs(fit, calibration, truncatedAtMs, q) {
|
|
10291
|
+
const passed = truncatedAtMs > 0 ? calibration.cdf((Math.log(truncatedAtMs) - fit.mu) / fit.sigma) : 0;
|
|
10292
|
+
return Math.exp(fit.mu + fit.sigma * calibration.quantile(passed + q * (1 - passed)));
|
|
10293
|
+
}
|
|
10294
|
+
var EMPTY_HISTORY = /* @__PURE__ */ new Map();
|
|
10295
|
+
function buildTurnModel(history = EMPTY_HISTORY, now = Date.now()) {
|
|
10296
|
+
const pools = /* @__PURE__ */ new Map();
|
|
10297
|
+
const poolFor = (key, effort) => {
|
|
10298
|
+
const id = `${key}\0${effort ?? ""}`;
|
|
10299
|
+
let pool = pools.get(id);
|
|
10300
|
+
if (!pool) {
|
|
10301
|
+
pool = agentPool(history, key, effort, now);
|
|
10302
|
+
pools.set(id, pool);
|
|
10303
|
+
}
|
|
10304
|
+
return pool;
|
|
10305
|
+
};
|
|
10306
|
+
const assessed = /* @__PURE__ */ new Map();
|
|
10307
|
+
const residuals = [];
|
|
10308
|
+
const residualWeights = [];
|
|
10309
|
+
for (const [key, samples] of history) {
|
|
10310
|
+
const usable = usableSamples(samples);
|
|
10311
|
+
if (usable.length < 2) continue;
|
|
10312
|
+
const rows = [];
|
|
10313
|
+
for (const held of usable.slice(-MAX_ASSESSED_PER_KEY)) {
|
|
10314
|
+
const input = { chars: held.chars, effort: held.effort, session: held.session };
|
|
10315
|
+
const own = prepare(
|
|
10316
|
+
usable.filter((sample) => sample !== held),
|
|
10317
|
+
input,
|
|
10318
|
+
now
|
|
10319
|
+
);
|
|
10320
|
+
const fitted = fitKey(own, poolFor(key, held.effort), input);
|
|
10321
|
+
if (!fitted) continue;
|
|
10322
|
+
rows.push({ ms: held.ms, fit: fitted.fit });
|
|
10323
|
+
residuals.push((Math.log(Math.max(1, held.ms)) - fitted.fit.mu) / fitted.fit.sigma);
|
|
10324
|
+
residualWeights.push(recencyWeight(now, held.at));
|
|
10325
|
+
}
|
|
10326
|
+
if (rows.length > 0) assessed.set(key, rows);
|
|
10327
|
+
}
|
|
10328
|
+
const calibration = calibrationFrom(residuals, residualWeights);
|
|
10329
|
+
const estimate = (key, input) => {
|
|
10330
|
+
const at = input.now ?? now;
|
|
10331
|
+
const own = prepare(history.get(key) ?? [], input, at);
|
|
10332
|
+
const fitted = fitKey(own, poolFor(key, input.effort), input);
|
|
10333
|
+
if (!fitted) return void 0;
|
|
10334
|
+
const elapsed = Math.max(0, Number.isFinite(input.elapsedMs) ? input.elapsedMs : 0);
|
|
10335
|
+
if (elapsed > fitted.maxMs) {
|
|
10336
|
+
return { totalMs: quantileMs(fitted.usual, calibration, 0, 0.5), fill: 1, over: true };
|
|
10337
|
+
}
|
|
10338
|
+
const totalMs = Math.max(elapsed, quantileMs(fitted.fit, calibration, elapsed, 0.5));
|
|
10339
|
+
const lowMs = Math.min(totalMs, Math.max(elapsed, quantileMs(fitted.fit, calibration, elapsed, 0.25)));
|
|
10340
|
+
const highMs = Math.max(totalMs, quantileMs(fitted.fit, calibration, elapsed, 0.75));
|
|
10341
|
+
return {
|
|
10342
|
+
totalMs,
|
|
10343
|
+
fill: totalMs > 0 ? elapsed / totalMs : 0,
|
|
10344
|
+
over: false,
|
|
10345
|
+
band: {
|
|
10346
|
+
lowMs,
|
|
10347
|
+
highMs,
|
|
10348
|
+
sure: highMs > 0 ? elapsed / highMs : 0,
|
|
10349
|
+
maybe: lowMs > 0 ? Math.min(1, elapsed / lowMs) : 0
|
|
10350
|
+
}
|
|
10351
|
+
};
|
|
10352
|
+
};
|
|
10353
|
+
const assess = () => {
|
|
10354
|
+
const keys = [];
|
|
10355
|
+
for (const [key, samples] of history) {
|
|
10356
|
+
const usable = usableSamples(samples);
|
|
10357
|
+
const failed = samples.filter((sample) => sample.failed).length;
|
|
10358
|
+
if (usable.length === 0 && failed === 0) continue;
|
|
10359
|
+
const durations = usable.map((sample) => sample.ms).sort((a, b) => a - b);
|
|
10360
|
+
const typicalMs = durations.length > 0 ? durations[Math.floor((durations.length - 1) / 2)] : 0;
|
|
10361
|
+
const rows = assessed.get(key) ?? [];
|
|
10362
|
+
const misses = [];
|
|
10363
|
+
let over = 0;
|
|
10364
|
+
let inBand = 0;
|
|
10365
|
+
for (const row of rows) {
|
|
10366
|
+
const expected = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.5));
|
|
10367
|
+
const low = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.25));
|
|
10368
|
+
const high = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.75));
|
|
10369
|
+
misses.push(Math.abs(Math.log(Math.max(1, row.ms) / expected)));
|
|
10370
|
+
if (row.ms > expected) over += 1;
|
|
10371
|
+
if (row.ms >= low && row.ms <= high) inBand += 1;
|
|
10372
|
+
}
|
|
10373
|
+
misses.sort((a, b) => a - b);
|
|
10374
|
+
keys.push({
|
|
10375
|
+
key,
|
|
10376
|
+
turns: usable.length,
|
|
10377
|
+
failed,
|
|
10378
|
+
typicalMs,
|
|
10379
|
+
assessed: rows.length,
|
|
10380
|
+
...rows.length > 0 ? {
|
|
10381
|
+
missFactor: Math.exp(misses[Math.floor((misses.length - 1) / 2)]),
|
|
10382
|
+
overShare: over / rows.length,
|
|
10383
|
+
inBandShare: inBand / rows.length
|
|
10384
|
+
} : {}
|
|
10385
|
+
});
|
|
10386
|
+
}
|
|
10387
|
+
keys.sort((a, b) => b.turns - a.turns || a.key.localeCompare(b.key));
|
|
10388
|
+
return { keys, residuals: residuals.length, calibrationWeight: calibration.weight };
|
|
10389
|
+
};
|
|
10390
|
+
return { estimate, calibration, assess };
|
|
10391
|
+
}
|
|
10392
|
+
|
|
9638
10393
|
// src/stale-build.ts
|
|
9639
10394
|
import * as fs7 from "fs";
|
|
9640
10395
|
import * as path10 from "path";
|
|
@@ -10196,6 +10951,12 @@ function formatDoctorReport(report) {
|
|
|
10196
10951
|
lines.push("");
|
|
10197
10952
|
lines.push(...formatAccountUsage(report.accountUsage));
|
|
10198
10953
|
}
|
|
10954
|
+
if (report.turnEstimate) {
|
|
10955
|
+
lines.push("");
|
|
10956
|
+
lines.push("**Progress estimate**");
|
|
10957
|
+
lines.push("");
|
|
10958
|
+
lines.push(...formatTurnEstimate(report.turnEstimate));
|
|
10959
|
+
}
|
|
10199
10960
|
lines.push("");
|
|
10200
10961
|
lines.push("**Background subagents**");
|
|
10201
10962
|
lines.push("");
|
|
@@ -10312,6 +11073,37 @@ function formatAccountUsage(usage, now = Date.now()) {
|
|
|
10312
11073
|
...rows
|
|
10313
11074
|
];
|
|
10314
11075
|
}
|
|
11076
|
+
function formatTurnEstimate(assessment) {
|
|
11077
|
+
if (assessment.keys.length === 0) {
|
|
11078
|
+
return [
|
|
11079
|
+
`No turns recorded yet. The server half records one per served turn in \`turn-history.json\`; the sidebar draws a bar once an agent has ${MIN_TURN_SAMPLES} under one model, or that many across its other models.`
|
|
11080
|
+
];
|
|
11081
|
+
}
|
|
11082
|
+
const percent = (share) => share === void 0 ? "n/a" : `${Math.round(share * 100)}%`;
|
|
11083
|
+
const lines = [
|
|
11084
|
+
assessment.residuals === 0 ? "No leave-one-out error to calibrate on yet, so the band is the normal curve's." : `The band is calibrated on ${assessment.residuals} leave-one-out error${assessment.residuals === 1 ? "" : "s"}, which weigh ${percent(assessment.calibrationWeight)} against the normal curve: the curve counts as ${CALIBRATION_PRIOR_RESIDUALS}, so the measured spread takes over as they accrue.`,
|
|
11085
|
+
"",
|
|
11086
|
+
"| agent/model | turns | failed | typical | assessed | off by | outran | in band |",
|
|
11087
|
+
"|---|---|---|---|---|---|---|---|"
|
|
11088
|
+
];
|
|
11089
|
+
for (const key of assessment.keys) {
|
|
11090
|
+
lines.push(
|
|
11091
|
+
`| ${tableCell(key.key)} | ${key.turns} | ${key.failed} | ${key.turns > 0 ? formatAge(key.typicalMs) : "n/a"} | ${key.assessed} | ${key.missFactor === void 0 ? "n/a" : `\xD7${key.missFactor.toFixed(1)}`} | ${percent(key.overShare)} | ${percent(key.inBandShare)} |`
|
|
11092
|
+
);
|
|
11093
|
+
}
|
|
11094
|
+
lines.push("");
|
|
11095
|
+
lines.push(
|
|
11096
|
+
"Each turn is estimated at its start from the rest of its record. `off by` is the typical factor between that estimate and what the turn took, either way (\xD71.5 is 50% off); `outran` is the share that took longer than estimated, and half is unbiased; `in band` is the share that ended inside the band the bar draws, and half is calibrated. A turn that ended on a failed tool is counted under `failed` and never read."
|
|
11097
|
+
);
|
|
11098
|
+
return lines;
|
|
11099
|
+
}
|
|
11100
|
+
function assessTurnEstimate() {
|
|
11101
|
+
try {
|
|
11102
|
+
return buildTurnModel(createTurnHistoryReader().read()).assess();
|
|
11103
|
+
} catch {
|
|
11104
|
+
return void 0;
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
10315
11107
|
function formatBackgroundSubagents(state) {
|
|
10316
11108
|
const lines = [];
|
|
10317
11109
|
if (state.running) {
|
|
@@ -10497,6 +11289,7 @@ async function gatherDoctorReport(options) {
|
|
|
10497
11289
|
hookFailures: snapshotHookFailures(),
|
|
10498
11290
|
planUsage,
|
|
10499
11291
|
accountUsage: snapshotAccountUsage(),
|
|
11292
|
+
turnEstimate: assessTurnEstimate(),
|
|
10500
11293
|
backgroundSubagents: {
|
|
10501
11294
|
gate: snapshotBackgroundSubagentGate(),
|
|
10502
11295
|
ledgers: snapshotBackgroundTasks(),
|
|
@@ -11629,9 +12422,9 @@ async function resolveSkillPluginDirs(opts) {
|
|
|
11629
12422
|
// src/claude-session-wrapper.ts
|
|
11630
12423
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
11631
12424
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
11632
|
-
import { existsSync as existsSync4, writeFileSync as
|
|
12425
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
11633
12426
|
import { unlink as unlink2 } from "fs/promises";
|
|
11634
|
-
import { join as
|
|
12427
|
+
import { join as join16 } from "path";
|
|
11635
12428
|
|
|
11636
12429
|
// src/claude-session-bun.ts
|
|
11637
12430
|
import * as os6 from "os";
|
|
@@ -12813,8 +13606,8 @@ ${text2}`
|
|
|
12813
13606
|
}
|
|
12814
13607
|
function stageImage(data, extension) {
|
|
12815
13608
|
try {
|
|
12816
|
-
const file =
|
|
12817
|
-
|
|
13609
|
+
const file = join16(pluginTmpDir(), `attachment-${randomUUID5()}.${extension}`);
|
|
13610
|
+
writeFileSync9(file, data, { mode: 384, flag: "wx" });
|
|
12818
13611
|
return file;
|
|
12819
13612
|
} catch (err) {
|
|
12820
13613
|
log.warn("interactive transport could not stage an image", {
|
|
@@ -13295,17 +14088,17 @@ function spawnInteractiveProcess(opts) {
|
|
|
13295
14088
|
}
|
|
13296
14089
|
|
|
13297
14090
|
// src/spawn-record-store.ts
|
|
13298
|
-
import { mkdirSync as
|
|
13299
|
-
import { homedir as
|
|
13300
|
-
import { join as
|
|
14091
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync11, renameSync as renameSync7, statSync as statSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
14092
|
+
import { homedir as homedir14 } from "os";
|
|
14093
|
+
import { join as join17 } from "path";
|
|
13301
14094
|
var MAX_SPAWN_RECORDS = 256;
|
|
13302
14095
|
var SPAWN_RECORD_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
13303
|
-
var
|
|
14096
|
+
var pathOverride6 = null;
|
|
13304
14097
|
var lastWritten = /* @__PURE__ */ new Map();
|
|
13305
14098
|
function spawnRecordStorePath() {
|
|
13306
|
-
if (
|
|
13307
|
-
const stateRoot = process.env.XDG_STATE_HOME ||
|
|
13308
|
-
return
|
|
14099
|
+
if (pathOverride6) return pathOverride6;
|
|
14100
|
+
const stateRoot = process.env.XDG_STATE_HOME || join17(homedir14(), ".local", "state");
|
|
14101
|
+
return join17(stateRoot, "opencode-claude-code-plugin", "session-spawns.json");
|
|
13309
14102
|
}
|
|
13310
14103
|
function isRecord5(value) {
|
|
13311
14104
|
const record = value;
|
|
@@ -13315,10 +14108,10 @@ function isRecord5(value) {
|
|
|
13315
14108
|
if (!ok(record.model) || !ok(record.effort) || !ok(record.account) || !ok(record.agent)) return false;
|
|
13316
14109
|
return !!record.model;
|
|
13317
14110
|
}
|
|
13318
|
-
function
|
|
14111
|
+
function readEntries3(file) {
|
|
13319
14112
|
const out = /* @__PURE__ */ new Map();
|
|
13320
14113
|
try {
|
|
13321
|
-
const parsed = JSON.parse(
|
|
14114
|
+
const parsed = JSON.parse(readFileSync11(file, "utf8"));
|
|
13322
14115
|
const entries = parsed && typeof parsed === "object" ? parsed.sessions : null;
|
|
13323
14116
|
if (!entries || typeof entries !== "object") return out;
|
|
13324
14117
|
const cutoff = Date.now() - SPAWN_RECORD_MAX_AGE_MS;
|
|
@@ -13351,18 +14144,18 @@ ${record.account ?? ""}
|
|
|
13351
14144
|
${record.agent ?? ""}`;
|
|
13352
14145
|
if (lastWritten.get(sessionID) === fingerprint) return false;
|
|
13353
14146
|
const file = spawnRecordStorePath();
|
|
13354
|
-
const merged =
|
|
14147
|
+
const merged = readEntries3(file);
|
|
13355
14148
|
merged.delete(sessionID);
|
|
13356
14149
|
merged.set(sessionID, record);
|
|
13357
14150
|
const cutoff = now - SPAWN_RECORD_MAX_AGE_MS;
|
|
13358
14151
|
const kept = [...merged.entries()].filter(([, entry]) => entry.at >= cutoff).sort((a, b) => a[1].at - b[1].at).slice(-MAX_SPAWN_RECORDS);
|
|
13359
14152
|
try {
|
|
13360
|
-
|
|
14153
|
+
mkdirSync9(join17(file, ".."), { recursive: true, mode: 448 });
|
|
13361
14154
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
13362
|
-
|
|
14155
|
+
writeFileSync10(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(kept) }), {
|
|
13363
14156
|
mode: 384
|
|
13364
14157
|
});
|
|
13365
|
-
|
|
14158
|
+
renameSync7(tmp, file);
|
|
13366
14159
|
} catch {
|
|
13367
14160
|
return false;
|
|
13368
14161
|
}
|
|
@@ -13371,157 +14164,6 @@ ${record.agent ?? ""}`;
|
|
|
13371
14164
|
return true;
|
|
13372
14165
|
}
|
|
13373
14166
|
|
|
13374
|
-
// src/turn-history.ts
|
|
13375
|
-
import { mkdirSync as mkdirSync8, readFileSync as readFileSync10, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
13376
|
-
import { homedir as homedir13 } from "os";
|
|
13377
|
-
import { join as join16 } from "path";
|
|
13378
|
-
var MAX_TURN_SAMPLES = 48;
|
|
13379
|
-
var MAX_TURN_KEYS = 64;
|
|
13380
|
-
var TURN_SAMPLE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
13381
|
-
var CHARS_BANDWIDTH = Math.log(4);
|
|
13382
|
-
var pathOverride5 = null;
|
|
13383
|
-
function turnHistoryStorePath() {
|
|
13384
|
-
if (pathOverride5) return pathOverride5;
|
|
13385
|
-
const stateRoot = process.env.XDG_STATE_HOME || join16(homedir13(), ".local", "state");
|
|
13386
|
-
return join16(stateRoot, "opencode-claude-code-plugin", "turn-history.json");
|
|
13387
|
-
}
|
|
13388
|
-
function turnHistoryKey(agent, model) {
|
|
13389
|
-
return `${agent || "main"}/${model}`;
|
|
13390
|
-
}
|
|
13391
|
-
var INJECTED_BLOCKS = [
|
|
13392
|
-
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
13393
|
-
/<dcp-system-reminder>[\s\S]*?<\/dcp-system-reminder>/g,
|
|
13394
|
-
/<dcp-message-id>[\s\S]*?<\/dcp-message-id>/g
|
|
13395
|
-
];
|
|
13396
|
-
function promptChars(texts) {
|
|
13397
|
-
if (texts.length === 0) return void 0;
|
|
13398
|
-
let total = 0;
|
|
13399
|
-
for (const text of texts) {
|
|
13400
|
-
let stripped = text;
|
|
13401
|
-
for (const block of INJECTED_BLOCKS) stripped = stripped.replace(block, "");
|
|
13402
|
-
total += stripped.trim().length;
|
|
13403
|
-
}
|
|
13404
|
-
return total;
|
|
13405
|
-
}
|
|
13406
|
-
function textsOf(content) {
|
|
13407
|
-
if (typeof content === "string") return [content];
|
|
13408
|
-
if (!Array.isArray(content)) return [];
|
|
13409
|
-
const texts = [];
|
|
13410
|
-
for (const part of content) {
|
|
13411
|
-
if (!part || typeof part !== "object" || part.type !== "text") continue;
|
|
13412
|
-
const text = part.text;
|
|
13413
|
-
if (typeof text === "string") texts.push(text);
|
|
13414
|
-
}
|
|
13415
|
-
return texts;
|
|
13416
|
-
}
|
|
13417
|
-
function newestPromptChars(prompt) {
|
|
13418
|
-
for (let index = prompt.length - 1; index >= 0; index -= 1) {
|
|
13419
|
-
if (prompt[index].role === "user") return promptChars(textsOf(prompt[index].content));
|
|
13420
|
-
}
|
|
13421
|
-
return void 0;
|
|
13422
|
-
}
|
|
13423
|
-
function startsTurn(prompt) {
|
|
13424
|
-
const last = prompt[prompt.length - 1];
|
|
13425
|
-
if (!last || last.role !== "user") return false;
|
|
13426
|
-
const before = prompt[prompt.length - 2];
|
|
13427
|
-
return before === void 0 || before.role !== "tool";
|
|
13428
|
-
}
|
|
13429
|
-
function turnToolCalls(prompt) {
|
|
13430
|
-
let start = 0;
|
|
13431
|
-
for (let index = prompt.length - 1; index >= 0; index -= 1) {
|
|
13432
|
-
if (prompt[index].role !== "user") continue;
|
|
13433
|
-
const before = prompt[index - 1];
|
|
13434
|
-
if (before === void 0 || before.role !== "tool") {
|
|
13435
|
-
start = index;
|
|
13436
|
-
break;
|
|
13437
|
-
}
|
|
13438
|
-
}
|
|
13439
|
-
let calls = 0;
|
|
13440
|
-
for (let index = start; index < prompt.length; index += 1) {
|
|
13441
|
-
const message = prompt[index];
|
|
13442
|
-
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
13443
|
-
for (const part of message.content) {
|
|
13444
|
-
if (part && typeof part === "object" && part.type === "tool-call") calls += 1;
|
|
13445
|
-
}
|
|
13446
|
-
}
|
|
13447
|
-
return calls;
|
|
13448
|
-
}
|
|
13449
|
-
var MAX_TURN_STARTS = 256;
|
|
13450
|
-
var turnStarts = /* @__PURE__ */ new Map();
|
|
13451
|
-
function markTurnStart(key, at, chars) {
|
|
13452
|
-
turnStarts.delete(key);
|
|
13453
|
-
if (turnStarts.size >= MAX_TURN_STARTS) {
|
|
13454
|
-
const oldest = turnStarts.keys().next().value;
|
|
13455
|
-
if (oldest !== void 0) turnStarts.delete(oldest);
|
|
13456
|
-
}
|
|
13457
|
-
turnStarts.set(key, { at, ...chars !== void 0 ? { chars } : {} });
|
|
13458
|
-
}
|
|
13459
|
-
function takeTurnStart(key) {
|
|
13460
|
-
const start = turnStarts.get(key);
|
|
13461
|
-
turnStarts.delete(key);
|
|
13462
|
-
return start;
|
|
13463
|
-
}
|
|
13464
|
-
function isSample(value) {
|
|
13465
|
-
const sample = value;
|
|
13466
|
-
if (!sample || typeof sample !== "object" || Array.isArray(sample)) return false;
|
|
13467
|
-
if (typeof sample.ms !== "number" || !Number.isFinite(sample.ms) || sample.ms < 0) return false;
|
|
13468
|
-
if (typeof sample.at !== "number" || !Number.isFinite(sample.at)) return false;
|
|
13469
|
-
if (sample.chars !== void 0 && (typeof sample.chars !== "number" || !Number.isFinite(sample.chars) || sample.chars < 0)) {
|
|
13470
|
-
return false;
|
|
13471
|
-
}
|
|
13472
|
-
if (sample.effort !== void 0 && typeof sample.effort !== "string") return false;
|
|
13473
|
-
if (sample.calls !== void 0 && (typeof sample.calls !== "number" || !Number.isFinite(sample.calls) || sample.calls < 0)) {
|
|
13474
|
-
return false;
|
|
13475
|
-
}
|
|
13476
|
-
return true;
|
|
13477
|
-
}
|
|
13478
|
-
function cleanSample(sample) {
|
|
13479
|
-
return {
|
|
13480
|
-
ms: Math.round(sample.ms),
|
|
13481
|
-
...sample.chars !== void 0 ? { chars: Math.round(sample.chars) } : {},
|
|
13482
|
-
...sample.effort ? { effort: sample.effort } : {},
|
|
13483
|
-
...sample.calls !== void 0 ? { calls: Math.round(sample.calls) } : {},
|
|
13484
|
-
at: sample.at
|
|
13485
|
-
};
|
|
13486
|
-
}
|
|
13487
|
-
function readEntries2(file, now) {
|
|
13488
|
-
const out = /* @__PURE__ */ new Map();
|
|
13489
|
-
try {
|
|
13490
|
-
const parsed = JSON.parse(readFileSync10(file, "utf8"));
|
|
13491
|
-
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
13492
|
-
if (!entries || typeof entries !== "object") return out;
|
|
13493
|
-
const cutoff = now - TURN_SAMPLE_MAX_AGE_MS;
|
|
13494
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
13495
|
-
if (!key || !Array.isArray(value)) continue;
|
|
13496
|
-
const samples = value.filter(isSample).filter((sample) => sample.at >= cutoff).sort((a, b) => a.at - b.at).slice(-MAX_TURN_SAMPLES).map(cleanSample);
|
|
13497
|
-
if (samples.length > 0) out.set(key, samples);
|
|
13498
|
-
}
|
|
13499
|
-
} catch {
|
|
13500
|
-
}
|
|
13501
|
-
return out;
|
|
13502
|
-
}
|
|
13503
|
-
function bounded2(history) {
|
|
13504
|
-
const newest = (samples) => samples[samples.length - 1]?.at ?? 0;
|
|
13505
|
-
return new Map([...history.entries()].sort((a, b) => newest(a[1]) - newest(b[1])).slice(-MAX_TURN_KEYS));
|
|
13506
|
-
}
|
|
13507
|
-
function recordTurnSample(key, sample) {
|
|
13508
|
-
if (!key || !isSample(sample)) return false;
|
|
13509
|
-
const file = turnHistoryStorePath();
|
|
13510
|
-
const merged = readEntries2(file, sample.at);
|
|
13511
|
-
const samples = [...merged.get(key) ?? [], cleanSample(sample)].sort((a, b) => a.at - b.at).slice(-MAX_TURN_SAMPLES);
|
|
13512
|
-
merged.set(key, samples);
|
|
13513
|
-
const kept = bounded2(merged);
|
|
13514
|
-
try {
|
|
13515
|
-
mkdirSync8(join16(file, ".."), { recursive: true, mode: 448 });
|
|
13516
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
13517
|
-
writeFileSync9(tmp, JSON.stringify({ version: 1, turns: Object.fromEntries(kept) }), { mode: 384 });
|
|
13518
|
-
renameSync6(tmp, file);
|
|
13519
|
-
} catch {
|
|
13520
|
-
return false;
|
|
13521
|
-
}
|
|
13522
|
-
return true;
|
|
13523
|
-
}
|
|
13524
|
-
|
|
13525
14167
|
// src/account-transcript.ts
|
|
13526
14168
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
13527
14169
|
import { constants as fsConstants } from "fs";
|
|
@@ -13583,13 +14225,13 @@ function dedupe2(dirs) {
|
|
|
13583
14225
|
}
|
|
13584
14226
|
|
|
13585
14227
|
// src/prompts.ts
|
|
13586
|
-
import { readFileSync as
|
|
13587
|
-
import { homedir as
|
|
14228
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
14229
|
+
import { homedir as homedir15 } from "os";
|
|
13588
14230
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
13589
|
-
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as
|
|
14231
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join18 } from "path";
|
|
13590
14232
|
function readPromptFileIfPresent(path15) {
|
|
13591
14233
|
try {
|
|
13592
|
-
const content =
|
|
14234
|
+
const content = readFileSync12(path15, "utf8").trim();
|
|
13593
14235
|
return content || void 0;
|
|
13594
14236
|
} catch {
|
|
13595
14237
|
return void 0;
|
|
@@ -13598,7 +14240,7 @@ function readPromptFileIfPresent(path15) {
|
|
|
13598
14240
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
13599
14241
|
let dir = cwd;
|
|
13600
14242
|
while (true) {
|
|
13601
|
-
const content = readPromptFileIfPresent(
|
|
14243
|
+
const content = readPromptFileIfPresent(join18(dir, "AGENTS.md"));
|
|
13602
14244
|
if (content) return content;
|
|
13603
14245
|
const parent = dirname6(dir);
|
|
13604
14246
|
if (parent === dir) return void 0;
|
|
@@ -13709,7 +14351,7 @@ var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLA
|
|
|
13709
14351
|
function userAuthoredInstructions(forwarded, opts = {}) {
|
|
13710
14352
|
const readFile3 = opts.readFile ?? ((path15) => {
|
|
13711
14353
|
try {
|
|
13712
|
-
return
|
|
14354
|
+
return readFileSync12(path15, "utf8");
|
|
13713
14355
|
} catch {
|
|
13714
14356
|
return null;
|
|
13715
14357
|
}
|
|
@@ -13750,8 +14392,8 @@ ${options.compressionSummary.trim()}`
|
|
|
13750
14392
|
for (const s of extraSystemContent) {
|
|
13751
14393
|
if (s.trim()) parts.push(s.trim());
|
|
13752
14394
|
}
|
|
13753
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
13754
|
-
const globalAgents = readPromptFileIfPresent(
|
|
14395
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join18(homedir15(), ".config");
|
|
14396
|
+
const globalAgents = readPromptFileIfPresent(join18(configRoot, "opencode", "AGENTS.md"));
|
|
13755
14397
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
13756
14398
|
const forwarded = extraSystemContent.join("\n\n");
|
|
13757
14399
|
const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
|
|
@@ -13767,9 +14409,9 @@ ${options.compressionSummary.trim()}`
|
|
|
13767
14409
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
13768
14410
|
const content = parts.join("\n\n");
|
|
13769
14411
|
if (!content) return void 0;
|
|
13770
|
-
const path15 =
|
|
14412
|
+
const path15 = join18(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
|
|
13771
14413
|
try {
|
|
13772
|
-
|
|
14414
|
+
writeFileSync11(path15, content, { encoding: "utf8", mode: 384 });
|
|
13773
14415
|
return path15;
|
|
13774
14416
|
} catch (err) {
|
|
13775
14417
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
@@ -14699,6 +15341,7 @@ function createTurnState(init) {
|
|
|
14699
15341
|
toolCallsById: /* @__PURE__ */ new Map(),
|
|
14700
15342
|
toolCallEmittedIds: /* @__PURE__ */ new Set(),
|
|
14701
15343
|
toolCallAnsweredIds: /* @__PURE__ */ new Set(),
|
|
15344
|
+
lastCliToolResultError: void 0,
|
|
14702
15345
|
resultMeta: {},
|
|
14703
15346
|
lastCallUsage: void 0,
|
|
14704
15347
|
resultFailure: void 0,
|
|
@@ -15768,6 +16411,10 @@ ${plan}
|
|
|
15768
16411
|
if (state.gotPartialEvents && msg.type === "message_delta" && typeof msg.delta?.stop_reason === "string") {
|
|
15769
16412
|
state.lastStopReason = msg.delta.stop_reason;
|
|
15770
16413
|
}
|
|
16414
|
+
if (msg.type === "message_delta") {
|
|
16415
|
+
const output = msg.usage?.output_tokens;
|
|
16416
|
+
if (typeof output === "number") noteTurnOutput(state.sessionKey, output);
|
|
16417
|
+
}
|
|
15771
16418
|
if (msg.type === "assistant" && msg.message && typeof msg.message.stop_reason === "string") {
|
|
15772
16419
|
state.lastStopReason = msg.message.stop_reason;
|
|
15773
16420
|
}
|
|
@@ -16047,6 +16694,7 @@ ${plan}
|
|
|
16047
16694
|
const toolCall = state.toolCallsById.get(block2.tool_use_id) ?? adoptOpenCliToolCall(state, ctx, block2.tool_use_id);
|
|
16048
16695
|
if (toolCall) {
|
|
16049
16696
|
const isError = block2.is_error === true;
|
|
16697
|
+
state.lastCliToolResultError = isError;
|
|
16050
16698
|
state.controller.enqueue({
|
|
16051
16699
|
type: "tool-result",
|
|
16052
16700
|
toolCallId: block2.tool_use_id,
|
|
@@ -16552,12 +17200,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
16552
17200
|
this.config.provider
|
|
16553
17201
|
);
|
|
16554
17202
|
}
|
|
16555
|
-
controlRequestBehaviorForTool(toolName) {
|
|
16556
|
-
return controlRequestBehaviorForTool(this.config, toolName);
|
|
16557
|
-
}
|
|
16558
|
-
writeControlResponse(proc, requestId, response) {
|
|
16559
|
-
writeControlResponse(proc, requestId, response);
|
|
16560
|
-
}
|
|
16561
17203
|
/**
|
|
16562
17204
|
* Handle Claude stream-json control requests (`can_use_tool`, etc.) and
|
|
16563
17205
|
* respond via stdin with a matching `control_response`.
|
|
@@ -16606,9 +17248,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
16606
17248
|
thinkingDisplay: process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0 ? "summarized" : void 0
|
|
16607
17249
|
};
|
|
16608
17250
|
}
|
|
16609
|
-
latestUserText(prompt) {
|
|
16610
|
-
return latestUserText(prompt);
|
|
16611
|
-
}
|
|
16612
17251
|
synthesizeTitle(prompt) {
|
|
16613
17252
|
return synthesizeTitle(prompt);
|
|
16614
17253
|
}
|
|
@@ -17174,8 +17813,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17174
17813
|
account: (this.config.failoverAccounts?.length ?? 0) > 1 ? failover.target ?? sourceAccount : void 0,
|
|
17175
17814
|
agent: this.getOpencodeAgent(options)
|
|
17176
17815
|
});
|
|
17177
|
-
|
|
17178
|
-
|
|
17816
|
+
const parked = new Set(getPendingProxyCalls(sk).map((call) => call.toolCallId));
|
|
17817
|
+
if (startsTurn(options.prompt, parked)) {
|
|
17818
|
+
markTurnStart(sk, {
|
|
17819
|
+
at: turnStartedAt,
|
|
17820
|
+
chars: newestPromptChars(options.prompt),
|
|
17821
|
+
promptLength: options.prompt.length,
|
|
17822
|
+
session: sessionTag(affinity)
|
|
17823
|
+
});
|
|
17179
17824
|
}
|
|
17180
17825
|
}
|
|
17181
17826
|
const hasPriorConversation = options.prompt.filter((m) => m.role === "user" || m.role === "assistant").length > 1;
|
|
@@ -17466,7 +18111,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
17466
18111
|
let replayReason = "no-session-for-key";
|
|
17467
18112
|
if (this.config.resumeAfterRestart !== false && includeHistoryContext && !compactionMode && !forkFromClaudeSessionId && failoverAnswer?.kind !== "switch") {
|
|
17468
18113
|
const configDir = resolveConfigDir(this.config.configDir);
|
|
17469
|
-
const transcriptPath2 = (id, dir) =>
|
|
18114
|
+
const transcriptPath2 = (id, dir) => join19(dir ?? configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
|
|
17470
18115
|
const resumePoint = findResumePoint({
|
|
17471
18116
|
sessionKey: sk,
|
|
17472
18117
|
prompt: options.prompt,
|
|
@@ -18540,7 +19185,18 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18540
19185
|
// The turn's tool calls, counted as the TUI counts its tool
|
|
18541
19186
|
// parts (h #g250): the earlier steps' from the prompt, this
|
|
18542
19187
|
// step's own from what it emitted.
|
|
18543
|
-
calls: turnToolCalls(options.prompt) + state.toolCallEmittedIds.size,
|
|
19188
|
+
calls: turnToolCalls(options.prompt, start.promptLength) + state.toolCallEmittedIds.size,
|
|
19189
|
+
// The output its API calls generated, summed as the live
|
|
19190
|
+
// channel wrote it, so a running turn is compared with the
|
|
19191
|
+
// record in the same unit (h #g251).
|
|
19192
|
+
out: start.out,
|
|
19193
|
+
// A turn whose newest tool result was an error ended on a
|
|
19194
|
+
// failure (h #g251): this step's own CLI-executed results
|
|
19195
|
+
// are newer than the prompt's, so they answer first.
|
|
19196
|
+
failed: state.lastCliToolResultError ?? lastToolResultFailed(options.prompt, start.promptLength ?? 0),
|
|
19197
|
+
// The conversation, as a hash of its id: the estimate's
|
|
19198
|
+
// session level (h #g251).
|
|
19199
|
+
session: sessionTag(affinity),
|
|
18544
19200
|
at: endedAt
|
|
18545
19201
|
});
|
|
18546
19202
|
}
|
|
@@ -18766,38 +19422,40 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18766
19422
|
dropped: unattended.dropped
|
|
18767
19423
|
});
|
|
18768
19424
|
let partialText = false;
|
|
18769
|
-
|
|
19425
|
+
const narration = [];
|
|
19426
|
+
for (const line of unattended.lines) {
|
|
19427
|
+
try {
|
|
19428
|
+
const outer = JSON.parse(line);
|
|
19429
|
+
const msg = outer.type === "stream_event" && outer.event ? outer.event : outer;
|
|
19430
|
+
let text = "";
|
|
19431
|
+
if (msg.type === "content_block_delta" && msg.delta?.type === "text_delta") {
|
|
19432
|
+
text = msg.delta.text ?? "";
|
|
19433
|
+
partialText = true;
|
|
19434
|
+
} else if (msg.type === "assistant") {
|
|
19435
|
+
if (!partialText) text = (msg.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
19436
|
+
partialText = false;
|
|
19437
|
+
} else if (msg.type === "result") {
|
|
19438
|
+
state.unattendedTurnEnded = true;
|
|
19439
|
+
for (const entry of state.activeProcess.pendingProxyCompletions?.values() ?? []) {
|
|
19440
|
+
if (isPendingProxyCallChannelClosed(entry.call)) entry.recoveryRequired = true;
|
|
19441
|
+
}
|
|
19442
|
+
if (outer.session_id) setClaudeSessionId(sk, outer.session_id);
|
|
19443
|
+
if (msg.is_error && msg.result) text = msg.result;
|
|
19444
|
+
}
|
|
19445
|
+
if (text) narration.push(text);
|
|
19446
|
+
} catch {
|
|
19447
|
+
}
|
|
19448
|
+
}
|
|
19449
|
+
if (narration.length > 0 || unattended.dropped > 0) {
|
|
18770
19450
|
const replayId = state.startTextBlock();
|
|
18771
19451
|
controller.enqueue({
|
|
18772
19452
|
type: "text-delta",
|
|
18773
19453
|
id: replayId,
|
|
18774
19454
|
delta: formatUnattendedReplayNote(unattended.dropped)
|
|
18775
19455
|
});
|
|
18776
|
-
for (const
|
|
18777
|
-
|
|
18778
|
-
const outer = JSON.parse(line);
|
|
18779
|
-
const msg = outer.type === "stream_event" && outer.event ? outer.event : outer;
|
|
18780
|
-
let text = "";
|
|
18781
|
-
if (msg.type === "content_block_delta" && msg.delta?.type === "text_delta") {
|
|
18782
|
-
text = msg.delta.text ?? "";
|
|
18783
|
-
partialText = true;
|
|
18784
|
-
} else if (msg.type === "assistant") {
|
|
18785
|
-
if (!partialText) text = (msg.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
18786
|
-
partialText = false;
|
|
18787
|
-
} else if (msg.type === "result") {
|
|
18788
|
-
state.unattendedTurnEnded = true;
|
|
18789
|
-
for (const entry of state.activeProcess.pendingProxyCompletions?.values() ?? []) {
|
|
18790
|
-
if (isPendingProxyCallChannelClosed(entry.call)) entry.recoveryRequired = true;
|
|
18791
|
-
}
|
|
18792
|
-
if (outer.session_id) setClaudeSessionId(sk, outer.session_id);
|
|
18793
|
-
if (msg.is_error && msg.result) text = msg.result;
|
|
18794
|
-
}
|
|
18795
|
-
if (text) controller.enqueue({ type: "text-delta", id: state.currentTextId ?? state.startTextBlock(), delta: text });
|
|
18796
|
-
} catch {
|
|
18797
|
-
}
|
|
18798
|
-
}
|
|
19456
|
+
for (const text of narration) controller.enqueue({ type: "text-delta", id: replayId, delta: text });
|
|
19457
|
+
state.endTextBlock();
|
|
18799
19458
|
}
|
|
18800
|
-
state.endTextBlock();
|
|
18801
19459
|
clearFallbackTimer(state);
|
|
18802
19460
|
state.hasReceivedContent = false;
|
|
18803
19461
|
}
|
|
@@ -18995,7 +19653,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
18995
19653
|
};
|
|
18996
19654
|
|
|
18997
19655
|
// src/account-tier.ts
|
|
18998
|
-
import { readFileSync as
|
|
19656
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
18999
19657
|
import path14 from "path";
|
|
19000
19658
|
var ORGANIZATION_TYPE_LABELS = {
|
|
19001
19659
|
claude_max: "Max",
|
|
@@ -19038,7 +19696,7 @@ function readTierFromConfigDir(configDir, allowLegacyFallback) {
|
|
|
19038
19696
|
function readOauthAccount(file) {
|
|
19039
19697
|
let raw;
|
|
19040
19698
|
try {
|
|
19041
|
-
raw =
|
|
19699
|
+
raw = readFileSync13(file, "utf8");
|
|
19042
19700
|
} catch {
|
|
19043
19701
|
return void 0;
|
|
19044
19702
|
}
|
|
@@ -19075,14 +19733,14 @@ function registerSidebarAccounts(accounts) {
|
|
|
19075
19733
|
// src/cleanup-stale.ts
|
|
19076
19734
|
import {
|
|
19077
19735
|
existsSync as existsSync5,
|
|
19078
|
-
mkdirSync as
|
|
19079
|
-
readFileSync as
|
|
19736
|
+
mkdirSync as mkdirSync10,
|
|
19737
|
+
readFileSync as readFileSync14,
|
|
19080
19738
|
realpathSync as realpathSync4,
|
|
19081
19739
|
rmSync as rmSync3,
|
|
19082
|
-
writeFileSync as
|
|
19740
|
+
writeFileSync as writeFileSync12
|
|
19083
19741
|
} from "fs";
|
|
19084
|
-
import { homedir as
|
|
19085
|
-
import { dirname as dirname7, join as
|
|
19742
|
+
import { homedir as homedir16 } from "os";
|
|
19743
|
+
import { dirname as dirname7, join as join20, resolve as resolve6 } from "path";
|
|
19086
19744
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
19087
19745
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
19088
19746
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
@@ -19090,20 +19748,20 @@ var alreadyRan = false;
|
|
|
19090
19748
|
function candidateCacheRoots() {
|
|
19091
19749
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
19092
19750
|
return [
|
|
19093
|
-
xdg ?
|
|
19094
|
-
|
|
19095
|
-
|
|
19751
|
+
xdg ? join20(xdg, "opencode") : null,
|
|
19752
|
+
join20(homedir16(), ".cache", "opencode"),
|
|
19753
|
+
join20(homedir16(), "Library", "Caches", "opencode")
|
|
19096
19754
|
].filter((p) => Boolean(p));
|
|
19097
19755
|
}
|
|
19098
19756
|
function userOpencodeJsonPath() {
|
|
19099
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
19100
|
-
return
|
|
19757
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join20(homedir16(), ".config");
|
|
19758
|
+
return join20(xdgConfig, "opencode", "opencode.json");
|
|
19101
19759
|
}
|
|
19102
19760
|
function userIntendsToUseUnscoped() {
|
|
19103
19761
|
const cfg = userOpencodeJsonPath();
|
|
19104
19762
|
if (!existsSync5(cfg)) return false;
|
|
19105
19763
|
try {
|
|
19106
|
-
const json = JSON.parse(
|
|
19764
|
+
const json = JSON.parse(readFileSync14(cfg, "utf8"));
|
|
19107
19765
|
const plugins = json.plugin;
|
|
19108
19766
|
if (!Array.isArray(plugins)) return false;
|
|
19109
19767
|
return plugins.some(
|
|
@@ -19124,19 +19782,19 @@ function ourLoadedDir() {
|
|
|
19124
19782
|
function ourVersion(dir) {
|
|
19125
19783
|
if (!dir) return null;
|
|
19126
19784
|
try {
|
|
19127
|
-
const pkg = JSON.parse(
|
|
19785
|
+
const pkg = JSON.parse(readFileSync14(join20(dir, "package.json"), "utf8"));
|
|
19128
19786
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
19129
19787
|
} catch {
|
|
19130
19788
|
return null;
|
|
19131
19789
|
}
|
|
19132
19790
|
}
|
|
19133
19791
|
function markerPath() {
|
|
19134
|
-
const stateRoot = process.env.XDG_STATE_HOME ??
|
|
19135
|
-
return
|
|
19792
|
+
const stateRoot = process.env.XDG_STATE_HOME ?? join20(homedir16(), ".local", "state");
|
|
19793
|
+
return join20(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
|
|
19136
19794
|
}
|
|
19137
19795
|
function markerVersion() {
|
|
19138
19796
|
try {
|
|
19139
|
-
const marker = JSON.parse(
|
|
19797
|
+
const marker = JSON.parse(readFileSync14(markerPath(), "utf8"));
|
|
19140
19798
|
return typeof marker.version === "string" ? marker.version : null;
|
|
19141
19799
|
} catch {
|
|
19142
19800
|
return null;
|
|
@@ -19145,8 +19803,8 @@ function markerVersion() {
|
|
|
19145
19803
|
function writeMarker(version) {
|
|
19146
19804
|
const file = markerPath();
|
|
19147
19805
|
try {
|
|
19148
|
-
|
|
19149
|
-
|
|
19806
|
+
mkdirSync10(dirname7(file), { recursive: true, mode: 448 });
|
|
19807
|
+
writeFileSync12(
|
|
19150
19808
|
file,
|
|
19151
19809
|
JSON.stringify({ version, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
|
|
19152
19810
|
"utf8"
|
|
@@ -19181,7 +19839,7 @@ function cleanupStaleUnscopedInstall() {
|
|
|
19181
19839
|
}
|
|
19182
19840
|
function cleanupOne(cacheRoot, ourDir) {
|
|
19183
19841
|
if (!existsSync5(cacheRoot)) return;
|
|
19184
|
-
const stalePath =
|
|
19842
|
+
const stalePath = join20(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
19185
19843
|
if (!existsSync5(stalePath)) return;
|
|
19186
19844
|
let realStalePath = stalePath;
|
|
19187
19845
|
try {
|
|
@@ -19189,11 +19847,11 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
19189
19847
|
} catch {
|
|
19190
19848
|
}
|
|
19191
19849
|
if (ourDir && realStalePath === ourDir) return;
|
|
19192
|
-
const pkgJsonPath =
|
|
19850
|
+
const pkgJsonPath = join20(stalePath, "package.json");
|
|
19193
19851
|
if (!existsSync5(pkgJsonPath)) return;
|
|
19194
19852
|
let pkg = {};
|
|
19195
19853
|
try {
|
|
19196
|
-
pkg = JSON.parse(
|
|
19854
|
+
pkg = JSON.parse(readFileSync14(pkgJsonPath, "utf8"));
|
|
19197
19855
|
} catch {
|
|
19198
19856
|
return;
|
|
19199
19857
|
}
|
|
@@ -19209,13 +19867,13 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
19209
19867
|
});
|
|
19210
19868
|
return;
|
|
19211
19869
|
}
|
|
19212
|
-
const cachePkgJson =
|
|
19870
|
+
const cachePkgJson = join20(cacheRoot, "package.json");
|
|
19213
19871
|
if (!existsSync5(cachePkgJson)) return;
|
|
19214
19872
|
try {
|
|
19215
|
-
const cfg = JSON.parse(
|
|
19873
|
+
const cfg = JSON.parse(readFileSync14(cachePkgJson, "utf8"));
|
|
19216
19874
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
19217
19875
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
19218
|
-
|
|
19876
|
+
writeFileSync12(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
19219
19877
|
log.info("cleanup-stale: pruned dep from cache package.json");
|
|
19220
19878
|
}
|
|
19221
19879
|
} catch (err) {
|
|
@@ -19225,11 +19883,42 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
19225
19883
|
}
|
|
19226
19884
|
}
|
|
19227
19885
|
|
|
19228
|
-
// src/
|
|
19229
|
-
var
|
|
19886
|
+
// src/plugin-notice.ts
|
|
19887
|
+
var NOTICE_RPC_ID = "opencode-claude-code-plugin";
|
|
19888
|
+
var NOTICE_EVENT = "notice";
|
|
19889
|
+
var NOTICE_EVENT_TYPE = `rpc.${NOTICE_RPC_ID}.${NOTICE_EVENT}`;
|
|
19890
|
+
var NOTICE_RPC_DEFINITION = {
|
|
19891
|
+
id: NOTICE_RPC_ID,
|
|
19892
|
+
methods: {},
|
|
19893
|
+
events: {
|
|
19894
|
+
[NOTICE_EVENT]: {
|
|
19895
|
+
schema: {
|
|
19896
|
+
type: "object",
|
|
19897
|
+
properties: {
|
|
19898
|
+
id: { type: "string" },
|
|
19899
|
+
level: { type: "string", enum: ["warn", "error"] },
|
|
19900
|
+
message: { type: "string" }
|
|
19901
|
+
},
|
|
19902
|
+
required: ["id", "level", "message"],
|
|
19903
|
+
additionalProperties: false
|
|
19904
|
+
}
|
|
19905
|
+
}
|
|
19906
|
+
}
|
|
19907
|
+
};
|
|
19230
19908
|
var TOAST_TITLE = "claude-code";
|
|
19231
19909
|
var TOAST_DURATION_MS = 1e4;
|
|
19232
19910
|
var MAX_TOAST_CHARS = 240;
|
|
19911
|
+
function toastMessage(message) {
|
|
19912
|
+
const flat = message.replace(/\s+/g, " ").trim();
|
|
19913
|
+
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
19914
|
+
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
19915
|
+
}
|
|
19916
|
+
function pluginNotice(id, level, message) {
|
|
19917
|
+
return { id, level, message: toastMessage(message) };
|
|
19918
|
+
}
|
|
19919
|
+
|
|
19920
|
+
// src/tui-log-sink.ts
|
|
19921
|
+
var LOG_SERVICE_NAME = "opencode-claude-code";
|
|
19233
19922
|
var APP_LOG_LEVEL = {
|
|
19234
19923
|
debug: "debug",
|
|
19235
19924
|
info: "info",
|
|
@@ -19237,11 +19926,6 @@ var APP_LOG_LEVEL = {
|
|
|
19237
19926
|
warn: "warn",
|
|
19238
19927
|
error: "error"
|
|
19239
19928
|
};
|
|
19240
|
-
function toastMessage(message) {
|
|
19241
|
-
const flat = message.replace(/\s+/g, " ").trim();
|
|
19242
|
-
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
19243
|
-
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
19244
|
-
}
|
|
19245
19929
|
function appLogBody(entry) {
|
|
19246
19930
|
return {
|
|
19247
19931
|
service: LOG_SERVICE_NAME,
|
|
@@ -19291,6 +19975,9 @@ function registerOpencodeLogSink(client) {
|
|
|
19291
19975
|
return true;
|
|
19292
19976
|
}
|
|
19293
19977
|
|
|
19978
|
+
// src/v2.ts
|
|
19979
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
19980
|
+
|
|
19294
19981
|
// src/v2-client.ts
|
|
19295
19982
|
function formatAgentTypeList(agents) {
|
|
19296
19983
|
const lines = agents.flatMap((agent) => {
|
|
@@ -19580,24 +20267,89 @@ function deletedSessionIdV2(event) {
|
|
|
19580
20267
|
const id = event.data?.sessionID;
|
|
19581
20268
|
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
19582
20269
|
}
|
|
19583
|
-
|
|
20270
|
+
function stoppedSessionIdV2(event) {
|
|
20271
|
+
if (!event || event.type !== "session.execution.interrupted") return void 0;
|
|
20272
|
+
if (event.data?.reason !== "user") return void 0;
|
|
20273
|
+
const id = event.data?.sessionID;
|
|
20274
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
20275
|
+
}
|
|
20276
|
+
async function followSessionEvents(subscribe, signal) {
|
|
20277
|
+
const closeFeed = openSessionRunEventFeed();
|
|
19584
20278
|
try {
|
|
19585
20279
|
for await (const event of subscribe({ signal })) {
|
|
19586
20280
|
if (signal.aborted) break;
|
|
19587
|
-
|
|
19588
|
-
|
|
19589
|
-
|
|
19590
|
-
|
|
19591
|
-
|
|
20281
|
+
noteSessionRunEvent(event);
|
|
20282
|
+
const deleted = deletedSessionIdV2(event);
|
|
20283
|
+
if (deleted) {
|
|
20284
|
+
const released = deleteActiveProcessesForSession(deleted);
|
|
20285
|
+
if (released.length > 0) {
|
|
20286
|
+
log.info("released claude state for deleted session", { sessionID: deleted, released });
|
|
20287
|
+
}
|
|
20288
|
+
continue;
|
|
19592
20289
|
}
|
|
20290
|
+
const stopped = stoppedSessionIdV2(event);
|
|
20291
|
+
if (stopped) releaseParkedProxyCallsForSession(stopped);
|
|
19593
20292
|
}
|
|
19594
20293
|
} catch (err) {
|
|
19595
20294
|
if (!signal.aborted) {
|
|
19596
|
-
log.warn(
|
|
19597
|
-
|
|
19598
|
-
|
|
20295
|
+
log.warn(
|
|
20296
|
+
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction, and stopped turns keep their proxy calls until the next message",
|
|
20297
|
+
{ error: err instanceof Error ? err.message : String(err) }
|
|
20298
|
+
);
|
|
19599
20299
|
}
|
|
20300
|
+
} finally {
|
|
20301
|
+
closeFeed();
|
|
20302
|
+
}
|
|
20303
|
+
}
|
|
20304
|
+
var noticeEmitters = /* @__PURE__ */ new Set();
|
|
20305
|
+
var NOTICE_PROCESS_TAG = randomUUID8().slice(0, 8);
|
|
20306
|
+
var noticeCounter = 0;
|
|
20307
|
+
function emitNotice(entry) {
|
|
20308
|
+
if (entry.level !== "warn" && entry.level !== "error") return;
|
|
20309
|
+
noticeCounter += 1;
|
|
20310
|
+
const notice = pluginNotice(`${NOTICE_PROCESS_TAG}-${noticeCounter}`, entry.level, entry.message);
|
|
20311
|
+
for (const emit2 of noticeEmitters) emit2(notice);
|
|
20312
|
+
}
|
|
20313
|
+
async function registerNoticeChannel(ctx) {
|
|
20314
|
+
const rpc = ctx.rpc;
|
|
20315
|
+
if (typeof rpc?.register !== "function") return void 0;
|
|
20316
|
+
let registration;
|
|
20317
|
+
try {
|
|
20318
|
+
registration = await rpc.register(NOTICE_RPC_DEFINITION, {});
|
|
20319
|
+
} catch (err) {
|
|
20320
|
+
log.debug("opencode 2 refused the notice channel; warnings stay in the plugin log", {
|
|
20321
|
+
error: err instanceof Error ? err.message : String(err)
|
|
20322
|
+
});
|
|
20323
|
+
return void 0;
|
|
19600
20324
|
}
|
|
20325
|
+
const emitter2 = (notice) => {
|
|
20326
|
+
try {
|
|
20327
|
+
void Promise.resolve(registration.events.emit(NOTICE_EVENT, { ...notice })).catch(() => void 0);
|
|
20328
|
+
} catch {
|
|
20329
|
+
}
|
|
20330
|
+
};
|
|
20331
|
+
noticeEmitters.add(emitter2);
|
|
20332
|
+
setToastNotifier(emitNotice);
|
|
20333
|
+
return {
|
|
20334
|
+
dispose: async () => {
|
|
20335
|
+
noticeEmitters.delete(emitter2);
|
|
20336
|
+
if (noticeEmitters.size === 0) setToastNotifier(null);
|
|
20337
|
+
await registration.dispose();
|
|
20338
|
+
}
|
|
20339
|
+
};
|
|
20340
|
+
}
|
|
20341
|
+
function v2BtwClient(shim) {
|
|
20342
|
+
const shimSession = shim.session;
|
|
20343
|
+
const messages = shimSession?.messages;
|
|
20344
|
+
const status = async () => ({ data: busySessionsFromEvents() ?? {} });
|
|
20345
|
+
return {
|
|
20346
|
+
session: {
|
|
20347
|
+
...typeof messages === "function" ? { messages: (options) => messages.call(shimSession, options) } : {},
|
|
20348
|
+
get status() {
|
|
20349
|
+
return busySessionsFromEvents() === void 0 ? void 0 : status;
|
|
20350
|
+
}
|
|
20351
|
+
}
|
|
20352
|
+
};
|
|
19601
20353
|
}
|
|
19602
20354
|
function createV2Setup(deps) {
|
|
19603
20355
|
return async (ctx) => {
|
|
@@ -19608,9 +20360,13 @@ function createV2Setup(deps) {
|
|
|
19608
20360
|
return () => void 0;
|
|
19609
20361
|
}
|
|
19610
20362
|
ensureProcessExitCleanup();
|
|
19611
|
-
|
|
20363
|
+
const shim = createV1ClientShim(ctx);
|
|
20364
|
+
setOpencodeClient(shim);
|
|
19612
20365
|
const directory = ctx.location?.directory;
|
|
19613
20366
|
setOpencodeProjectDirectory(isUsableDirectory(directory) ? directory : void 0);
|
|
20367
|
+
const registrations = [];
|
|
20368
|
+
const noticeChannel = await registerNoticeChannel(ctx);
|
|
20369
|
+
if (noticeChannel) registrations.push(noticeChannel);
|
|
19614
20370
|
if (deps.buildAgentRegistry) {
|
|
19615
20371
|
try {
|
|
19616
20372
|
const config = deps.loadConfig(directory ?? process.cwd());
|
|
@@ -19630,7 +20386,6 @@ function createV2Setup(deps) {
|
|
|
19630
20386
|
}
|
|
19631
20387
|
}
|
|
19632
20388
|
const planned = /* @__PURE__ */ new Map();
|
|
19633
|
-
const registrations = [];
|
|
19634
20389
|
registrations.push(
|
|
19635
20390
|
await ctx.provider.transform((editor) => {
|
|
19636
20391
|
const seed = editor.get(BASE_PROVIDER_ID);
|
|
@@ -19686,6 +20441,7 @@ function createV2Setup(deps) {
|
|
|
19686
20441
|
})
|
|
19687
20442
|
);
|
|
19688
20443
|
const prompt = ctx.session.prompt;
|
|
20444
|
+
const btwClient = v2BtwClient(shim);
|
|
19689
20445
|
if (ctx.command?.transform && typeof prompt === "function") {
|
|
19690
20446
|
const send = (input, command, delivery) => prompt.call(ctx.session, {
|
|
19691
20447
|
...input.prompt,
|
|
@@ -19703,7 +20459,15 @@ function createV2Setup(deps) {
|
|
|
19703
20459
|
editor.add({
|
|
19704
20460
|
name: "btw",
|
|
19705
20461
|
description: BTW_COMMAND_DESCRIPTION,
|
|
19706
|
-
execute: (input) =>
|
|
20462
|
+
execute: (input) => {
|
|
20463
|
+
void askBtwThenRelease(
|
|
20464
|
+
btwClient,
|
|
20465
|
+
{ command: "btw", sessionID: input.sessionID, arguments: input.prompt.text ?? "" },
|
|
20466
|
+
() => send(input, "btw", "queue"),
|
|
20467
|
+
deps.btwWaitOptions
|
|
20468
|
+
);
|
|
20469
|
+
return Promise.resolve();
|
|
20470
|
+
}
|
|
19707
20471
|
});
|
|
19708
20472
|
})
|
|
19709
20473
|
);
|
|
@@ -19724,7 +20488,7 @@ function createV2Setup(deps) {
|
|
|
19724
20488
|
}
|
|
19725
20489
|
const events = new AbortController();
|
|
19726
20490
|
if (typeof ctx.event?.subscribe === "function") {
|
|
19727
|
-
void
|
|
20491
|
+
void followSessionEvents(ctx.event.subscribe.bind(ctx.event), events.signal);
|
|
19728
20492
|
}
|
|
19729
20493
|
log.info("claude-code plugin set up for opencode 2", {
|
|
19730
20494
|
opencode: ctx.app?.version,
|