@centralu/host-linux-x64 0.1.0-beta.35 → 0.1.0-beta.36
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/host/bundle-info.json +2 -2
- package/host/centralu-package.json +5 -5
- package/host/main.mjs +479 -67
- package/package.json +1 -1
package/host/bundle-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "centralu",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.36",
|
|
4
4
|
"description": "Run, watch and steer several Claude Code and Codex CLI sessions from one window",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ijun17",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"win32"
|
|
39
39
|
],
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"@centralu/darwin-arm64": "0.1.0-beta.
|
|
42
|
-
"@centralu/linux-arm64": "0.1.0-beta.
|
|
43
|
-
"@centralu/linux-x64": "0.1.0-beta.
|
|
44
|
-
"@centralu/win32-x64": "0.1.0-beta.
|
|
41
|
+
"@centralu/darwin-arm64": "0.1.0-beta.36",
|
|
42
|
+
"@centralu/linux-arm64": "0.1.0-beta.36",
|
|
43
|
+
"@centralu/linux-x64": "0.1.0-beta.36",
|
|
44
|
+
"@centralu/win32-x64": "0.1.0-beta.36"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/host/main.mjs
CHANGED
|
@@ -43055,7 +43055,7 @@ import { mkdirSync as mkdirSync19, writeSync as writeSync3 } from "node:fs";
|
|
|
43055
43055
|
|
|
43056
43056
|
// packages/protocol/src/brand.ts
|
|
43057
43057
|
var APP_NAME = "Centralu";
|
|
43058
|
-
var APP_VERSION = "0.1.0-beta.
|
|
43058
|
+
var APP_VERSION = "0.1.0-beta.36";
|
|
43059
43059
|
var APP_SLUG = "centralu";
|
|
43060
43060
|
var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
|
|
43061
43061
|
var DATA_DIR = ".centralu";
|
|
@@ -58787,6 +58787,13 @@ var RequestInput = external_exports.object({
|
|
|
58787
58787
|
* when the tool does not say, or the request wrote nothing.
|
|
58788
58788
|
*/
|
|
58789
58789
|
cacheTtl: external_exports.enum(["5m", "1h"]).optional(),
|
|
58790
|
+
/**
|
|
58791
|
+
* Of `cacheCreationTokens`, what the API said stays cached an hour and five minutes (Claude's `cache_creation` split, #1492):
|
|
58792
|
+
* the two are priced apart (docs/agent-host.md §6.1), so the weighting of writes needs them, not only which one the request had
|
|
58793
|
+
* most of. Absent when the tool did not split its writes; the part that is in neither is priced as a five-minute write.
|
|
58794
|
+
*/
|
|
58795
|
+
cacheWrite1hTokens: external_exports.number().optional(),
|
|
58796
|
+
cacheWrite5mTokens: external_exports.number().optional(),
|
|
58790
58797
|
/** The model that served this request, as the tool reported it (Claude's `message.model`); absent when it does not say (usage history, §6.2) */
|
|
58791
58798
|
model: external_exports.string().optional()
|
|
58792
58799
|
});
|
|
@@ -58813,7 +58820,13 @@ var CacheSessionStats = external_exports.object({
|
|
|
58813
58820
|
cacheCreationTokens: external_exports.number(),
|
|
58814
58821
|
rebuilds: external_exports.number(),
|
|
58815
58822
|
/** The rebuilds by their likely cause (`RebuildCause`); absent from a host that predates it */
|
|
58816
|
-
rebuildCauses: external_exports.record(external_exports.string(), external_exports.number()).optional()
|
|
58823
|
+
rebuildCauses: external_exports.record(external_exports.string(), external_exports.number()).optional(),
|
|
58824
|
+
/**
|
|
58825
|
+
* What the rebuilds wrote to the cache, in tokens (#1492): the sum of the cache writes of the requests counted as rebuilds, so the
|
|
58826
|
+
* steady writes every new message needs are not in it. For a tool that does not report writes (Codex) the input it did not read
|
|
58827
|
+
* from the cache stands in. Absent from a host that predates it, and from hours an earlier build stored.
|
|
58828
|
+
*/
|
|
58829
|
+
rebuildTokens: external_exports.number().optional()
|
|
58817
58830
|
});
|
|
58818
58831
|
var CacheWindowStats = external_exports.object({
|
|
58819
58832
|
/** `5h`, `today` */
|
|
@@ -58830,6 +58843,17 @@ var CacheWindowStats = external_exports.object({
|
|
|
58830
58843
|
rebuilds: external_exports.number(),
|
|
58831
58844
|
/** The rebuilds by their likely cause (`RebuildCause`); absent from a host that predates it */
|
|
58832
58845
|
rebuildCauses: external_exports.record(external_exports.string(), external_exports.number()).optional(),
|
|
58846
|
+
/** What the rebuilds wrote to the cache, in tokens (`CacheSessionStats.rebuildTokens`) */
|
|
58847
|
+
rebuildTokens: external_exports.number().optional(),
|
|
58848
|
+
/**
|
|
58849
|
+
* What the cache cost in units of an uncached input token (#1492), for the share the writes were of it: the tokens read from the cache
|
|
58850
|
+
* times the price of a read, and the tokens written times the price of a write, each as a ratio to uncached input (Anthropic: read 0.1,
|
|
58851
|
+
* five-minute write 1.25, one-hour write 2; Codex by model, docs/agent-host.md §6.1). No money: the ratios, not the prices. For a tool
|
|
58852
|
+
* that does not report writes (Codex) the write units are those of the input it did not read from the cache. Absent from a host that
|
|
58853
|
+
* predates them.
|
|
58854
|
+
*/
|
|
58855
|
+
cacheReadCost: external_exports.number().optional(),
|
|
58856
|
+
cacheWriteCost: external_exports.number().optional(),
|
|
58833
58857
|
/**
|
|
58834
58858
|
* The shortest and longest idle time before a rebuild put down to the cache expiring (`expired`, `probably_expired`),
|
|
58835
58859
|
* in milliseconds; absent when there was none
|
|
@@ -59109,14 +59133,33 @@ function readNotifyPolicy(raw) {
|
|
|
59109
59133
|
var MIN_AUTO_COMPACT_SHARE = 0.2;
|
|
59110
59134
|
var MIN_IDLE_COMPACT_SHARE = 0.1;
|
|
59111
59135
|
var MAX_AUTO_COMPACT_SHARE = 0.95;
|
|
59112
|
-
var
|
|
59136
|
+
var MIN_NUDGE_SHARE = 0.1;
|
|
59137
|
+
var MAX_NUDGE_SHARE = 0.9;
|
|
59138
|
+
var NUDGE_SECOND_GAP = 0.1;
|
|
59139
|
+
var NUDGE_MARGIN = 0.05;
|
|
59140
|
+
var DEFAULT_AUTO_COMPACT = { controlled: true, share: 0.6, idleShare: 0.3, nudge: true, nudgeFirst: 0.3, nudgeSecond: null };
|
|
59141
|
+
var hundredths = (n) => Math.round(n * 100) / 100;
|
|
59142
|
+
function nudgeShares(c) {
|
|
59143
|
+
const ceiling = hundredths(c.share - NUDGE_MARGIN);
|
|
59144
|
+
const second = hundredths(c.nudgeSecond ?? c.share - NUDGE_SECOND_GAP);
|
|
59145
|
+
return { first: c.nudgeFirst, second: Math.min(ceiling, Math.max(MIN_NUDGE_SHARE, second)) };
|
|
59146
|
+
}
|
|
59113
59147
|
function readAutoCompact(raw) {
|
|
59114
59148
|
const out = { ...DEFAULT_AUTO_COMPACT };
|
|
59115
59149
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return out;
|
|
59116
59150
|
if ("controlled" in raw && typeof raw.controlled === "boolean") out.controlled = raw.controlled;
|
|
59117
59151
|
if ("share" in raw && typeof raw.share === "number" && Number.isFinite(raw.share)) out.share = Math.min(MAX_AUTO_COMPACT_SHARE, Math.max(MIN_AUTO_COMPACT_SHARE, raw.share));
|
|
59118
59152
|
if ("idleShare" in raw && typeof raw.idleShare === "number" && Number.isFinite(raw.idleShare)) out.idleShare = Math.min(MAX_AUTO_COMPACT_SHARE, Math.max(MIN_IDLE_COMPACT_SHARE, raw.idleShare));
|
|
59119
|
-
|
|
59153
|
+
if ("nudge" in raw && typeof raw.nudge === "boolean") out.nudge = raw.nudge;
|
|
59154
|
+
if ("nudgeFirst" in raw && typeof raw.nudgeFirst === "number" && Number.isFinite(raw.nudgeFirst)) out.nudgeFirst = Math.min(MAX_NUDGE_SHARE, Math.max(MIN_NUDGE_SHARE, raw.nudgeFirst));
|
|
59155
|
+
if ("nudgeSecond" in raw && typeof raw.nudgeSecond === "number" && Number.isFinite(raw.nudgeSecond)) out.nudgeSecond = Math.min(MAX_NUDGE_SHARE, Math.max(MIN_NUDGE_SHARE, raw.nudgeSecond));
|
|
59156
|
+
return settledSecond(out);
|
|
59157
|
+
}
|
|
59158
|
+
function settledSecond(c) {
|
|
59159
|
+
if (c.nudgeSecond === null) return c;
|
|
59160
|
+
const held3 = Math.min(c.nudgeSecond, hundredths(c.share - NUDGE_MARGIN));
|
|
59161
|
+
const nudgeSecond = held3 > c.nudgeFirst ? held3 : null;
|
|
59162
|
+
return nudgeSecond === c.nudgeSecond ? c : { ...c, nudgeSecond };
|
|
59120
59163
|
}
|
|
59121
59164
|
var LineHeight = external_exports.enum(["compact", "normal", "relaxed"]);
|
|
59122
59165
|
var UiPreferences = external_exports.object({
|
|
@@ -59234,7 +59277,11 @@ var UiPreferencesPatch = UiPreferences.partial().extend({
|
|
|
59234
59277
|
autoCompact: external_exports.object({
|
|
59235
59278
|
controlled: external_exports.boolean(),
|
|
59236
59279
|
share: external_exports.number().min(MIN_AUTO_COMPACT_SHARE).max(MAX_AUTO_COMPACT_SHARE),
|
|
59237
|
-
idleShare: external_exports.number().min(MIN_IDLE_COMPACT_SHARE).max(MAX_AUTO_COMPACT_SHARE)
|
|
59280
|
+
idleShare: external_exports.number().min(MIN_IDLE_COMPACT_SHARE).max(MAX_AUTO_COMPACT_SHARE),
|
|
59281
|
+
nudge: external_exports.boolean(),
|
|
59282
|
+
nudgeFirst: external_exports.number().min(MIN_NUDGE_SHARE).max(MAX_NUDGE_SHARE),
|
|
59283
|
+
/** null: back to the default, below `share` by `NUDGE_SECOND_GAP` */
|
|
59284
|
+
nudgeSecond: external_exports.number().min(MIN_NUDGE_SHARE).max(MAX_NUDGE_SHARE).nullable()
|
|
59238
59285
|
}).partial().optional()
|
|
59239
59286
|
});
|
|
59240
59287
|
var DEFAULT_AGENT_DEPTH = 1;
|
|
@@ -59735,7 +59782,8 @@ var NormalizedEvent = external_exports.discriminatedUnion("type", [
|
|
|
59735
59782
|
after: external_exports.number().optional(),
|
|
59736
59783
|
/**
|
|
59737
59784
|
* Who started it, when the tool says (Claude's `compact_metadata.trigger`): `manual` is a `/compact` (the person's or
|
|
59738
|
-
* Centralu's, #1451), `auto` the tool's own at its threshold. Centralu does not take an `auto` one for its own.
|
|
59785
|
+
* Centralu's, #1451), `auto` the tool's own at its threshold. Centralu does not take an `auto` one for its own. Codex does
|
|
59786
|
+
* not say why; its adapter says `manual` for a compaction inside the compact turn the session started, `auto` otherwise (#1500).
|
|
59739
59787
|
*/
|
|
59740
59788
|
trigger: external_exports.enum(["manual", "auto"]).optional()
|
|
59741
59789
|
}),
|
|
@@ -65780,12 +65828,12 @@ var HostServer = class _HostServer {
|
|
|
65780
65828
|
}
|
|
65781
65829
|
const { method, params, id: id3 } = frame.data;
|
|
65782
65830
|
if (bridgeOf !== null) {
|
|
65783
|
-
const
|
|
65784
|
-
if (
|
|
65831
|
+
const refusal2 = bridgeRefusal(bridgeOf, method, params);
|
|
65832
|
+
if (refusal2 !== null) return this.sendError(ws2, id3, { code: "internal", message: refusal2, retryable: false });
|
|
65785
65833
|
}
|
|
65786
65834
|
if (limited) {
|
|
65787
|
-
const
|
|
65788
|
-
if (
|
|
65835
|
+
const refusal2 = limitedRefusal(method, params);
|
|
65836
|
+
if (refusal2?.rights === "asks" && this.opts.onAsk) {
|
|
65789
65837
|
try {
|
|
65790
65838
|
const result = await this.opts.onAsk(method, params, { client: label }, leaving.signal);
|
|
65791
65839
|
this.sendTo(ws2, JSON.stringify({ kind: "res", id: id3, ok: true, result }));
|
|
@@ -65795,9 +65843,9 @@ var HostServer = class _HostServer {
|
|
|
65795
65843
|
}
|
|
65796
65844
|
return;
|
|
65797
65845
|
}
|
|
65798
|
-
if (
|
|
65799
|
-
console.error(`[agent-host] refused ${JSON.stringify(method)} to a connection holding the limited credential (${
|
|
65800
|
-
return this.sendError(ws2, id3, { code: "internal", message:
|
|
65846
|
+
if (refusal2 !== null) {
|
|
65847
|
+
console.error(`[agent-host] refused ${JSON.stringify(method)} to a connection holding the limited credential (${refusal2.rights})`);
|
|
65848
|
+
return this.sendError(ws2, id3, { code: "internal", message: refusal2.message, retryable: false, data: { refused: "rights", rights: refusal2.rights } });
|
|
65801
65849
|
}
|
|
65802
65850
|
}
|
|
65803
65851
|
try {
|
|
@@ -102663,8 +102711,8 @@ var InlineViews = class {
|
|
|
102663
102711
|
if (this.deps.rt.list().find((a) => a.appId === v.ref.appId && a.projectId === v.ref.projectId)?.status === "failed") {
|
|
102664
102712
|
refuse3("This app stopped after failing repeatedly. Restart it, then reopen this view");
|
|
102665
102713
|
}
|
|
102666
|
-
const
|
|
102667
|
-
if (
|
|
102714
|
+
const refusal2 = await this.refusal(v.ref, v.uri);
|
|
102715
|
+
if (refusal2) refuse3(refusal2);
|
|
102668
102716
|
if (!v.instanceId) {
|
|
102669
102717
|
let instanceId;
|
|
102670
102718
|
try {
|
|
@@ -102751,10 +102799,10 @@ var InlineViews = class {
|
|
|
102751
102799
|
}
|
|
102752
102800
|
if (this.disposed) return;
|
|
102753
102801
|
const base2 = { type: "app_view", sessionId: c.sessionId, callId, appId: c.ref.appId, projectId: c.ref.projectId, tool: c.tool };
|
|
102754
|
-
const
|
|
102755
|
-
if (
|
|
102756
|
-
this.log(`[apps] ${where2}: view rejected \u2014 ${
|
|
102757
|
-
this.deps.emit({ ...base2, phase: "rejected", reason:
|
|
102802
|
+
const refusal2 = await this.refusal(c.ref, ui2.uri);
|
|
102803
|
+
if (refusal2) {
|
|
102804
|
+
this.log(`[apps] ${where2}: view rejected \u2014 ${refusal2}`);
|
|
102805
|
+
this.deps.emit({ ...base2, phase: "rejected", reason: refusal2 });
|
|
102758
102806
|
return;
|
|
102759
102807
|
}
|
|
102760
102808
|
let instanceId;
|
|
@@ -107134,7 +107182,10 @@ var ORCHESTRATOR_TOOLS = [
|
|
|
107134
107182
|
autoCompact: external_exports.object({
|
|
107135
107183
|
controlled: external_exports.boolean().optional().describe("Whether Centralu sets when agents compact (default on); off, each agent's own settings decide."),
|
|
107136
107184
|
share: external_exports.number().optional().describe("Share of the model's window to compact at, 0.2 to 0.95 (default 0.6)."),
|
|
107137
|
-
idleShare: external_exports.number().optional().describe("Share over which a session at rest for 10 minutes is compacted by Centralu, 0.1 to 0.95 (default 0.3).")
|
|
107185
|
+
idleShare: external_exports.number().optional().describe("Share over which a session at rest for 10 minutes is compacted by Centralu, 0.1 to 0.95 (default 0.3)."),
|
|
107186
|
+
nudge: external_exports.boolean().optional().describe("Whether Centralu quietly tells a session to call compact_self as its context grows (default on)."),
|
|
107187
|
+
nudgeFirst: external_exports.number().optional().describe("Share of the window for the first notice, 0.1 to 0.9 (default 0.3)."),
|
|
107188
|
+
nudgeSecond: external_exports.number().nullable().optional().describe("Share for the second notice, 0.1 to 0.9 and 5+ points below share; null for the default, 10 points below share.")
|
|
107138
107189
|
}).strict().optional().describe("When agents compact by themselves.")
|
|
107139
107190
|
}).strict()
|
|
107140
107191
|
},
|
|
@@ -109159,7 +109210,7 @@ ${machineLine(machine)}` };
|
|
|
109159
109210
|
var HOUR_MS = 60 * 6e4;
|
|
109160
109211
|
var BUCKET_COLUMNS = `hour, session_id AS sessionId, project_id AS projectId, tool, model, machine, requests, input,
|
|
109161
109212
|
cache_read AS cacheRead, cache_write AS cacheWrite, cache_write_5m AS cacheWrite5m, cache_write_1h AS cacheWrite1h,
|
|
109162
|
-
output, rebuilds, causes`;
|
|
109213
|
+
output, rebuilds, rebuild_tokens AS rebuildTokens, causes`;
|
|
109163
109214
|
var UsageRecords = class {
|
|
109164
109215
|
constructor(db) {
|
|
109165
109216
|
this.db = db;
|
|
@@ -109215,14 +109266,14 @@ var UsageRecords = class {
|
|
|
109215
109266
|
);
|
|
109216
109267
|
const upsert = this.db.prepare(
|
|
109217
109268
|
`INSERT INTO usage_buckets (hour, session_id, project_id, tool, model, machine, requests, input, cache_read, cache_write,
|
|
109218
|
-
cache_write_5m, cache_write_1h, output, rebuilds, causes)
|
|
109269
|
+
cache_write_5m, cache_write_1h, output, rebuilds, rebuild_tokens, causes)
|
|
109219
109270
|
VALUES (@hour, @sessionId, @projectId, @tool, @model, @machine, @requests, @input, @cacheRead, @cacheWrite,
|
|
109220
|
-
@cacheWrite5m, @cacheWrite1h, @output, @rebuilds, @causes)
|
|
109271
|
+
@cacheWrite5m, @cacheWrite1h, @output, @rebuilds, @rebuildTokens, @causes)
|
|
109221
109272
|
ON CONFLICT(hour, session_id, project_id, tool, model, machine) DO UPDATE SET
|
|
109222
109273
|
requests = requests + excluded.requests, input = input + excluded.input, cache_read = cache_read + excluded.cache_read,
|
|
109223
109274
|
cache_write = cache_write + excluded.cache_write, cache_write_5m = cache_write_5m + excluded.cache_write_5m,
|
|
109224
109275
|
cache_write_1h = cache_write_1h + excluded.cache_write_1h, output = output + excluded.output,
|
|
109225
|
-
rebuilds = rebuilds + excluded.rebuilds, causes = excluded.causes`
|
|
109276
|
+
rebuilds = rebuilds + excluded.rebuilds, rebuild_tokens = rebuild_tokens + excluded.rebuild_tokens, causes = excluded.causes`
|
|
109226
109277
|
);
|
|
109227
109278
|
this.db.transaction(() => {
|
|
109228
109279
|
for (const b2 of buckets) {
|
|
@@ -113674,7 +113725,10 @@ var UPDATE_CARD = 'Turn on "Automatic updates"? From then on this computer check
|
|
|
113674
113725
|
var FOLDED_GROUPS_MAX = 500;
|
|
113675
113726
|
var SLEEP_CHOICES = 'a whole number of minutes, or "never"';
|
|
113676
113727
|
function autoCompactText(c) {
|
|
113677
|
-
|
|
113728
|
+
if (!c.controlled) return "Centralu does not set when agents compact; each agent's own settings decide";
|
|
113729
|
+
const n = nudgeShares(c);
|
|
113730
|
+
const nudge = c.nudge ? `a session is told quietly to call compact_self at ${Math.round(n.first * 100)}% and ${Math.round(n.second * 100)}%` : "a session is not told to call compact_self";
|
|
113731
|
+
return `agents compact at ${Math.round(c.share * 100)}% of the model's window, set by Centralu; a session at rest is compacted over ${Math.round(c.idleShare * 100)}%; ${nudge}`;
|
|
113678
113732
|
}
|
|
113679
113733
|
var PreferencesFlow = class {
|
|
113680
113734
|
constructor(host) {
|
|
@@ -115442,7 +115496,13 @@ var ProjectConsents = class {
|
|
|
115442
115496
|
|
|
115443
115497
|
// packages/agent-host/src/sessions/ui-preferences.ts
|
|
115444
115498
|
var UI_PREFS_KEY = "ui_preferences";
|
|
115445
|
-
var
|
|
115499
|
+
var COMPACT_BESIDE = {
|
|
115500
|
+
idleShare: "autoCompactIdleShare",
|
|
115501
|
+
nudge: "autoCompactNudge",
|
|
115502
|
+
nudgeFirst: "autoCompactNudgeFirst",
|
|
115503
|
+
nudgeSecond: "autoCompactNudgeSecond"
|
|
115504
|
+
};
|
|
115505
|
+
var COMPACT_BESIDE_FIELDS = ["idleShare", "nudge", "nudgeFirst", "nudgeSecond"];
|
|
115446
115506
|
var UiPreferenceStore = class {
|
|
115447
115507
|
constructor(host) {
|
|
115448
115508
|
this.host = host;
|
|
@@ -115479,8 +115539,9 @@ var UiPreferenceStore = class {
|
|
|
115479
115539
|
}
|
|
115480
115540
|
if (this.brokenRow() || "hubAuto" in stored && typeof stored.hubAuto !== "boolean") prefs.hubAuto = false;
|
|
115481
115541
|
prefs.sessionTools = true;
|
|
115482
|
-
const
|
|
115483
|
-
|
|
115542
|
+
const beside = {};
|
|
115543
|
+
for (const field of COMPACT_BESIDE_FIELDS) if (COMPACT_BESIDE[field] in stored) beside[field] = stored[COMPACT_BESIDE[field]];
|
|
115544
|
+
if (Object.keys(beside).length > 0) prefs.autoCompact = readAutoCompact({ ...prefs.autoCompact ?? {}, ...beside });
|
|
115484
115545
|
return prefs;
|
|
115485
115546
|
}
|
|
115486
115547
|
/**
|
|
@@ -115495,21 +115556,37 @@ var UiPreferenceStore = class {
|
|
|
115495
115556
|
const { notifyPolicy: switches, autoCompact: compact, sessionTools: _alwaysOn, ...rest } = patch;
|
|
115496
115557
|
const base2 = current.notifyPolicy ?? readNotifyPolicy(this.loadWorkspace()?.notifyPolicy);
|
|
115497
115558
|
const notifyPolicy = switches ? { ...base2, ...switches } : void 0;
|
|
115498
|
-
const
|
|
115499
|
-
const
|
|
115500
|
-
|
|
115559
|
+
const before = readAutoCompact(current.autoCompact);
|
|
115560
|
+
const asked = compact ? { ...before, ...withoutUndefined(compact) } : void 0;
|
|
115561
|
+
if (compact && asked && asked.nudgeSecond !== null && (compact.nudgeSecond != null || compact.nudgeFirst !== void 0)) {
|
|
115562
|
+
const second = Math.round(asked.nudgeSecond * 100);
|
|
115563
|
+
if (asked.nudgeSecond > asked.share - NUDGE_MARGIN + 1e-9) {
|
|
115564
|
+
throw refusal(`The second reminder (${second}%) must be at least ${Math.round(NUDGE_MARGIN * 100)} points below the share agents compact at (${Math.round(asked.share * 100)}%): the tool's own compaction would come first.`);
|
|
115565
|
+
}
|
|
115566
|
+
if (asked.nudgeSecond <= asked.nudgeFirst + 1e-9) {
|
|
115567
|
+
throw refusal(`The second reminder (${second}%) must be above the first (${Math.round(asked.nudgeFirst * 100)}%).`);
|
|
115568
|
+
}
|
|
115569
|
+
}
|
|
115570
|
+
const merged = asked ? readAutoCompact(asked) : void 0;
|
|
115571
|
+
const compactKeys = Object.keys(withoutUndefined(compact ?? {}));
|
|
115572
|
+
const autoCompact = merged && compactKeys.some((k2) => !(k2 in COMPACT_BESIDE)) ? { controlled: merged.controlled, share: merged.share } : void 0;
|
|
115573
|
+
const besides = {};
|
|
115574
|
+
if (compact && merged) {
|
|
115575
|
+
for (const field of COMPACT_BESIDE_FIELDS) if (compact[field] !== void 0 || merged[field] !== before[field]) besides[COMPACT_BESIDE[field]] = merged[field];
|
|
115576
|
+
}
|
|
115501
115577
|
const written = {
|
|
115502
115578
|
...rest,
|
|
115503
115579
|
...notifyPolicy ? { notifyPolicy } : {},
|
|
115504
115580
|
...autoCompact ? { autoCompact } : {},
|
|
115505
|
-
...
|
|
115581
|
+
...besides
|
|
115506
115582
|
};
|
|
115507
115583
|
const stored = this.storedUiPreferences();
|
|
115508
115584
|
const damaged = this.brokenRow() || "hubAuto" in stored && typeof stored.hubAuto !== "boolean";
|
|
115509
115585
|
const kept = damaged && patch.hubAuto === void 0 ? { hubAuto: false } : {};
|
|
115510
115586
|
this.host.store.settings.setAppSetting(UI_PREFS_KEY, JSON.stringify({ ...stored, ...kept, ...written }));
|
|
115511
115587
|
if (Object.keys(written).length) this.host.emit({ type: "prefs_changed" });
|
|
115512
|
-
const {
|
|
115588
|
+
const { autoCompact: _two, ...shown3 } = written;
|
|
115589
|
+
for (const key of Object.values(COMPACT_BESIDE)) delete shown3[key];
|
|
115513
115590
|
return { ...current, ...shown3, ...merged ? { autoCompact: merged } : {} };
|
|
115514
115591
|
}
|
|
115515
115592
|
/** The stored record as written, every field kept; `{}` for none or for one that is not a JSON object */
|
|
@@ -115531,6 +115608,19 @@ var UiPreferenceStore = class {
|
|
|
115531
115608
|
return raw !== null && !isObj(parseJson(raw));
|
|
115532
115609
|
}
|
|
115533
115610
|
};
|
|
115611
|
+
function refusal(message) {
|
|
115612
|
+
return rpcError(message, "refused", { retryable: false, data: { reason: "bad_value" } });
|
|
115613
|
+
}
|
|
115614
|
+
function withoutUndefined(o) {
|
|
115615
|
+
return {
|
|
115616
|
+
...o.controlled !== void 0 ? { controlled: o.controlled } : {},
|
|
115617
|
+
...o.share !== void 0 ? { share: o.share } : {},
|
|
115618
|
+
...o.idleShare !== void 0 ? { idleShare: o.idleShare } : {},
|
|
115619
|
+
...o.nudge !== void 0 ? { nudge: o.nudge } : {},
|
|
115620
|
+
...o.nudgeFirst !== void 0 ? { nudgeFirst: o.nudgeFirst } : {},
|
|
115621
|
+
...o.nudgeSecond !== void 0 ? { nudgeSecond: o.nudgeSecond } : {}
|
|
115622
|
+
};
|
|
115623
|
+
}
|
|
115534
115624
|
|
|
115535
115625
|
// packages/agent-host/src/sessions/session-apps.ts
|
|
115536
115626
|
var TOOL_LIST_WAIT_MS = 15e3;
|
|
@@ -119556,6 +119646,61 @@ var MailDelivery = class {
|
|
|
119556
119646
|
}
|
|
119557
119647
|
};
|
|
119558
119648
|
|
|
119649
|
+
// packages/agent-host/src/sessions/context-meter.ts
|
|
119650
|
+
var ESTIMATE_GAP_MS = 3e3;
|
|
119651
|
+
var ContextMeter = class {
|
|
119652
|
+
constructor(now = Date.now) {
|
|
119653
|
+
this.now = now;
|
|
119654
|
+
}
|
|
119655
|
+
now;
|
|
119656
|
+
sessions = /* @__PURE__ */ new Map();
|
|
119657
|
+
state(id3) {
|
|
119658
|
+
let s = this.sessions.get(id3);
|
|
119659
|
+
if (!s) this.sessions.set(id3, s = { working: false, estimated: false, readThisTurn: false, owed: false, at: 0 });
|
|
119660
|
+
return s;
|
|
119661
|
+
}
|
|
119662
|
+
/**
|
|
119663
|
+
* Whether a context reading, before it is applied, is the turn before's landing in a turn that has an estimate of its own: then it
|
|
119664
|
+
* must not become the session's figure. Sees the tool's readings only; an estimate is never stale.
|
|
119665
|
+
*/
|
|
119666
|
+
stale(e) {
|
|
119667
|
+
if (e.type !== "context_update" || e.exactness === "estimate") return false;
|
|
119668
|
+
const s = this.state(e.sessionId);
|
|
119669
|
+
const late = s.owed;
|
|
119670
|
+
s.owed = false;
|
|
119671
|
+
if (late) return s.working && s.estimated;
|
|
119672
|
+
s.readThisTurn = true;
|
|
119673
|
+
return false;
|
|
119674
|
+
}
|
|
119675
|
+
/**
|
|
119676
|
+
* After an event is applied (`m` is the session as it now is): the estimate it brings, if any, as the event to apply.
|
|
119677
|
+
* `perRequest`: the tool reads the size with every request (Codex sends it right after the request's token count), so the
|
|
119678
|
+
* figure is exact without an estimate, which could only flicker in front of it (#1504 review)
|
|
119679
|
+
*/
|
|
119680
|
+
observe(e, m, perRequest = false) {
|
|
119681
|
+
const s = this.state(m.id);
|
|
119682
|
+
if (e.type === "session_deleted") {
|
|
119683
|
+
this.sessions.delete(m.id);
|
|
119684
|
+
return null;
|
|
119685
|
+
}
|
|
119686
|
+
const working = inTurn(m.state);
|
|
119687
|
+
if (working && !s.working) {
|
|
119688
|
+
s.estimated = false;
|
|
119689
|
+
s.readThisTurn = false;
|
|
119690
|
+
}
|
|
119691
|
+
if (s.working && !working) s.owed = !s.readThisTurn;
|
|
119692
|
+
s.working = working;
|
|
119693
|
+
if (perRequest || e.type !== "usage_update" || !e.request || e.request.agent !== void 0) return null;
|
|
119694
|
+
if (!working || s.readThisTurn || !m.context || m.context.window <= 0) return null;
|
|
119695
|
+
const used = Math.min(m.context.window, e.request.inputTokens + e.request.cacheReadTokens + (e.request.cacheCreationTokens ?? 0));
|
|
119696
|
+
const now = this.now();
|
|
119697
|
+
if (used === m.context.used || now - s.at < ESTIMATE_GAP_MS) return null;
|
|
119698
|
+
s.at = now;
|
|
119699
|
+
s.estimated = true;
|
|
119700
|
+
return { type: "context_update", sessionId: m.id, used, window: m.context.window, exactness: "estimate" };
|
|
119701
|
+
}
|
|
119702
|
+
};
|
|
119703
|
+
|
|
119559
119704
|
// packages/agent-host/src/sessions/app-messages.ts
|
|
119560
119705
|
function appMessageFrame(app2, text2, via = "inline") {
|
|
119561
119706
|
const body = text2.split(/\r\n|[\n\r\u0085\u2028\u2029]/).map((line) => `> ${line}`).join("\n");
|
|
@@ -119616,6 +119761,8 @@ var SessionEvents = class {
|
|
|
119616
119761
|
this.host = host;
|
|
119617
119762
|
}
|
|
119618
119763
|
host;
|
|
119764
|
+
/** The context figure during a turn, from each request's size (#1492, context-meter.ts) */
|
|
119765
|
+
meter = new ContextMeter();
|
|
119619
119766
|
/** Receive event -> update metadata -> persist message -> broadcast */
|
|
119620
119767
|
onEvent(raw, from = null) {
|
|
119621
119768
|
const e = raw.type === "files_touched" ? this.projectRelative(raw) : raw;
|
|
@@ -119636,6 +119783,10 @@ var SessionEvents = class {
|
|
|
119636
119783
|
this.host.record.recordSubagentStep(e);
|
|
119637
119784
|
return;
|
|
119638
119785
|
}
|
|
119786
|
+
if (e.type === "context_update" && this.meter.stale(e)) {
|
|
119787
|
+
this.host.compactEvent(e, false);
|
|
119788
|
+
return;
|
|
119789
|
+
}
|
|
119639
119790
|
if (e.type === "settings_changed" && e.by === "tool") {
|
|
119640
119791
|
this.host.record.recordToolSettings(e);
|
|
119641
119792
|
return;
|
|
@@ -119718,6 +119869,9 @@ var SessionEvents = class {
|
|
|
119718
119869
|
if (endedTurn && e.sessionId) this.host.restartPolicy.applyDeferredRestart(e.sessionId);
|
|
119719
119870
|
if (e.sessionId) this.host.mailEvent(e, endedTurn);
|
|
119720
119871
|
if (e.sessionId) this.host.compactEvent(e, endedTurn);
|
|
119872
|
+
const meta3 = e.sessionId ? this.host.meta.get(e.sessionId) : void 0;
|
|
119873
|
+
const estimate = meta3 ? this.meter.observe(e, meta3, this.host.contextAtRequest(meta3.id)) : null;
|
|
119874
|
+
if (estimate) this.onEvent(estimate);
|
|
119721
119875
|
if (e.sessionId && (endedTurn || e.type === "state_change" || e.type === "approval_resolved" || e.type === "question_resolved")) this.host.mailPoke(e.sessionId);
|
|
119722
119876
|
if (e.type === "background_tasks" && e.sessionId) this.host.restartPolicy.applyDeferredRestart(e.sessionId);
|
|
119723
119877
|
if (e.type === "background_tasks" && e.sessionId && this.host.children.pausedReports.has(e.sessionId)) this.host.reports.settlePausedReport(e.sessionId);
|
|
@@ -119765,7 +119919,7 @@ var SessionEvents = class {
|
|
|
119765
119919
|
switch (e.type) {
|
|
119766
119920
|
case "usage_update": {
|
|
119767
119921
|
m.usage = e.tokens;
|
|
119768
|
-
const counted = e.request && this.host.cacheLedger.record(m.tool, m.id, e.request);
|
|
119922
|
+
const counted = e.request && this.host.cacheLedger.record(m.tool, m.id, e.request, void 0, m.model);
|
|
119769
119923
|
if (e.request && counted) this.host.usageRecorder.request({ sessionId: m.id, projectId: m.projectId, tool: m.tool, model: m.model ?? void 0 }, e.request, counted);
|
|
119770
119924
|
break;
|
|
119771
119925
|
}
|
|
@@ -119774,7 +119928,7 @@ var SessionEvents = class {
|
|
|
119774
119928
|
break;
|
|
119775
119929
|
case "context_update":
|
|
119776
119930
|
m.context = nextContext(m.context, e);
|
|
119777
|
-
this.host.modelWindows.note(m, e.window, e.model);
|
|
119931
|
+
if (e.exactness !== "estimate") this.host.modelWindows.note(m, e.window, e.model);
|
|
119778
119932
|
break;
|
|
119779
119933
|
case "conversation_reset":
|
|
119780
119934
|
m.context = nextContext(m.context, e);
|
|
@@ -120004,6 +120158,8 @@ var SelfCompaction = class {
|
|
|
120004
120158
|
/** The size of the last model request of the session's own conversation in the running turn: the size the turn grew to, in both tools */
|
|
120005
120159
|
lastRequest = /* @__PURE__ */ new Map();
|
|
120006
120160
|
timer = null;
|
|
120161
|
+
/** `stop()` was called: a late event of a turn that was running is not acted on (it would start the timer or send) */
|
|
120162
|
+
stopped = false;
|
|
120007
120163
|
now() {
|
|
120008
120164
|
return (this.host.now ?? Date.now)();
|
|
120009
120165
|
}
|
|
@@ -120028,7 +120184,8 @@ var SelfCompaction = class {
|
|
|
120028
120184
|
/** Hears every event of every session. Never throws into the caller's event */
|
|
120029
120185
|
observe(e, endedTurn) {
|
|
120030
120186
|
const id3 = e.sessionId;
|
|
120031
|
-
if (!id3) return;
|
|
120187
|
+
if (!id3 || this.stopped) return;
|
|
120188
|
+
if (e.type === "context_update" && e.exactness === "estimate") return;
|
|
120032
120189
|
if (e.type === "session_deleted") return this.forget(id3);
|
|
120033
120190
|
const m = this.host.session(id3);
|
|
120034
120191
|
if (!m) return;
|
|
@@ -120064,9 +120221,7 @@ var SelfCompaction = class {
|
|
|
120064
120221
|
}
|
|
120065
120222
|
const live2 = this.pending.get(id3);
|
|
120066
120223
|
if (e.type === "context_update" && live2 && live2.endedAt !== null) return this.decide(id3, e.used, m.tool);
|
|
120067
|
-
if (
|
|
120068
|
-
return this.decide(id3, live2.size ?? m.context?.used ?? 0, m.tool);
|
|
120069
|
-
}
|
|
120224
|
+
if (live2?.held && this.releaseHeld(id3, m)) return;
|
|
120070
120225
|
if (!endedTurn) return;
|
|
120071
120226
|
this.markRest(id3);
|
|
120072
120227
|
if (this.read.has(id3)) this.late.delete(id3);
|
|
@@ -120094,6 +120249,24 @@ var SelfCompaction = class {
|
|
|
120094
120249
|
if (grew !== null) this.decide(id3, grew, m.tool);
|
|
120095
120250
|
else this.awaitReading(id3, live2);
|
|
120096
120251
|
}
|
|
120252
|
+
/**
|
|
120253
|
+
* A request held for a card: judged now if the card is closed and the session at rest, dropped if it has no process any more.
|
|
120254
|
+
* Returns whether it was settled. **Not asked: background work.** The held path goes on `!busy && !cardOpen` even while the session
|
|
120255
|
+
* runs background work, as the rule for every request made in a turn does (the request does not ask whether it runs: waiting for
|
|
120256
|
+
* CI in the background is what it is for, docs/agent-host.md "Compacting at a break"); `sessionIdle`'s rule for the rest compaction is
|
|
120257
|
+
* stricter on purpose, so this is not to be made to match it.
|
|
120258
|
+
*/
|
|
120259
|
+
releaseHeld(id3, m) {
|
|
120260
|
+
const req = this.pending.get(id3);
|
|
120261
|
+
if (!req?.held) return false;
|
|
120262
|
+
if (!this.host.running(id3)) {
|
|
120263
|
+
this.settle(id3);
|
|
120264
|
+
return true;
|
|
120265
|
+
}
|
|
120266
|
+
if (inTurn(m.state) || this.cardOpen(id3)) return false;
|
|
120267
|
+
this.decide(id3, req.size ?? m.context?.used ?? 0, m.tool);
|
|
120268
|
+
return true;
|
|
120269
|
+
}
|
|
120097
120270
|
/** A request whose turn ended with no size: a timer decides it on the reading it has if the tool's own does not come */
|
|
120098
120271
|
awaitReading(id3, req) {
|
|
120099
120272
|
req.endedAt = this.now();
|
|
@@ -120170,6 +120343,7 @@ var SelfCompaction = class {
|
|
|
120170
120343
|
const setting = this.host.setting();
|
|
120171
120344
|
const sent = [];
|
|
120172
120345
|
for (const m of this.host.sessions()) {
|
|
120346
|
+
if (this.pending.get(m.id)?.held) this.releaseHeld(m.id, m);
|
|
120173
120347
|
if (!this.due(m, setting, now)) continue;
|
|
120174
120348
|
this.fire(m.id, `at rest for ${IDLE_COMPACT_MINUTES} minutes, over ${Math.round(setting.idleShare * 100)}% of the window`, null, m.tool);
|
|
120175
120349
|
if (this.compacting.has(m.id)) sent.push(m.id);
|
|
@@ -120198,10 +120372,12 @@ var SelfCompaction = class {
|
|
|
120198
120372
|
/** Starts the sweep. A host that never calls it still serves `compact_self` */
|
|
120199
120373
|
start() {
|
|
120200
120374
|
if (this.timer) return;
|
|
120375
|
+
this.stopped = false;
|
|
120201
120376
|
this.timer = setInterval(() => this.sweepSafely(), SWEEP_MS);
|
|
120202
120377
|
this.timer.unref?.();
|
|
120203
120378
|
}
|
|
120204
120379
|
stop() {
|
|
120380
|
+
this.stopped = true;
|
|
120205
120381
|
if (this.timer) clearInterval(this.timer);
|
|
120206
120382
|
this.timer = null;
|
|
120207
120383
|
for (const t of this.waits.values()) clearTimeout(t);
|
|
@@ -120225,6 +120401,102 @@ var SelfCompaction = class {
|
|
|
120225
120401
|
}
|
|
120226
120402
|
};
|
|
120227
120403
|
|
|
120404
|
+
// packages/agent-host/src/sessions/context-nudges.ts
|
|
120405
|
+
var ContextNudges = class {
|
|
120406
|
+
constructor(host) {
|
|
120407
|
+
this.host = host;
|
|
120408
|
+
}
|
|
120409
|
+
host;
|
|
120410
|
+
cycles = /* @__PURE__ */ new Map();
|
|
120411
|
+
stopped = false;
|
|
120412
|
+
/** Hears every event of every session. Never throws into the caller's event */
|
|
120413
|
+
observe(e) {
|
|
120414
|
+
const id3 = e.sessionId;
|
|
120415
|
+
if (!id3 || this.stopped) return;
|
|
120416
|
+
switch (e.type) {
|
|
120417
|
+
case "session_deleted":
|
|
120418
|
+
this.cycles.delete(id3);
|
|
120419
|
+
return;
|
|
120420
|
+
case "conversation_reset":
|
|
120421
|
+
this.cycles.delete(id3);
|
|
120422
|
+
return;
|
|
120423
|
+
case "compaction":
|
|
120424
|
+
if (!e.failed) this.cycles.delete(id3);
|
|
120425
|
+
return;
|
|
120426
|
+
case "usage_update":
|
|
120427
|
+
if (e.request && e.request.agent === void 0) this.requested(id3, e.request.inputTokens + e.request.cacheReadTokens + (e.request.cacheCreationTokens ?? 0));
|
|
120428
|
+
return;
|
|
120429
|
+
default:
|
|
120430
|
+
}
|
|
120431
|
+
}
|
|
120432
|
+
cycle(id3) {
|
|
120433
|
+
let c = this.cycles.get(id3);
|
|
120434
|
+
if (!c) this.cycles.set(id3, c = { told: /* @__PURE__ */ new Set(), waiting: null, size: 0, sending: false });
|
|
120435
|
+
return c;
|
|
120436
|
+
}
|
|
120437
|
+
requested(id3, size) {
|
|
120438
|
+
if (size < COMPACT_FLOOR_TOKENS) return;
|
|
120439
|
+
const setting = this.host.setting();
|
|
120440
|
+
const m = this.host.session(id3);
|
|
120441
|
+
if (!m || !setting.controlled || !setting.nudge || !this.host.hasCompactTool(id3)) return;
|
|
120442
|
+
const window2 = this.host.window(m) ?? m.context?.window ?? 0;
|
|
120443
|
+
if (window2 <= 0) return;
|
|
120444
|
+
const c = this.cycle(id3);
|
|
120445
|
+
c.size = size;
|
|
120446
|
+
const share = size / window2;
|
|
120447
|
+
const { first, second } = nudgeShares(setting);
|
|
120448
|
+
const crossed = ["first", "second"].filter((k2) => !c.told.has(k2) && (k2 === "first" ? first : second) < setting.share && (k2 === "first" ? first : second) <= share);
|
|
120449
|
+
if (crossed.length > 0) {
|
|
120450
|
+
for (const k2 of crossed) c.told.add(k2);
|
|
120451
|
+
c.waiting = crossed.includes("second") ? "second" : "first";
|
|
120452
|
+
}
|
|
120453
|
+
if (c.waiting) this.deliver(id3, c, m, window2, setting);
|
|
120454
|
+
}
|
|
120455
|
+
deliver(id3, c, m, window2, setting) {
|
|
120456
|
+
const which = c.waiting;
|
|
120457
|
+
if (!which || c.sending) return;
|
|
120458
|
+
const percent = Math.round(c.size / window2 * 100);
|
|
120459
|
+
const used = `${Math.round(c.size / 1e3)}k of ${Math.round(window2 / 1e3)}k tokens`;
|
|
120460
|
+
const text2 = which === "second" ? `[Centralu] Your context is now about ${percent}% full (${used}), and the tool compacts it by itself in the middle of work at ${Math.round(setting.share * 100)}%. Call compact_self when the current step is done, at the latest; do not stop what you are doing for it.` : `[Centralu] Your context is now about ${percent}% full (${used}). When this step of the work is done, call compact_self so the next step starts from a summary; do not stop what you are doing for it.`;
|
|
120461
|
+
const failed3 = (err) => {
|
|
120462
|
+
c.waiting = null;
|
|
120463
|
+
console.error(`[compact] ${id3.slice(0, 8)}: could not send the context notice: ${err instanceof Error ? err.message : String(err)}`);
|
|
120464
|
+
};
|
|
120465
|
+
const answered = (took2) => {
|
|
120466
|
+
if (!took2) return;
|
|
120467
|
+
c.waiting = null;
|
|
120468
|
+
this.host.notice(id3, `Centralu told it its context is ${percent}% full: call compact_self when this step is done`);
|
|
120469
|
+
};
|
|
120470
|
+
let took;
|
|
120471
|
+
try {
|
|
120472
|
+
took = this.host.nudge(id3, text2);
|
|
120473
|
+
} catch (err) {
|
|
120474
|
+
failed3(err);
|
|
120475
|
+
return;
|
|
120476
|
+
}
|
|
120477
|
+
if (typeof took === "boolean") {
|
|
120478
|
+
answered(took);
|
|
120479
|
+
return;
|
|
120480
|
+
}
|
|
120481
|
+
c.sending = true;
|
|
120482
|
+
void took.then(
|
|
120483
|
+
(ok) => {
|
|
120484
|
+
c.sending = false;
|
|
120485
|
+
if (!this.stopped && this.cycles.get(id3) === c) answered(ok);
|
|
120486
|
+
},
|
|
120487
|
+
(err) => {
|
|
120488
|
+
c.sending = false;
|
|
120489
|
+
if (!this.stopped && this.cycles.get(id3) === c) failed3(err);
|
|
120490
|
+
}
|
|
120491
|
+
);
|
|
120492
|
+
}
|
|
120493
|
+
/** The host is leaving: nothing is told after this, and nothing waits to be */
|
|
120494
|
+
stop() {
|
|
120495
|
+
this.stopped = true;
|
|
120496
|
+
this.cycles.clear();
|
|
120497
|
+
}
|
|
120498
|
+
};
|
|
120499
|
+
|
|
120228
120500
|
// packages/agent-host/src/adapters/claude/index.ts
|
|
120229
120501
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
120230
120502
|
import { mkdtemp as mkdtemp2, readdir as readdir9, rm as rm7, stat as stat8 } from "node:fs/promises";
|
|
@@ -122457,8 +122729,14 @@ var CLAUDE_KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
|
122457
122729
|
]);
|
|
122458
122730
|
function ttlOf(split2) {
|
|
122459
122731
|
const c = asObj(split2);
|
|
122460
|
-
|
|
122461
|
-
|
|
122732
|
+
const hour = Number(c.ephemeral_1h_input_tokens ?? 0);
|
|
122733
|
+
const five = Number(c.ephemeral_5m_input_tokens ?? 0);
|
|
122734
|
+
const amounts = {
|
|
122735
|
+
...Number.isFinite(hour) && "ephemeral_1h_input_tokens" in c ? { cacheWrite1hTokens: Math.max(0, hour) } : {},
|
|
122736
|
+
...Number.isFinite(five) && "ephemeral_5m_input_tokens" in c ? { cacheWrite5mTokens: Math.max(0, five) } : {}
|
|
122737
|
+
};
|
|
122738
|
+
if (hour > 0) return { cacheTtl: "1h", ...amounts };
|
|
122739
|
+
if (five > 0) return { cacheTtl: "5m", ...amounts };
|
|
122462
122740
|
return {};
|
|
122463
122741
|
}
|
|
122464
122742
|
function readingOf(events2) {
|
|
@@ -122689,10 +122967,10 @@ var ClaudeStreamNormalizer = class {
|
|
|
122689
122967
|
if (subagent) return events2;
|
|
122690
122968
|
events2 = this.followRetry(type, events2);
|
|
122691
122969
|
if (type === "result" && this.refusal !== null) {
|
|
122692
|
-
const
|
|
122970
|
+
const refusal2 = this.refusal;
|
|
122693
122971
|
this.refusal = null;
|
|
122694
122972
|
const failed3 = events2.some((e) => e.type === "error");
|
|
122695
|
-
events2 = failed3 ? events2.map((e) => e.type === "error" ? { ...e, error: { ...e.error, message:
|
|
122973
|
+
events2 = failed3 ? events2.map((e) => e.type === "error" ? { ...e, error: { ...e.error, message: refusal2 } } : e) : [{ type: "notice", sessionId: this.sessionId, level: "warning", text: refusal2, from: "Claude Code", label: "refusal" }, ...events2];
|
|
122696
122974
|
}
|
|
122697
122975
|
if (msg.type === "stream_event") {
|
|
122698
122976
|
const e = msg.event;
|
|
@@ -124154,6 +124432,12 @@ var ClaudeSession = class {
|
|
|
124154
124432
|
send(text2, opts) {
|
|
124155
124433
|
this.sendInput(text2, opts?.id !== void 0 && isUuid(opts.id) ? opts.id : randomUUID24(), void 0, opts?.origin, opts?.quiet);
|
|
124156
124434
|
}
|
|
124435
|
+
/** A note read with the next message or at the running turn's next tool round, starting nothing (#1492): quiet input, stamped as Centralu's */
|
|
124436
|
+
nudge(text2) {
|
|
124437
|
+
if (this.closed) return false;
|
|
124438
|
+
this.send(text2, { origin: { kind: "channel", server: "centralu" }, quiet: true });
|
|
124439
|
+
return true;
|
|
124440
|
+
}
|
|
124157
124441
|
/** The inputs the result being handled names (#1377); null when the CLI cannot name them, and outside a result */
|
|
124158
124442
|
resultInputs = null;
|
|
124159
124443
|
turnInputs() {
|
|
@@ -124456,6 +124740,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
124456
124740
|
tool = "claude";
|
|
124457
124741
|
/** `shouldQuery: false` (sdk.d.ts, measured on the Mac 2026-10-20: no turn, merged into the next message) */
|
|
124458
124742
|
quietInput = true;
|
|
124743
|
+
cacheBilling = "anthropic";
|
|
124459
124744
|
/** What Claude Code offers against what this adapter limits (#270) */
|
|
124460
124745
|
surface = CLAUDE_SURFACE;
|
|
124461
124746
|
launch;
|
|
@@ -126690,7 +126975,7 @@ var CodexSlashCommands = class {
|
|
|
126690
126975
|
/** The request a slash command started, or null when the text is a message */
|
|
126691
126976
|
run(text2) {
|
|
126692
126977
|
if (text2.trim() === "/compact") {
|
|
126693
|
-
this.host.setBlockingTurn(true);
|
|
126978
|
+
this.host.setBlockingTurn(true, true);
|
|
126694
126979
|
return this.host.request("thread/compact/start", { threadId: this.host.threadId() }).catch((e) => {
|
|
126695
126980
|
this.host.setBlockingTurn(false);
|
|
126696
126981
|
this.host.flushPending();
|
|
@@ -127040,8 +127325,9 @@ var CodexSession = class {
|
|
|
127040
127325
|
emit: (e) => this.emit(e),
|
|
127041
127326
|
request: (method, params) => this.client.request(method, params),
|
|
127042
127327
|
threadId: () => this.threadId,
|
|
127043
|
-
setBlockingTurn: (on2) => {
|
|
127328
|
+
setBlockingTurn: (on2, compact) => {
|
|
127044
127329
|
this.blockingTurn = on2;
|
|
127330
|
+
this.compactTurn = on2 && compact === true;
|
|
127045
127331
|
},
|
|
127046
127332
|
flushPending: () => this.flushPending()
|
|
127047
127333
|
});
|
|
@@ -127144,6 +127430,8 @@ var CodexSession = class {
|
|
|
127144
127430
|
inputGroups = /* @__PURE__ */ new Map();
|
|
127145
127431
|
/** An unsteerable turn (compact/review) is running — we know because we are the ones who started it */
|
|
127146
127432
|
blockingTurn = false;
|
|
127433
|
+
/** The unsteerable turn is a compaction we started (`/compact`), not a review: a compaction event in it is manual */
|
|
127434
|
+
compactTurn = false;
|
|
127147
127435
|
/** Messages sent while the thread was still coming up, and those a Stop reclaimed (see `WakingSends`, #168) */
|
|
127148
127436
|
sends = new WakingSends();
|
|
127149
127437
|
/** Codex is reconnecting on its own (#168): the `retrying` activity and what it puts back (see `RetryActivity`) */
|
|
@@ -127290,6 +127578,7 @@ var CodexSession = class {
|
|
|
127290
127578
|
}
|
|
127291
127579
|
if (n.method === "turn/completed" && this.blockingTurn) {
|
|
127292
127580
|
this.blockingTurn = false;
|
|
127581
|
+
this.compactTurn = false;
|
|
127293
127582
|
this.flushPending();
|
|
127294
127583
|
}
|
|
127295
127584
|
if (n.method === "thread/settings/updated") {
|
|
@@ -127312,7 +127601,8 @@ var CodexSession = class {
|
|
|
127312
127601
|
}
|
|
127313
127602
|
/** A notification of this thread's own, as the events it maps to (`normalizeNotification`), each the way it goes out */
|
|
127314
127603
|
emitNotification(n) {
|
|
127315
|
-
for (const
|
|
127604
|
+
for (const said of normalizeNotification(this.sessionId, n)) {
|
|
127605
|
+
const e = said.type === "compaction" ? { ...said, trigger: this.compactTurn ? "manual" : "auto" } : said;
|
|
127316
127606
|
if (e.type === "error" && this.inputReceipts.size) this.interrupt(new Error(e.error.message));
|
|
127317
127607
|
if (e.type === "message_image" && !e.data && e.path) {
|
|
127318
127608
|
void imageEventFromDisk(this.sessionId, e.path).then((filled2) => this.emit(filled2));
|
|
@@ -127323,7 +127613,8 @@ var CodexSession = class {
|
|
|
127323
127613
|
if (e.type === "usage_update") {
|
|
127324
127614
|
const request = this.requests.take(n.params);
|
|
127325
127615
|
if (request) {
|
|
127326
|
-
this.
|
|
127616
|
+
const model = this.shown?.model ?? this.opts.model ?? this.threadSettings?.model ?? null;
|
|
127617
|
+
this.emit({ ...e, request: model ? { ...request, model } : request });
|
|
127327
127618
|
continue;
|
|
127328
127619
|
}
|
|
127329
127620
|
}
|
|
@@ -127352,6 +127643,22 @@ var CodexSession = class {
|
|
|
127352
127643
|
send(text2) {
|
|
127353
127644
|
this.sendInput(text2);
|
|
127354
127645
|
}
|
|
127646
|
+
/**
|
|
127647
|
+
* A note steered into the regular turn that is running, starting nothing (#1492). `turn/start` would do it only while a turn is
|
|
127648
|
+
* running: with none, it starts a turn of its own (Codex sends a request's token count just before `turn/completed`, so a note for
|
|
127649
|
+
* the last request of a turn went out after the turn had ended and ran as a turn nobody asked for, #1504 review), and behind a
|
|
127650
|
+
* compact or review turn it queues the note and runs it as a turn. `turn/steer` names the turn it is for (`expectedTurnId`) and
|
|
127651
|
+
* is refused by Codex when that turn is not the one running, so a note for a turn that has ended starts nothing. The answer says
|
|
127652
|
+
* whether it was taken: a refused note is not told, and waits for a later request of a running turn (`ContextNudges`).
|
|
127653
|
+
*/
|
|
127654
|
+
nudge(text2) {
|
|
127655
|
+
const turnId = this.turns.turnId;
|
|
127656
|
+
if (this.closed || !this.started || !this.threadId || turnId === null || this.blockingTurn || this.pendingInputs.length > 0) return false;
|
|
127657
|
+
return this.client.request("turn/steer", { threadId: this.threadId, input: [{ type: "text", text: text2 }], expectedTurnId: turnId }).then(
|
|
127658
|
+
() => true,
|
|
127659
|
+
() => false
|
|
127660
|
+
);
|
|
127661
|
+
}
|
|
127355
127662
|
sendInput(text2, receipt) {
|
|
127356
127663
|
const nth = this.sends.issue();
|
|
127357
127664
|
this.startsInFlight += 1;
|
|
@@ -127912,6 +128219,8 @@ var exec5 = promisify6(execFile7);
|
|
|
127912
128219
|
var CODEX_VERBOSITIES = ["low", "medium", "high"];
|
|
127913
128220
|
var CodexAdapter = class {
|
|
127914
128221
|
tool = "codex";
|
|
128222
|
+
cacheBilling = "openai";
|
|
128223
|
+
contextAtRequest = true;
|
|
127915
128224
|
/** What Codex offers against what this adapter limits (#270) */
|
|
127916
128225
|
surface = CODEX_SURFACE;
|
|
127917
128226
|
descriptor = {
|
|
@@ -129416,6 +129725,25 @@ var SessionSettings = class {
|
|
|
129416
129725
|
}
|
|
129417
129726
|
};
|
|
129418
129727
|
|
|
129728
|
+
// packages/agent-host/src/sessions/cache-cost.ts
|
|
129729
|
+
function gptGeneration(model) {
|
|
129730
|
+
const m = /gpt-(\d+)(?:\.(\d+))?/i.exec(model ?? "");
|
|
129731
|
+
return m ? Number(m[1]) + Number(m[2] ?? 0) / 100 : null;
|
|
129732
|
+
}
|
|
129733
|
+
function cachePrices(billing, model) {
|
|
129734
|
+
if (billing === "anthropic") return { read: 0.1, write5m: 1.25, write1h: 2 };
|
|
129735
|
+
const generation = gptGeneration(model);
|
|
129736
|
+
if (generation === null || generation >= 5.06) return { read: /(^|[^a-z])sol([^a-z]|$)/i.test(model ?? "") ? 0.05 : 0.1, write5m: 1.25, write1h: 1.25 };
|
|
129737
|
+
return { read: 0.1, write5m: 1, write1h: 1 };
|
|
129738
|
+
}
|
|
129739
|
+
function cacheCost(billing, model, t, reportsWrites) {
|
|
129740
|
+
const price = cachePrices(billing, model);
|
|
129741
|
+
const read = t.read * price.read;
|
|
129742
|
+
if (!reportsWrites) return { read, write: t.input * price.write5m };
|
|
129743
|
+
const hour = Math.min(t.write, Math.max(0, t.write1h ?? 0));
|
|
129744
|
+
return { read, write: hour * price.write1h + (t.write - hour) * price.write5m };
|
|
129745
|
+
}
|
|
129746
|
+
|
|
129419
129747
|
// packages/agent-host/src/sessions/cache-ledger.ts
|
|
129420
129748
|
var REBUILD_SHARE = 0.5;
|
|
129421
129749
|
var REBUILD_MIN_TOKENS = 4096;
|
|
@@ -129428,12 +129756,21 @@ var TOP_SESSIONS = 3;
|
|
|
129428
129756
|
var FIVE_HOURS_MS = 5 * 60 * 6e4;
|
|
129429
129757
|
var HOUR_MS3 = 60 * 6e4;
|
|
129430
129758
|
var NOTED = ["cold_start", "new_process", "model_change", "compaction", "thread_restart", "instructions", "launch_settings"];
|
|
129759
|
+
function paidTokens(r) {
|
|
129760
|
+
return r.cacheCreationTokens ?? r.inputTokens;
|
|
129761
|
+
}
|
|
129431
129762
|
function isRebuild(r) {
|
|
129432
129763
|
const total = r.inputTokens + r.cacheReadTokens + (r.cacheCreationTokens ?? 0);
|
|
129433
|
-
const paid = r
|
|
129764
|
+
const paid = paidTokens(r);
|
|
129434
129765
|
return paid >= REBUILD_MIN_TOKENS && paid >= total * REBUILD_SHARE;
|
|
129435
129766
|
}
|
|
129436
129767
|
var CacheLedger = class {
|
|
129768
|
+
/** `billingOf`: whose price ratios a tool's model is billed by (`AgentAdapter.cacheBilling`) */
|
|
129769
|
+
constructor(now = Date.now(), billingOf = () => "anthropic") {
|
|
129770
|
+
this.billingOf = billingOf;
|
|
129771
|
+
this.startedAt = now;
|
|
129772
|
+
}
|
|
129773
|
+
billingOf;
|
|
129437
129774
|
entries = /* @__PURE__ */ new Map();
|
|
129438
129775
|
carried = /* @__PURE__ */ new Map();
|
|
129439
129776
|
/** Tools that have reported a cache write figure at least once (Claude); the others report reads only */
|
|
@@ -129442,9 +129779,6 @@ var CacheLedger = class {
|
|
|
129442
129779
|
/** What happened in a session since its conversation's last request (`note`), consumed by the next one */
|
|
129443
129780
|
notes = /* @__PURE__ */ new Map();
|
|
129444
129781
|
startedAt;
|
|
129445
|
-
constructor(now = Date.now()) {
|
|
129446
|
-
this.startedAt = now;
|
|
129447
|
-
}
|
|
129448
129782
|
/**
|
|
129449
129783
|
* Something happened in a session that would make its next request rebuild the cache: the session was created, its
|
|
129450
129784
|
* process started again, its model changed. Kept until that session's own conversation makes a request.
|
|
@@ -129480,14 +129814,22 @@ var CacheLedger = class {
|
|
|
129480
129814
|
read: b2.cacheRead,
|
|
129481
129815
|
write: b2.cacheWrite,
|
|
129482
129816
|
rebuilds: b2.rebuilds,
|
|
129817
|
+
// A rebuild is at least `REBUILD_MIN_TOKENS`: fewer tokens than that for the rebuilds counted means some were stored before
|
|
129818
|
+
// the figure was (an older build kept adding rebuilds to an hour this one had written), and the sum is not known
|
|
129819
|
+
rebuildTokens: b2.rebuildTokens < b2.rebuilds * REBUILD_MIN_TOKENS ? null : b2.rebuildTokens,
|
|
129820
|
+
// The ratios depend on the tool and the model, which a bucket has one of, so its sums are weighted at once
|
|
129821
|
+
...this.costOfBucket(b2),
|
|
129483
129822
|
causes: b2.causes
|
|
129484
129823
|
});
|
|
129485
129824
|
if (b2.cacheWrite > 0) this.reportsWrites.add(b2.tool);
|
|
129486
129825
|
this.startedAt = Math.min(this.startedAt, b2.hour);
|
|
129487
129826
|
}
|
|
129488
129827
|
}
|
|
129489
|
-
/**
|
|
129490
|
-
|
|
129828
|
+
/**
|
|
129829
|
+
* Counts one request. Returns what it counted as, or undefined for a reading that was no request. `model` is the one the session
|
|
129830
|
+
* runs when the request does not say (the Codex price of a read and a write depends on it)
|
|
129831
|
+
*/
|
|
129832
|
+
record(tool, sessionId, r, at2 = Date.now(), model) {
|
|
129491
129833
|
const write = r.cacheCreationTokens ?? 0;
|
|
129492
129834
|
if (r.inputTokens + r.cacheReadTokens + write <= 0) return void 0;
|
|
129493
129835
|
if (r.cacheCreationTokens !== void 0) this.reportsWrites.add(tool);
|
|
@@ -129497,7 +129839,8 @@ ${r.agent ?? ""}`;
|
|
|
129497
129839
|
const notes = r.agent ? void 0 : this.notes.get(sessionId);
|
|
129498
129840
|
if (!r.agent) this.notes.delete(sessionId);
|
|
129499
129841
|
const rebuild = isRebuild(r);
|
|
129500
|
-
const
|
|
129842
|
+
const cost = cacheCost(this.billingOf(tool), r.model ?? model, { input: r.inputTokens, read: r.cacheReadTokens, write, write1h: r.cacheWrite1hTokens ?? (r.cacheTtl === "1h" ? write : 0) }, r.cacheCreationTokens !== void 0);
|
|
129843
|
+
const entry = { at: at2, sessionId, input: r.inputTokens, read: r.cacheReadTokens, write, rebuild, rebuildTokens: rebuild ? paidTokens(r) : 0, readCost: cost.read, writeCost: cost.write };
|
|
129501
129844
|
if (rebuild) Object.assign(entry, causeOf(r, lane, notes, at2));
|
|
129502
129845
|
this.lanes.delete(key);
|
|
129503
129846
|
const ttlMs = r.cacheTtl ? TTL_MS[r.cacheTtl] : lane?.ttlMs ?? null;
|
|
@@ -129512,6 +129855,12 @@ ${r.agent ?? ""}`;
|
|
|
129512
129855
|
if (drop > 0) list.splice(0, drop);
|
|
129513
129856
|
return { rebuild, ...entry.cause ? { cause: entry.cause } : {} };
|
|
129514
129857
|
}
|
|
129858
|
+
/** A stored bucket's cache cost: the ratios belong to its tool and model, so its sums are weighted at once (`cacheCost`) */
|
|
129859
|
+
costOfBucket(b2) {
|
|
129860
|
+
const billing = this.billingOf(b2.tool);
|
|
129861
|
+
const { read, write } = cacheCost(billing, b2.model, { input: b2.input, read: b2.cacheRead, write: b2.cacheWrite, write1h: b2.cacheWrite1h }, billing === "anthropic" || b2.cacheWrite > 0);
|
|
129862
|
+
return { readCost: read, writeCost: write };
|
|
129863
|
+
}
|
|
129515
129864
|
/**
|
|
129516
129865
|
* The last five hours and today (the host's local midnight). `nameOf` names a session; undefined for one that is
|
|
129517
129866
|
* gone.
|
|
@@ -129551,9 +129900,13 @@ function windowOf(list, carried, id3, label, since, writes, nameOf3) {
|
|
|
129551
129900
|
cacheCreationTokens: 0,
|
|
129552
129901
|
rebuilds: 0,
|
|
129553
129902
|
rebuildCauses: {},
|
|
129903
|
+
rebuildTokens: 0,
|
|
129904
|
+
cacheReadCost: 0,
|
|
129905
|
+
cacheWriteCost: 0,
|
|
129554
129906
|
sessions: []
|
|
129555
129907
|
};
|
|
129556
129908
|
const causes = w.rebuildCauses;
|
|
129909
|
+
const unknownTokens = /* @__PURE__ */ new Set();
|
|
129557
129910
|
const bySession = /* @__PURE__ */ new Map();
|
|
129558
129911
|
for (const e of list) {
|
|
129559
129912
|
if (e.at < since) continue;
|
|
@@ -129563,16 +129916,20 @@ function windowOf(list, carried, id3, label, since, writes, nameOf3) {
|
|
|
129563
129916
|
w.cacheCreationTokens += e.write;
|
|
129564
129917
|
let s = bySession.get(e.sessionId);
|
|
129565
129918
|
if (!s) {
|
|
129566
|
-
s = { sessionId: e.sessionId, name: "", requests: 0, inputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, rebuilds: 0, rebuildCauses: {} };
|
|
129919
|
+
s = { sessionId: e.sessionId, name: "", requests: 0, inputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, rebuilds: 0, rebuildCauses: {}, rebuildTokens: 0 };
|
|
129567
129920
|
bySession.set(e.sessionId, s);
|
|
129568
129921
|
}
|
|
129569
129922
|
s.requests++;
|
|
129570
129923
|
s.inputTokens += e.input;
|
|
129571
129924
|
s.cacheReadTokens += e.read;
|
|
129572
129925
|
s.cacheCreationTokens += e.write;
|
|
129926
|
+
w.cacheReadCost = (w.cacheReadCost ?? 0) + e.readCost;
|
|
129927
|
+
w.cacheWriteCost = (w.cacheWriteCost ?? 0) + e.writeCost;
|
|
129573
129928
|
if (!e.rebuild) continue;
|
|
129574
129929
|
w.rebuilds++;
|
|
129575
129930
|
s.rebuilds++;
|
|
129931
|
+
w.rebuildTokens = (w.rebuildTokens ?? 0) + e.rebuildTokens;
|
|
129932
|
+
s.rebuildTokens = (s.rebuildTokens ?? 0) + e.rebuildTokens;
|
|
129576
129933
|
const cause2 = e.cause ?? "unknown";
|
|
129577
129934
|
causes[cause2] = (causes[cause2] ?? 0) + 1;
|
|
129578
129935
|
s.rebuildCauses[cause2] = (s.rebuildCauses[cause2] ?? 0) + 1;
|
|
@@ -129588,9 +129945,13 @@ function windowOf(list, carried, id3, label, since, writes, nameOf3) {
|
|
|
129588
129945
|
w.cacheReadTokens += c.read;
|
|
129589
129946
|
w.cacheCreationTokens += c.write;
|
|
129590
129947
|
w.rebuilds += c.rebuilds;
|
|
129948
|
+
if (c.rebuildTokens === null) unknownTokens.add(c.sessionId);
|
|
129949
|
+
else w.rebuildTokens = (w.rebuildTokens ?? 0) + c.rebuildTokens;
|
|
129950
|
+
w.cacheReadCost = (w.cacheReadCost ?? 0) + c.readCost;
|
|
129951
|
+
w.cacheWriteCost = (w.cacheWriteCost ?? 0) + c.writeCost;
|
|
129591
129952
|
let s = bySession.get(c.sessionId);
|
|
129592
129953
|
if (!s) {
|
|
129593
|
-
s = { sessionId: c.sessionId, name: "", requests: 0, inputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, rebuilds: 0, rebuildCauses: {} };
|
|
129954
|
+
s = { sessionId: c.sessionId, name: "", requests: 0, inputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, rebuilds: 0, rebuildCauses: {}, rebuildTokens: 0 };
|
|
129594
129955
|
bySession.set(c.sessionId, s);
|
|
129595
129956
|
}
|
|
129596
129957
|
s.requests += c.requests;
|
|
@@ -129598,12 +129959,15 @@ function windowOf(list, carried, id3, label, since, writes, nameOf3) {
|
|
|
129598
129959
|
s.cacheReadTokens += c.read;
|
|
129599
129960
|
s.cacheCreationTokens += c.write;
|
|
129600
129961
|
s.rebuilds += c.rebuilds;
|
|
129962
|
+
if (c.rebuildTokens !== null) s.rebuildTokens = (s.rebuildTokens ?? 0) + c.rebuildTokens;
|
|
129601
129963
|
const ofSession = s.rebuildCauses ??= {};
|
|
129602
129964
|
for (const [cause2, n] of Object.entries(c.causes)) {
|
|
129603
129965
|
causes[cause2] = (causes[cause2] ?? 0) + n;
|
|
129604
129966
|
ofSession[cause2] = (ofSession[cause2] ?? 0) + n;
|
|
129605
129967
|
}
|
|
129606
129968
|
}
|
|
129969
|
+
if (unknownTokens.size > 0) delete w.rebuildTokens;
|
|
129970
|
+
for (const id4 of unknownTokens) delete bySession.get(id4)?.rebuildTokens;
|
|
129607
129971
|
const cost = (s) => writes ? s.cacheCreationTokens : s.inputTokens;
|
|
129608
129972
|
w.sessions = [...bySession.values()].filter((s) => cost(s) > 0).sort((a, b2) => cost(b2) - cost(a)).slice(0, TOP_SESSIONS).map((s) => ({ ...s, name: nameOf3(s.sessionId) ?? "" }));
|
|
129609
129973
|
return w;
|
|
@@ -129640,7 +130004,7 @@ var UsageRecorder = class {
|
|
|
129640
130004
|
if (!b2) {
|
|
129641
130005
|
this.buckets.set(
|
|
129642
130006
|
id3,
|
|
129643
|
-
b2 = { hour, sessionId: src.sessionId, projectId, tool: src.tool, model, machine: "", requests: 0, input: 0, cacheRead: 0, cacheWrite: 0, cacheWrite5m: 0, cacheWrite1h: 0, output: 0, rebuilds: 0, causes: {} }
|
|
130007
|
+
b2 = { hour, sessionId: src.sessionId, projectId, tool: src.tool, model, machine: "", requests: 0, input: 0, cacheRead: 0, cacheWrite: 0, cacheWrite5m: 0, cacheWrite1h: 0, output: 0, rebuilds: 0, rebuildTokens: 0, causes: {} }
|
|
129644
130008
|
);
|
|
129645
130009
|
}
|
|
129646
130010
|
const write = r.cacheCreationTokens ?? 0;
|
|
@@ -129648,11 +130012,12 @@ var UsageRecorder = class {
|
|
|
129648
130012
|
b2.input += r.inputTokens;
|
|
129649
130013
|
b2.cacheRead += r.cacheReadTokens;
|
|
129650
130014
|
b2.cacheWrite += write;
|
|
129651
|
-
|
|
129652
|
-
|
|
130015
|
+
b2.cacheWrite1h += r.cacheWrite1hTokens ?? (r.cacheTtl === "1h" ? write : 0);
|
|
130016
|
+
b2.cacheWrite5m += r.cacheWrite5mTokens ?? (r.cacheTtl === "5m" ? write : 0);
|
|
129653
130017
|
b2.output += r.outputTokens ?? 0;
|
|
129654
130018
|
if (counted.rebuild) {
|
|
129655
130019
|
b2.rebuilds++;
|
|
130020
|
+
b2.rebuildTokens += paidTokens(r);
|
|
129656
130021
|
const cause2 = counted.cause ?? "unknown";
|
|
129657
130022
|
b2.causes[cause2] = (b2.causes[cause2] ?? 0) + 1;
|
|
129658
130023
|
}
|
|
@@ -131008,10 +131373,10 @@ var ReaderGate = class {
|
|
|
131008
131373
|
return this.storedMinReader() ?? 0;
|
|
131009
131374
|
}
|
|
131010
131375
|
refuseIfTooNew() {
|
|
131011
|
-
const
|
|
131012
|
-
if (!
|
|
131376
|
+
const refusal2 = this.tooNew(this.dbPath);
|
|
131377
|
+
if (!refusal2) return;
|
|
131013
131378
|
this.db.close();
|
|
131014
|
-
throw
|
|
131379
|
+
throw refusal2;
|
|
131015
131380
|
}
|
|
131016
131381
|
/**
|
|
131017
131382
|
* Why this host cannot open the store, or null. A record above this host refuses (#292). A record that is not a
|
|
@@ -133047,6 +133412,27 @@ var StepsV62 = class extends MigrationRange {
|
|
|
133047
133412
|
CREATE INDEX IF NOT EXISTS idx_usage_buckets_session ON usage_buckets(session_id);
|
|
133048
133413
|
`);
|
|
133049
133414
|
}
|
|
133415
|
+
},
|
|
133416
|
+
{
|
|
133417
|
+
to: 63,
|
|
133418
|
+
breaksOlderReaders: false,
|
|
133419
|
+
/**
|
|
133420
|
+
* What the rebuilds wrote to the cache, per hour bucket (#1492): `usage_buckets.rebuild_tokens`, the sum of the cache writes
|
|
133421
|
+
* of the requests counted in `rebuilds` (for a tool that reports no writes, the input it did not read), so the usage panel can
|
|
133422
|
+
* say "7 rebuilds · 1.0M written" after a restart too. Hours stored before it have 0 with rebuilds counted: they are read as
|
|
133423
|
+
* "not known" rather than as nothing written.
|
|
133424
|
+
*
|
|
133425
|
+
* **Expand only (#292's rule).** A defaulted column. An older host names its columns in every read and insert: it neither
|
|
133426
|
+
* reads this one nor fails on it, and the row it adds or adds to keeps its sum (the upsert never touches the column), which
|
|
133427
|
+
* leaves that hour's rebuilds with fewer tokens than rebuilds, as an hour from before. `IF` because a store from a test's
|
|
133428
|
+
* partial schema has no such table.
|
|
133429
|
+
*/
|
|
133430
|
+
run: () => {
|
|
133431
|
+
const cols = this.db.prepare(`PRAGMA table_info(usage_buckets)`).all();
|
|
133432
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "rebuild_tokens")) {
|
|
133433
|
+
this.db.exec(`ALTER TABLE usage_buckets ADD COLUMN rebuild_tokens INTEGER NOT NULL DEFAULT 0`);
|
|
133434
|
+
}
|
|
133435
|
+
}
|
|
133050
133436
|
}
|
|
133051
133437
|
];
|
|
133052
133438
|
}
|
|
@@ -137148,6 +137534,18 @@ var SessionManager = class {
|
|
|
137148
137534
|
send: (id3, text2) => this.events.sendCompact(id3, text2),
|
|
137149
137535
|
notice: (id3, text2) => this.onEvent({ type: "notice", sessionId: id3, level: "info", from: "Centralu", label: "compaction", text: text2 })
|
|
137150
137536
|
});
|
|
137537
|
+
this.contextNudges = new ContextNudges({
|
|
137538
|
+
session: (id3) => this.meta.get(id3),
|
|
137539
|
+
window: (m) => this.modelWindows.of(m),
|
|
137540
|
+
setting: () => autoCompactOf(this.uiPreferences()),
|
|
137541
|
+
// The two seats that have the tool (tool-seats.ts): an ordinary session and the orchestrator
|
|
137542
|
+
hasCompactTool: (id3) => {
|
|
137543
|
+
const profile = this.toolProfileOf(id3);
|
|
137544
|
+
return profile === "reader" || profile === "orchestrator";
|
|
137545
|
+
},
|
|
137546
|
+
nudge: (id3, text2) => this.handles.get(id3)?.nudge?.(text2) ?? false,
|
|
137547
|
+
notice: (id3, text2) => this.onEvent({ type: "notice", sessionId: id3, level: "info", from: "Centralu", label: "compaction", text: text2 })
|
|
137548
|
+
});
|
|
137151
137549
|
this.orchestratorSlot = new OrchestratorSlot(this.parts());
|
|
137152
137550
|
const rawEmit = this.emit;
|
|
137153
137551
|
this.emit = (full) => {
|
|
@@ -137218,7 +137616,7 @@ var SessionManager = class {
|
|
|
137218
137616
|
*/
|
|
137219
137617
|
running = /* @__PURE__ */ new Map();
|
|
137220
137618
|
/** Every model request's cache figures over the last day, per tool (docs/agent-host.md §6.1); read by `usageFor` */
|
|
137221
|
-
cacheLedger = new CacheLedger();
|
|
137619
|
+
cacheLedger = new CacheLedger(Date.now(), (tool) => this.adapters.get(tool)?.cacheBilling ?? "anthropic");
|
|
137222
137620
|
/** The same requests summed per hour and kept, with the limit windows' readings (docs/agent-host.md §6.2) */
|
|
137223
137621
|
usageRecorder;
|
|
137224
137622
|
/** Each model's context window as the tools reported it, for where agents compact (#1376, auto-compact.ts) */
|
|
@@ -137281,6 +137679,8 @@ var SessionManager = class {
|
|
|
137281
137679
|
updatesAsked;
|
|
137282
137680
|
/** Compacting at a turn's end: the agent's `compact_self` and a session at rest over the lower share (#1451, self-compaction.ts) */
|
|
137283
137681
|
selfCompaction;
|
|
137682
|
+
/** The quiet notices that tell a growing session to call `compact_self` (#1492, context-nudges.ts) */
|
|
137683
|
+
contextNudges;
|
|
137284
137684
|
/** Whether a session of one project may reach another, and the consents remembered for it (project-consents.ts) */
|
|
137285
137685
|
consents;
|
|
137286
137686
|
/** The screen's preferences and workspace layout (ui-preferences.ts) */
|
|
@@ -137668,6 +138068,7 @@ var SessionManager = class {
|
|
|
137668
138068
|
}
|
|
137669
138069
|
stopSelfCompaction() {
|
|
137670
138070
|
this.selfCompaction.stop();
|
|
138071
|
+
this.contextNudges.stop();
|
|
137671
138072
|
}
|
|
137672
138073
|
/** Starts compacting the sessions at rest over the lower share (#1451); the shutdown plan stops it before the sessions are let go of */
|
|
137673
138074
|
startSelfCompaction() {
|
|
@@ -138531,6 +138932,11 @@ var SessionManager = class {
|
|
|
138531
138932
|
} catch (err) {
|
|
138532
138933
|
console.error(`[compact] ${e.sessionId?.slice(0, 8) ?? "?"}: ${errorMessage(err)}`);
|
|
138533
138934
|
}
|
|
138935
|
+
try {
|
|
138936
|
+
self2.contextNudges.observe(e);
|
|
138937
|
+
} catch (err) {
|
|
138938
|
+
console.error(`[compact] ${e.sessionId?.slice(0, 8) ?? "?"}: ${errorMessage(err)}`);
|
|
138939
|
+
}
|
|
138534
138940
|
},
|
|
138535
138941
|
listAgents: (...a) => self2.delegationFlow.listAgents(...a),
|
|
138536
138942
|
get agentRuns() {
|
|
@@ -138590,6 +138996,10 @@ var SessionManager = class {
|
|
|
138590
138996
|
const tool = self2.meta.get(sessionId)?.tool;
|
|
138591
138997
|
return tool !== void 0 && self2.adapters.get(tool)?.capabilities.midTurnInput === true;
|
|
138592
138998
|
},
|
|
138999
|
+
contextAtRequest: (sessionId) => {
|
|
139000
|
+
const tool = self2.meta.get(sessionId)?.tool;
|
|
139001
|
+
return tool !== void 0 && self2.adapters.get(tool)?.contextAtRequest === true;
|
|
139002
|
+
},
|
|
138593
139003
|
get children() {
|
|
138594
139004
|
return self2.children;
|
|
138595
139005
|
},
|
|
@@ -139176,6 +139586,7 @@ var SessionManager = class {
|
|
|
139176
139586
|
async disposeAll() {
|
|
139177
139587
|
this.usageRecorder.stop();
|
|
139178
139588
|
this.selfCompaction.stop();
|
|
139589
|
+
this.contextNudges.stop();
|
|
139179
139590
|
this.sweepingHandles = true;
|
|
139180
139591
|
try {
|
|
139181
139592
|
await this.disposeEverything();
|
|
@@ -139207,6 +139618,7 @@ var SessionManager = class {
|
|
|
139207
139618
|
async detachAll() {
|
|
139208
139619
|
this.usageRecorder.stop();
|
|
139209
139620
|
this.selfCompaction.stop();
|
|
139621
|
+
this.contextNudges.stop();
|
|
139210
139622
|
this.handingOver = true;
|
|
139211
139623
|
this.leaveHost("Centralu is restarting");
|
|
139212
139624
|
await Promise.allSettled([...[...this.handles.values()].map((h) => h.detach ? h.detach() : h.dispose()), ...this.merges.mergeChecks]);
|
|
@@ -139517,17 +139929,17 @@ var MailService = class {
|
|
|
139517
139929
|
}
|
|
139518
139930
|
}
|
|
139519
139931
|
const reachable = [];
|
|
139520
|
-
let
|
|
139932
|
+
let refusal2;
|
|
139521
139933
|
for (const to of this.recipients(thread.id, caller)) {
|
|
139522
139934
|
try {
|
|
139523
139935
|
this.mayWriteOrRefuse(caller, to, thread);
|
|
139524
139936
|
reachable.push(to);
|
|
139525
139937
|
} catch (e) {
|
|
139526
139938
|
if (!(e instanceof Error) || refusalReason(e) === void 0) throw e;
|
|
139527
|
-
|
|
139939
|
+
refusal2 ??= e;
|
|
139528
139940
|
}
|
|
139529
139941
|
}
|
|
139530
|
-
if (
|
|
139942
|
+
if (refusal2 !== void 0 && reachable.length === 0) throw refusal2;
|
|
139531
139943
|
if (sender.kind === "person") this.records.putParticipantIfAbsent(thread.id, PERSON, "member", now);
|
|
139532
139944
|
const message = this.insertPost(thread.id, caller, input.kind, body, {}, input, now, input.replyTo, reachable);
|
|
139533
139945
|
let after = thread;
|
|
@@ -150187,7 +150599,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
|
|
|
150187
150599
|
console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
|
|
150188
150600
|
process.exit(1);
|
|
150189
150601
|
}) : launch.token;
|
|
150190
|
-
var BUILD = true ? "
|
|
150602
|
+
var BUILD = true ? "50607f5" : "dev";
|
|
150191
150603
|
var { values } = parseArgs({
|
|
150192
150604
|
options: {
|
|
150193
150605
|
port: { type: "string", default: "5175" },
|
package/package.json
CHANGED