@cabane/companion 0.6.143 → 0.6.144

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.
Files changed (3) hide show
  1. package/dist/cli.js +111 -21
  2. package/dist/runtime.js +110 -20
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1816,14 +1816,11 @@ var init_turn_event = __esm({
1816
1816
  // claude-code/codex it's the runtime's CUMULATIVE total summed across every model
1817
1817
  // request in the agentic turn, so it grows with the tool-call count and is NOT
1818
1818
  // "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
1819
- // read: the input the model saw on its FINAL request of the turn (uncached +
1820
- // cache, since cached tokens still occupy the window) — the number the composer
1821
- // gauge wants. `contextWindow` is the model's true window in tokens when the
1822
- // runtime reports it (claude-code's SDK does, per model) — a real denominator so
1823
- // the gauge can show a fraction. Both optional: a runtime that can't source a
1824
- // clean final-request figure (codex's cumulative-only usage) omits `contextTokens`
1825
- // and the gauge falls back to the raw count; `contextWindow` falls back to the
1826
- // model catalog.
1819
+ // read and `contextWindow` the window it is measured against, both as the
1820
+ // harness reported them (claude-code's `getContextUsage`, or its final
1821
+ // request's input and `modelUsage` window when that call gives nothing). A
1822
+ // runtime that can't source them (codex's cumulative-only usage) omits both,
1823
+ // and no surface shows a reading for that turn.
1827
1824
  //
1828
1825
  // CT601: two more optional carry-homes on the terminal result, alongside
1829
1826
  // `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
@@ -1846,7 +1843,25 @@ var init_turn_event = __esm({
1846
1843
  cacheReadTokens: z6.number().optional(),
1847
1844
  cacheCreationTokens: z6.number().optional(),
1848
1845
  contextTokens: z6.number().optional(),
1849
- contextWindow: z6.number().optional()
1846
+ contextWindow: z6.number().optional(),
1847
+ // CT1751: the rest of the harness's own context reading, when it gave
1848
+ // one: where it will compact, its `/context` breakdown, and every
1849
+ // compaction it reported during the turn. Claude Code only.
1850
+ contextCompactAt: z6.number().optional(),
1851
+ contextCategories: z6.array(
1852
+ z6.object({
1853
+ name: z6.string(),
1854
+ tokens: z6.number(),
1855
+ kind: z6.enum(["used", "free", "buffer", "deferred"])
1856
+ })
1857
+ ).optional(),
1858
+ contextCompactions: z6.array(
1859
+ z6.object({
1860
+ trigger: z6.enum(["manual", "auto"]),
1861
+ preTokens: z6.number(),
1862
+ postTokens: z6.number().optional()
1863
+ })
1864
+ ).optional()
1850
1865
  }).optional(),
1851
1866
  resolvedModel: z6.string().optional(),
1852
1867
  resolvedConfig: z6.object({
@@ -2902,6 +2917,7 @@ async function* decodeSdkStream(iter, ctx) {
2902
2917
  let resultReason;
2903
2918
  let sawResult = false;
2904
2919
  let usage;
2920
+ const compactions = [];
2905
2921
  let lastRequestContextTokens;
2906
2922
  let resolvedModel;
2907
2923
  const signals = { sawRejectedLimit: false };
@@ -2927,6 +2943,9 @@ async function* decodeSdkStream(iter, ctx) {
2927
2943
  ...ctx.degraded ? { degraded: true } : {}
2928
2944
  };
2929
2945
  }
2946
+ } else if (msg.type === "system" && msg.subtype === "compact_boundary") {
2947
+ const compaction = readCompaction(msg);
2948
+ if (compaction) compactions.push(compaction);
2930
2949
  } else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
2931
2950
  if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
2932
2951
  const failure = classifyPreInitFrame(msg, signals);
@@ -2971,6 +2990,9 @@ async function* decodeSdkStream(iter, ctx) {
2971
2990
  usage.contextTokens = lastRequestContextTokens;
2972
2991
  const window = readContextWindow(msg, resolvedModel);
2973
2992
  if (window !== void 0) usage.contextWindow = window;
2993
+ if (compactions.length > 0) usage.contextCompactions = compactions;
2994
+ const reading = await readHarnessContext(ctx.readContextUsage);
2995
+ if (reading) Object.assign(usage, reading);
2974
2996
  }
2975
2997
  const isError = msg.is_error === true;
2976
2998
  if (msg.subtype === "success" && !isError) {
@@ -3121,6 +3143,56 @@ function readRequestContextTokens(msg) {
3121
3143
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3122
3144
  return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
3123
3145
  }
3146
+ async function readHarnessContext(read) {
3147
+ if (!read) return void 0;
3148
+ let timer;
3149
+ const timeout = new Promise((resolve2) => {
3150
+ timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
3151
+ });
3152
+ try {
3153
+ return parseContextUsage(await Promise.race([read(), timeout]));
3154
+ } catch {
3155
+ return void 0;
3156
+ } finally {
3157
+ clearTimeout(timer);
3158
+ }
3159
+ }
3160
+ function parseContextUsage(response) {
3161
+ const r = response;
3162
+ if (!r || typeof r !== "object") return void 0;
3163
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
3164
+ const total = count(r.totalTokens);
3165
+ const window = count(r.maxTokens);
3166
+ if (total === void 0 || window === void 0 || window === 0) return void 0;
3167
+ const categories = [];
3168
+ for (const c of Array.isArray(r.categories) ? r.categories : []) {
3169
+ const row = c;
3170
+ const tokens = count(row?.tokens);
3171
+ if (typeof row?.name === "string" && tokens !== void 0 && typeof row.kind === "string" && CATEGORY_KINDS.has(row.kind)) {
3172
+ categories.push({ name: row.name, tokens, kind: row.kind });
3173
+ }
3174
+ }
3175
+ const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
3176
+ return {
3177
+ contextTokens: total,
3178
+ contextWindow: window,
3179
+ ...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
3180
+ ...categories.length > 0 ? { contextCategories: categories } : {}
3181
+ };
3182
+ }
3183
+ function readCompaction(msg) {
3184
+ const meta = msg.compact_metadata;
3185
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
3186
+ const preTokens = count(meta?.pre_tokens);
3187
+ if (!meta || preTokens === void 0) return void 0;
3188
+ if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
3189
+ const postTokens = count(meta.post_tokens);
3190
+ return {
3191
+ trigger: meta.trigger,
3192
+ preTokens,
3193
+ ...postTokens !== void 0 ? { postTokens } : {}
3194
+ };
3195
+ }
3124
3196
  function readContextWindow(msg, resolvedModel) {
3125
3197
  const modelUsage = msg.modelUsage;
3126
3198
  if (!modelUsage || typeof modelUsage !== "object") return void 0;
@@ -3136,7 +3208,7 @@ function readContextWindow(msg, resolvedModel) {
3136
3208
  }
3137
3209
  return max;
3138
3210
  }
3139
- var HARNESS_PLACEHOLDERS;
3211
+ var HARNESS_PLACEHOLDERS, CONTEXT_READING_TIMEOUT_MS, CATEGORY_KINDS;
3140
3212
  var init_loop = __esm({
3141
3213
  "packages/agent-runtime/src/claude-code/loop.ts"() {
3142
3214
  "use strict";
@@ -3144,11 +3216,13 @@ var init_loop = __esm({
3144
3216
  init_failure();
3145
3217
  init_session();
3146
3218
  HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
3219
+ CONTEXT_READING_TIMEOUT_MS = 2e3;
3220
+ CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
3147
3221
  }
3148
3222
  });
3149
3223
 
3150
3224
  // packages/agent-runtime/src/claude-code/prompt-input.ts
3151
- function buildQueryPrompt(req) {
3225
+ function buildQueryPrompt(req, openUntil) {
3152
3226
  const content = req.content;
3153
3227
  return (async function* () {
3154
3228
  yield {
@@ -3157,6 +3231,7 @@ function buildQueryPrompt(req) {
3157
3231
  message: { role: "user", content },
3158
3232
  parent_tool_use_id: null
3159
3233
  };
3234
+ if (openUntil) await openUntil;
3160
3235
  })();
3161
3236
  }
3162
3237
  var init_prompt_input = __esm({
@@ -3176,19 +3251,34 @@ function attachAbortController(options, signal) {
3176
3251
  else signal.addEventListener("abort", () => sdkAbort.abort(), { once: true });
3177
3252
  options.abortController = sdkAbort;
3178
3253
  }
3179
- function sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
3254
+ async function* sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
3180
3255
  attachAbortController(built.options, signal);
3181
- const prompt = buildQueryPrompt(req);
3182
- const iter = {
3183
- [Symbol.asyncIterator]: () => queryFn({ prompt, options: built.options })[Symbol.asyncIterator]()
3256
+ let release = () => {
3184
3257
  };
3185
- return decodeSdkStream(iter, {
3186
- signal,
3187
- cwd: req.local.cwd,
3188
- resumedSessionId: built.resume,
3189
- degraded,
3190
- ...startEvidence ? { startEvidence } : {}
3258
+ const held = new Promise((resolve2) => {
3259
+ release = resolve2;
3191
3260
  });
3261
+ const prompt = buildQueryPrompt(req, held);
3262
+ let active;
3263
+ const iter = {
3264
+ [Symbol.asyncIterator]: () => {
3265
+ const q = queryFn({ prompt, options: built.options });
3266
+ active = q;
3267
+ return q[Symbol.asyncIterator]();
3268
+ }
3269
+ };
3270
+ try {
3271
+ yield* decodeSdkStream(iter, {
3272
+ signal,
3273
+ readContextUsage: async () => typeof active?.getContextUsage === "function" ? active.getContextUsage({ detail: "summary" }) : void 0,
3274
+ cwd: req.local.cwd,
3275
+ resumedSessionId: built.resume,
3276
+ degraded,
3277
+ ...startEvidence ? { startEvidence } : {}
3278
+ });
3279
+ } finally {
3280
+ release();
3281
+ }
3192
3282
  }
3193
3283
  async function freshRequest(req, reason, hooks) {
3194
3284
  const recomposed = hooks?.fresh ? await hooks.fresh(reason) : null;
package/dist/runtime.js CHANGED
@@ -3309,14 +3309,11 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3309
3309
  // claude-code/codex it's the runtime's CUMULATIVE total summed across every model
3310
3310
  // request in the agentic turn, so it grows with the tool-call count and is NOT
3311
3311
  // "how full is the window." `contextTokens` is the distinct CONTEXT-OCCUPANCY
3312
- // read: the input the model saw on its FINAL request of the turn (uncached +
3313
- // cache, since cached tokens still occupy the window) — the number the composer
3314
- // gauge wants. `contextWindow` is the model's true window in tokens when the
3315
- // runtime reports it (claude-code's SDK does, per model) — a real denominator so
3316
- // the gauge can show a fraction. Both optional: a runtime that can't source a
3317
- // clean final-request figure (codex's cumulative-only usage) omits `contextTokens`
3318
- // and the gauge falls back to the raw count; `contextWindow` falls back to the
3319
- // model catalog.
3312
+ // read and `contextWindow` the window it is measured against, both as the
3313
+ // harness reported them (claude-code's `getContextUsage`, or its final
3314
+ // request's input and `modelUsage` window when that call gives nothing). A
3315
+ // runtime that can't source them (codex's cumulative-only usage) omits both,
3316
+ // and no surface shows a reading for that turn.
3320
3317
  //
3321
3318
  // CT601: two more optional carry-homes on the terminal result, alongside
3322
3319
  // `usage`. `resolvedModel` is the CONCRETE model the runtime actually ran —
@@ -3339,7 +3336,25 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3339
3336
  cacheReadTokens: z6.number().optional(),
3340
3337
  cacheCreationTokens: z6.number().optional(),
3341
3338
  contextTokens: z6.number().optional(),
3342
- contextWindow: z6.number().optional()
3339
+ contextWindow: z6.number().optional(),
3340
+ // CT1751: the rest of the harness's own context reading, when it gave
3341
+ // one: where it will compact, its `/context` breakdown, and every
3342
+ // compaction it reported during the turn. Claude Code only.
3343
+ contextCompactAt: z6.number().optional(),
3344
+ contextCategories: z6.array(
3345
+ z6.object({
3346
+ name: z6.string(),
3347
+ tokens: z6.number(),
3348
+ kind: z6.enum(["used", "free", "buffer", "deferred"])
3349
+ })
3350
+ ).optional(),
3351
+ contextCompactions: z6.array(
3352
+ z6.object({
3353
+ trigger: z6.enum(["manual", "auto"]),
3354
+ preTokens: z6.number(),
3355
+ postTokens: z6.number().optional()
3356
+ })
3357
+ ).optional()
3343
3358
  }).optional(),
3344
3359
  resolvedModel: z6.string().optional(),
3345
3360
  resolvedConfig: z6.object({
@@ -4294,6 +4309,7 @@ async function* decodeSdkStream(iter, ctx) {
4294
4309
  let resultReason;
4295
4310
  let sawResult = false;
4296
4311
  let usage;
4312
+ const compactions = [];
4297
4313
  let lastRequestContextTokens;
4298
4314
  let resolvedModel;
4299
4315
  const signals = { sawRejectedLimit: false };
@@ -4319,6 +4335,9 @@ async function* decodeSdkStream(iter, ctx) {
4319
4335
  ...ctx.degraded ? { degraded: true } : {}
4320
4336
  };
4321
4337
  }
4338
+ } else if (msg.type === "system" && msg.subtype === "compact_boundary") {
4339
+ const compaction = readCompaction(msg);
4340
+ if (compaction) compactions.push(compaction);
4322
4341
  } else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
4323
4342
  if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
4324
4343
  const failure = classifyPreInitFrame(msg, signals);
@@ -4363,6 +4382,9 @@ async function* decodeSdkStream(iter, ctx) {
4363
4382
  usage.contextTokens = lastRequestContextTokens;
4364
4383
  const window = readContextWindow(msg, resolvedModel);
4365
4384
  if (window !== void 0) usage.contextWindow = window;
4385
+ if (compactions.length > 0) usage.contextCompactions = compactions;
4386
+ const reading = await readHarnessContext(ctx.readContextUsage);
4387
+ if (reading) Object.assign(usage, reading);
4366
4388
  }
4367
4389
  const isError = msg.is_error === true;
4368
4390
  if (msg.subtype === "success" && !isError) {
@@ -4514,6 +4536,58 @@ function readRequestContextTokens(msg) {
4514
4536
  const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
4515
4537
  return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
4516
4538
  }
4539
+ var CONTEXT_READING_TIMEOUT_MS = 2e3;
4540
+ async function readHarnessContext(read) {
4541
+ if (!read) return void 0;
4542
+ let timer;
4543
+ const timeout = new Promise((resolve2) => {
4544
+ timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
4545
+ });
4546
+ try {
4547
+ return parseContextUsage(await Promise.race([read(), timeout]));
4548
+ } catch {
4549
+ return void 0;
4550
+ } finally {
4551
+ clearTimeout(timer);
4552
+ }
4553
+ }
4554
+ var CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
4555
+ function parseContextUsage(response) {
4556
+ const r = response;
4557
+ if (!r || typeof r !== "object") return void 0;
4558
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
4559
+ const total = count(r.totalTokens);
4560
+ const window = count(r.maxTokens);
4561
+ if (total === void 0 || window === void 0 || window === 0) return void 0;
4562
+ const categories = [];
4563
+ for (const c of Array.isArray(r.categories) ? r.categories : []) {
4564
+ const row = c;
4565
+ const tokens = count(row?.tokens);
4566
+ if (typeof row?.name === "string" && tokens !== void 0 && typeof row.kind === "string" && CATEGORY_KINDS.has(row.kind)) {
4567
+ categories.push({ name: row.name, tokens, kind: row.kind });
4568
+ }
4569
+ }
4570
+ const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
4571
+ return {
4572
+ contextTokens: total,
4573
+ contextWindow: window,
4574
+ ...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
4575
+ ...categories.length > 0 ? { contextCategories: categories } : {}
4576
+ };
4577
+ }
4578
+ function readCompaction(msg) {
4579
+ const meta = msg.compact_metadata;
4580
+ const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
4581
+ const preTokens = count(meta?.pre_tokens);
4582
+ if (!meta || preTokens === void 0) return void 0;
4583
+ if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
4584
+ const postTokens = count(meta.post_tokens);
4585
+ return {
4586
+ trigger: meta.trigger,
4587
+ preTokens,
4588
+ ...postTokens !== void 0 ? { postTokens } : {}
4589
+ };
4590
+ }
4517
4591
  function readContextWindow(msg, resolvedModel) {
4518
4592
  const modelUsage = msg.modelUsage;
4519
4593
  if (!modelUsage || typeof modelUsage !== "object") return void 0;
@@ -4531,7 +4605,7 @@ function readContextWindow(msg, resolvedModel) {
4531
4605
  }
4532
4606
 
4533
4607
  // packages/agent-runtime/src/claude-code/prompt-input.ts
4534
- function buildQueryPrompt(req) {
4608
+ function buildQueryPrompt(req, openUntil) {
4535
4609
  const content = req.content;
4536
4610
  return (async function* () {
4537
4611
  yield {
@@ -4540,6 +4614,7 @@ function buildQueryPrompt(req) {
4540
4614
  message: { role: "user", content },
4541
4615
  parent_tool_use_id: null
4542
4616
  };
4617
+ if (openUntil) await openUntil;
4543
4618
  })();
4544
4619
  }
4545
4620
 
@@ -4551,19 +4626,34 @@ function attachAbortController(options, signal) {
4551
4626
  else signal.addEventListener("abort", () => sdkAbort.abort(), { once: true });
4552
4627
  options.abortController = sdkAbort;
4553
4628
  }
4554
- function sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
4629
+ async function* sdkEvents(queryFn, req, signal, built, degraded, startEvidence) {
4555
4630
  attachAbortController(built.options, signal);
4556
- const prompt = buildQueryPrompt(req);
4557
- const iter = {
4558
- [Symbol.asyncIterator]: () => queryFn({ prompt, options: built.options })[Symbol.asyncIterator]()
4631
+ let release = () => {
4559
4632
  };
4560
- return decodeSdkStream(iter, {
4561
- signal,
4562
- cwd: req.local.cwd,
4563
- resumedSessionId: built.resume,
4564
- degraded,
4565
- ...startEvidence ? { startEvidence } : {}
4633
+ const held = new Promise((resolve2) => {
4634
+ release = resolve2;
4566
4635
  });
4636
+ const prompt = buildQueryPrompt(req, held);
4637
+ let active;
4638
+ const iter = {
4639
+ [Symbol.asyncIterator]: () => {
4640
+ const q = queryFn({ prompt, options: built.options });
4641
+ active = q;
4642
+ return q[Symbol.asyncIterator]();
4643
+ }
4644
+ };
4645
+ try {
4646
+ yield* decodeSdkStream(iter, {
4647
+ signal,
4648
+ readContextUsage: async () => typeof active?.getContextUsage === "function" ? active.getContextUsage({ detail: "summary" }) : void 0,
4649
+ cwd: req.local.cwd,
4650
+ resumedSessionId: built.resume,
4651
+ degraded,
4652
+ ...startEvidence ? { startEvidence } : {}
4653
+ });
4654
+ } finally {
4655
+ release();
4656
+ }
4567
4657
  }
4568
4658
  async function freshRequest(req, reason, hooks) {
4569
4659
  const recomposed = hooks?.fresh ? await hooks.fresh(reason) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.143",
3
+ "version": "0.6.144",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",