claudish 7.31.0 → 7.32.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.
Files changed (2) hide show
  1. package/dist/index.js +267 -52
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -651,7 +651,7 @@ var init_onepassword_config = __esm(() => {
651
651
  });
652
652
 
653
653
  // src/version.ts
654
- var VERSION = "7.31.0";
654
+ var VERSION = "7.32.0";
655
655
 
656
656
  // src/logger.ts
657
657
  var exports_logger = {};
@@ -36651,6 +36651,7 @@ data: ${JSON.stringify(d)}
36651
36651
  output_tokens: state.usage?.completion_tokens || 0
36652
36652
  }
36653
36653
  });
36654
+ behavior?.onTurnEnd?.();
36654
36655
  send("message_stop", { type: "message_stop" });
36655
36656
  }
36656
36657
  if (onTokenUpdate) {
@@ -36717,6 +36718,7 @@ data: ${JSON.stringify(d)}
36717
36718
  });
36718
36719
  }
36719
36720
  if (delta.reasoning_content) {
36721
+ behavior?.onAssistantText?.(delta.reasoning_content, "reasoning");
36720
36722
  state.lastActivity = Date.now();
36721
36723
  if (!state.reasoningStarted) {
36722
36724
  state.reasoningIdx = state.curIdx++;
@@ -36734,6 +36736,8 @@ data: ${JSON.stringify(d)}
36734
36736
  });
36735
36737
  }
36736
36738
  const txt = delta.content || "";
36739
+ if (txt)
36740
+ behavior?.onAssistantText?.(txt, "text");
36737
36741
  log(`[Streaming] Text chunk: "${txt.substring(0, 30).replace(/\n/g, "\\n")}" (${txt.length} chars)`);
36738
36742
  if (txt) {
36739
36743
  state.lastActivity = Date.now();
@@ -38191,24 +38195,44 @@ function parseBehaviorConfig(raw2) {
38191
38195
  }
38192
38196
  return result.data;
38193
38197
  }
38194
- function resolveSeverity(ruleId, defaultSeverity, config2) {
38198
+ function resolveSeverity(ruleId, defaultSeverity, config2, modelId) {
38195
38199
  const rules = config2.rules;
38196
38200
  if (!rules)
38197
38201
  return defaultSeverity;
38198
- const exact = rules[ruleId];
38199
- if (exact)
38200
- return exact;
38202
+ if (modelId) {
38203
+ const scoped = bestMatch(rules, ruleId, modelId);
38204
+ if (scoped)
38205
+ return scoped;
38206
+ }
38207
+ return bestMatch(rules, ruleId, undefined) ?? defaultSeverity;
38208
+ }
38209
+ function bestMatch(rules, ruleId, modelId) {
38201
38210
  let best = null;
38202
- for (const [pattern, severity] of Object.entries(rules)) {
38203
- if (!pattern.includes("*"))
38211
+ for (const [key, severity] of Object.entries(rules)) {
38212
+ const { model: keyModel, rule: keyRule } = parseRuleKey(key);
38213
+ if (!scopeMatches(keyModel, modelId))
38204
38214
  continue;
38205
- if (!globMatches(pattern, ruleId))
38215
+ if (!globMatches(keyRule, ruleId))
38206
38216
  continue;
38207
- const len = pattern.replace(/\*/g, "").length;
38208
- if (!best || len > best.len)
38209
- best = { len, severity };
38217
+ const score = specificity(keyRule);
38218
+ if (!best || score > best.score)
38219
+ best = { score, severity };
38210
38220
  }
38211
- return best ? best.severity : defaultSeverity;
38221
+ return best ? best.severity : null;
38222
+ }
38223
+ function parseRuleKey(key) {
38224
+ const sep = key.indexOf(":");
38225
+ if (sep <= 0 || key.startsWith("hook:"))
38226
+ return { rule: key };
38227
+ return { model: key.slice(0, sep), rule: key.slice(sep + 1) };
38228
+ }
38229
+ function scopeMatches(keyModel, modelId) {
38230
+ if (modelId === undefined)
38231
+ return keyModel === undefined;
38232
+ return keyModel !== undefined && globMatches(keyModel, modelId);
38233
+ }
38234
+ function specificity(pattern) {
38235
+ return pattern.includes("*") ? pattern.replace(/\*/g, "").length : Number.MAX_SAFE_INTEGER;
38212
38236
  }
38213
38237
  function globMatches(pattern, value) {
38214
38238
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
@@ -38235,6 +38259,41 @@ var init_config = __esm(() => {
38235
38259
  });
38236
38260
 
38237
38261
  // src/behavior/harness.ts
38262
+ function extractAvailableSkills(systemText) {
38263
+ if (!systemText)
38264
+ return [];
38265
+ const start = SKILL_SECTION.exec(systemText);
38266
+ if (!start)
38267
+ return [];
38268
+ const out = [];
38269
+ const body = systemText.slice(start.index + start[0].length);
38270
+ for (const line of body.split(`
38271
+ `)) {
38272
+ const trimmed = line.trim();
38273
+ if (!trimmed)
38274
+ continue;
38275
+ const m = SKILL_LINE.exec(trimmed);
38276
+ if (!m) {
38277
+ if (out.length > 0)
38278
+ break;
38279
+ continue;
38280
+ }
38281
+ out.push({ name: m[1], description: m[2].trim() });
38282
+ }
38283
+ return out;
38284
+ }
38285
+ function extractSessionId(claudeRequest) {
38286
+ const raw2 = claudeRequest?.metadata?.user_id;
38287
+ if (typeof raw2 !== "string")
38288
+ return;
38289
+ try {
38290
+ const parsed = JSON.parse(raw2);
38291
+ const id = parsed?.session_id;
38292
+ return typeof id === "string" && id.length > 0 ? id : undefined;
38293
+ } catch {
38294
+ return;
38295
+ }
38296
+ }
38238
38297
  function textOf(value) {
38239
38298
  if (!value)
38240
38299
  return "";
@@ -38286,7 +38345,7 @@ function detectHarnessFacts(claudeRequest) {
38286
38345
  }
38287
38346
  return facts;
38288
38347
  }
38289
- var PLAN_PATH_PATTERNS, PLAN_MODE_HINT;
38348
+ var PLAN_PATH_PATTERNS, PLAN_MODE_HINT, SKILL_SECTION, SKILL_LINE;
38290
38349
  var init_harness = __esm(() => {
38291
38350
  PLAN_PATH_PATTERNS = [
38292
38351
  /You should create your plan at\s+(\S+?\.md)/,
@@ -38294,10 +38353,12 @@ var init_harness = __esm(() => {
38294
38353
  /Read-only except plan file\s*\(([^)]+\.md)\)/
38295
38354
  ];
38296
38355
  PLAN_MODE_HINT = /plan file|create your plan at|Plan mode is active|Plan mode still active/i;
38356
+ SKILL_SECTION = /The following skills are available[^\n]*\n/;
38357
+ SKILL_LINE = /^-\s+([a-z0-9][a-z0-9:_-]*):\s*(.+)$/i;
38297
38358
  });
38298
38359
 
38299
38360
  // src/behavior/journal.ts
38300
- import { appendFile as appendFile2, mkdir, stat } from "fs/promises";
38361
+ import { appendFile as appendFile2, mkdir, readFile, rename, stat, writeFile } from "fs/promises";
38301
38362
  import { homedir as homedir18 } from "os";
38302
38363
  import { dirname as dirname6, join as join18 } from "path";
38303
38364
  function classifyPath(observed, expected) {
@@ -38313,28 +38374,46 @@ function classifyPath(observed, expected) {
38313
38374
  function journalPath() {
38314
38375
  return join18(homedir18(), ".claudish", "behavior-journal.jsonl");
38315
38376
  }
38377
+ async function prune(path) {
38378
+ const content = await readFile(path, "utf8");
38379
+ const lines = content.split(`
38380
+ `).filter(Boolean);
38381
+ let kept = 0;
38382
+ let firstKept = lines.length;
38383
+ for (let i = lines.length - 1;i >= 0; i--) {
38384
+ const cost = Buffer.byteLength(lines[i]) + 1;
38385
+ if (kept + cost > PRUNE_TO_BYTES)
38386
+ break;
38387
+ kept += cost;
38388
+ firstKept = i;
38389
+ }
38390
+ const survivors = lines.slice(firstKept);
38391
+ const tmp = `${path}.pruning`;
38392
+ await writeFile(tmp, survivors.length ? `${survivors.join(`
38393
+ `)}
38394
+ ` : "");
38395
+ await rename(tmp, path);
38396
+ log(`[behavior:journal] pruned ${lines.length - survivors.length} of ${lines.length} entries ` + `to stay under ${Math.round(MAX_JOURNAL_BYTES / 1e6)}MB`);
38397
+ }
38316
38398
  async function recordDecision(entry, path = journalPath()) {
38317
38399
  try {
38318
38400
  const size = await stat(path).then((s) => s.size, () => 0);
38319
- if (size > MAX_JOURNAL_BYTES) {
38320
- if (!capWarned) {
38321
- capWarned = true;
38322
- log(`[behavior:journal] ${path} exceeded ${Math.round(MAX_JOURNAL_BYTES / 1e6)}MB \u2014 ` + "no longer recording. Archive or delete it to resume.");
38323
- }
38324
- return;
38325
- }
38326
38401
  if (size === 0)
38327
38402
  await mkdir(dirname6(path), { recursive: true }).catch(() => {});
38403
+ if (size > MAX_JOURNAL_BYTES) {
38404
+ await prune(path).catch((err) => log(`[behavior:journal] prune failed: ${err}`));
38405
+ }
38328
38406
  await appendFile2(path, `${JSON.stringify(entry)}
38329
38407
  `);
38330
38408
  } catch (err) {
38331
38409
  log(`[behavior:journal] could not record: ${err}`);
38332
38410
  }
38333
38411
  }
38334
- var MAX_JOURNAL_BYTES, capWarned = false;
38412
+ var MAX_JOURNAL_BYTES, PRUNE_TO_BYTES;
38335
38413
  var init_journal = __esm(() => {
38336
38414
  init_logger();
38337
38415
  MAX_JOURNAL_BYTES = 32 * 1024 * 1024;
38416
+ PRUNE_TO_BYTES = Math.floor(MAX_JOURNAL_BYTES * 0.6);
38338
38417
  });
38339
38418
 
38340
38419
  // src/behavior/observer/digest.ts
@@ -38551,13 +38630,23 @@ class BehaviorSession {
38551
38630
  modelId;
38552
38631
  providerName;
38553
38632
  config;
38633
+ engine;
38554
38634
  facts = { planModeActive: false };
38555
38635
  bufferedTools = new Set;
38556
- constructor(active, modelId, providerName, config2 = {}) {
38636
+ textBuf = "";
38637
+ reasoningBuf = "";
38638
+ toolsCalled = [];
38639
+ sessionId;
38640
+ systemText = "";
38641
+ get watchesOutput() {
38642
+ return this.active.some((a) => typeof a.rule.onModelOutput === "function");
38643
+ }
38644
+ constructor(active, modelId, providerName, config2 = {}, engine = { queueCorrection() {}, drainCorrections: () => [] }) {
38557
38645
  this.active = active;
38558
38646
  this.modelId = modelId;
38559
38647
  this.providerName = providerName;
38560
38648
  this.config = config2;
38649
+ this.engine = engine;
38561
38650
  }
38562
38651
  get observerOn() {
38563
38652
  return this.config.observer?.enabled === true && (this.config.observer.mode ?? "suggest") !== "off";
@@ -38591,7 +38680,26 @@ class BehaviorSession {
38591
38680
  if (this.active.length === 0)
38592
38681
  return;
38593
38682
  this.facts = detectHarnessFacts(claudeRequest);
38683
+ this.sessionId = extractSessionId(claudeRequest);
38684
+ this.systemText = typeof claudeRequest?.system === "string" ? claudeRequest.system : String(claudeRequest?.system ?? "");
38685
+ this.facts.sessionId = this.sessionId;
38686
+ this.facts.skills = extractAvailableSkills(this.systemText);
38594
38687
  this.armBuffering();
38688
+ if (this.sessionId) {
38689
+ for (const text of this.engine.drainCorrections(this.sessionId)) {
38690
+ this.applyAction("behavior/pending-correction", "fix", { type: "injectSystemNote", text }, {
38691
+ modelId: this.modelId,
38692
+ providerName: this.providerName,
38693
+ isNativeAnthropic: false,
38694
+ claudeRequest,
38695
+ claudeTools,
38696
+ tools,
38697
+ messages,
38698
+ systemText: this.systemText,
38699
+ harness: this.facts
38700
+ });
38701
+ }
38702
+ }
38595
38703
  const ctx = {
38596
38704
  modelId: this.modelId,
38597
38705
  providerName: this.providerName,
@@ -38600,6 +38708,7 @@ class BehaviorSession {
38600
38708
  claudeTools,
38601
38709
  tools,
38602
38710
  messages,
38711
+ systemText: this.systemText,
38603
38712
  harness: this.facts
38604
38713
  };
38605
38714
  for (const { rule, severity } of this.active) {
@@ -38619,6 +38728,66 @@ class BehaviorSession {
38619
38728
  interceptsTool(toolName) {
38620
38729
  return this.bufferedTools.has(toolName);
38621
38730
  }
38731
+ observeText(text, kind = "text") {
38732
+ if (!this.watchesOutput || !text)
38733
+ return;
38734
+ const buf = kind === "reasoning" ? this.reasoningBuf : this.textBuf;
38735
+ if (buf.length >= MAX_OBSERVED_CHARS)
38736
+ return;
38737
+ if (kind === "reasoning")
38738
+ this.reasoningBuf += text;
38739
+ else
38740
+ this.textBuf += text;
38741
+ }
38742
+ observeToolCall(toolName) {
38743
+ if (!this.watchesOutput)
38744
+ return;
38745
+ if (this.toolsCalled.length < MAX_OBSERVED_TOOLS)
38746
+ this.toolsCalled.push(toolName);
38747
+ }
38748
+ finishTurn() {
38749
+ if (!this.watchesOutput)
38750
+ return;
38751
+ const ctx = {
38752
+ modelId: this.modelId,
38753
+ providerName: this.providerName,
38754
+ text: this.textBuf,
38755
+ reasoning: this.reasoningBuf,
38756
+ toolsCalled: this.toolsCalled,
38757
+ harness: this.facts
38758
+ };
38759
+ for (const { rule, severity } of this.active) {
38760
+ if (!rule.onModelOutput)
38761
+ continue;
38762
+ let actions = [];
38763
+ try {
38764
+ actions = rule.onModelOutput(ctx) ?? [];
38765
+ } catch (err) {
38766
+ log(`[behavior] rule ${rule.id} onModelOutput threw: ${err}`);
38767
+ continue;
38768
+ }
38769
+ for (const action of actions) {
38770
+ if (action.type === "warn") {
38771
+ log(`[behavior] ${rule.id} (output): ${action.message}`);
38772
+ this.journal("model_output", "warned", { ruleId: rule.id, note: action.message });
38773
+ continue;
38774
+ }
38775
+ if (action.type !== "injectSystemNote")
38776
+ continue;
38777
+ if (severity !== "fix") {
38778
+ this.journal("model_output", "warned", { ruleId: rule.id, note: "correction withheld" });
38779
+ continue;
38780
+ }
38781
+ if (this.sessionId)
38782
+ this.engine.queueCorrection(this.sessionId, action.text);
38783
+ this.journal("model_output", "matched", { ruleId: rule.id, note: "correction queued" });
38784
+ log(`[behavior] ${rule.id} queued a correction for the next request`);
38785
+ }
38786
+ }
38787
+ this.textBuf = "";
38788
+ this.reasoningBuf = "";
38789
+ this.toolsCalled = [];
38790
+ }
38622
38791
  repairToolCall(toolName, rawArgs) {
38623
38792
  if (!this.bufferedTools.has(toolName))
38624
38793
  return null;
@@ -38786,15 +38955,32 @@ ${action.text}`;
38786
38955
  class BehaviorEngine {
38787
38956
  config;
38788
38957
  rules;
38958
+ corrections = new Map;
38789
38959
  constructor(config2, rules) {
38790
38960
  this.config = config2;
38791
38961
  this.rules = rules;
38792
38962
  }
38963
+ queueCorrection(key, text) {
38964
+ const list = this.corrections.get(key) ?? [];
38965
+ list.push(text);
38966
+ this.corrections.set(key, list);
38967
+ while (this.corrections.size > MAX_TRACKED_CONVERSATIONS) {
38968
+ const oldest = this.corrections.keys().next().value;
38969
+ if (oldest === undefined)
38970
+ break;
38971
+ this.corrections.delete(oldest);
38972
+ }
38973
+ }
38974
+ drainCorrections(key) {
38975
+ const list = this.corrections.get(key) ?? [];
38976
+ this.corrections.delete(key);
38977
+ return list;
38978
+ }
38793
38979
  startSession(params) {
38794
38980
  const active = [];
38795
38981
  if (!params.isNativeAnthropic) {
38796
38982
  for (const rule of this.rules) {
38797
- const severity = resolveSeverity(rule.id, rule.defaultSeverity, this.config);
38983
+ const severity = resolveSeverity(rule.id, rule.defaultSeverity, this.config, params.modelId);
38798
38984
  if (severity === "off")
38799
38985
  continue;
38800
38986
  let applies = false;
@@ -38811,14 +38997,16 @@ class BehaviorEngine {
38811
38997
  if (active.length > 0) {
38812
38998
  log(`[behavior] ${active.length} rule(s) active for ${params.modelId}: ` + active.map((a) => `${a.rule.id}=${a.severity}`).join(", "));
38813
38999
  }
38814
- return new BehaviorSession(active, params.modelId, params.providerName, this.config);
39000
+ return new BehaviorSession(active, params.modelId, params.providerName, this.config, this);
38815
39001
  }
38816
39002
  }
39003
+ var MAX_OBSERVED_CHARS, MAX_OBSERVED_TOOLS = 200, MAX_TRACKED_CONVERSATIONS = 64;
38817
39004
  var init_engine = __esm(() => {
38818
39005
  init_logger();
38819
39006
  init_config();
38820
39007
  init_harness();
38821
39008
  init_journal();
39009
+ MAX_OBSERVED_CHARS = 64 * 1024;
38822
39010
  });
38823
39011
 
38824
39012
  // src/behavior/rules/plan-mode.ts
@@ -41248,11 +41436,13 @@ data: ${JSON.stringify({
41248
41436
  }
41249
41437
  if (data.type === "content_block_delta" && data.delta?.type === "text_delta") {
41250
41438
  const txt = data.delta.text || "";
41439
+ opts.onAssistantText?.(txt, "text");
41251
41440
  textChunks++;
41252
41441
  log(`[AnthropicSSE] Text chunk: "${txt.substring(0, 30).replace(/\n/g, "\\n")}" (${txt.length} chars)`);
41253
41442
  }
41254
41443
  if (data.type === "content_block_start" && data.content_block?.type === "tool_use") {
41255
41444
  toolUseBlocks++;
41445
+ opts.onToolCallObserved?.(data.content_block.name);
41256
41446
  log(`[AnthropicSSE] Tool use: ${data.content_block.name}`);
41257
41447
  }
41258
41448
  if (data.type === "message_delta" && data.delta?.stop_reason) {
@@ -41292,10 +41482,12 @@ data: ${JSON.stringify({
41292
41482
  outputTokens = data.usage.output_tokens || outputTokens;
41293
41483
  }
41294
41484
  if (data.type === "content_block_delta" && data.delta?.type === "text_delta") {
41485
+ opts.onAssistantText?.(data.delta.text || "", "text");
41295
41486
  textChunks++;
41296
41487
  }
41297
41488
  if (data.type === "content_block_start" && data.content_block?.type === "tool_use") {
41298
41489
  toolUseBlocks++;
41490
+ opts.onToolCallObserved?.(data.content_block.name);
41299
41491
  log(`[AnthropicSSE] Tool use: ${data.content_block.name}`);
41300
41492
  }
41301
41493
  if (data.type === "message_delta" && data.delta?.stop_reason) {
@@ -41306,6 +41498,7 @@ data: ${JSON.stringify({
41306
41498
  }
41307
41499
  }
41308
41500
  log(`[AnthropicSSE] Stream complete for ${opts.modelName}: ${totalLines} lines, ${textChunks} text chunks, ${toolUseBlocks} tool_use blocks, stop_reason=${stopReason}${filterThinking ? `, filtered ${thinkingBlocksSuppressed} thinking blocks` : ""}`);
41501
+ opts.onTurnEnd?.();
41309
41502
  if (opts.onTokenUpdate) {
41310
41503
  opts.onTokenUpdate(inputTokens, outputTokens);
41311
41504
  }
@@ -41434,6 +41627,7 @@ data: ${JSON.stringify(data)}
41434
41627
  output_tokens: outputTokens
41435
41628
  }
41436
41629
  });
41630
+ opts.onTurnEnd?.();
41437
41631
  send("message_stop", { type: "message_stop" });
41438
41632
  }
41439
41633
  if (!isClosed) {
@@ -41541,6 +41735,7 @@ data: ${JSON.stringify(data)}
41541
41735
  const toolIdx = toolCalls.size;
41542
41736
  const toolId = `toolu_${Date.now()}_${toolIdx}`;
41543
41737
  const blockIndex = curIdx++;
41738
+ opts.onToolCallObserved?.(part.functionCall.name);
41544
41739
  let args = JSON.stringify(part.functionCall.args || {});
41545
41740
  if (opts.repairToolArgs) {
41546
41741
  try {
@@ -41879,6 +42074,7 @@ data: ${JSON.stringify(data)}
41879
42074
  log(`[ResponsesSSE] Event: ${event.type}`);
41880
42075
  }
41881
42076
  if (event.type === "response.output_text.delta") {
42077
+ opts.onAssistantText?.(event.delta ?? "", "text");
41882
42078
  closeReasoning();
41883
42079
  if (textIdx < 0) {
41884
42080
  textIdx = curIdx++;
@@ -41914,6 +42110,7 @@ data: ${JSON.stringify(data)}
41914
42110
  functionCalls.set(itemId, fnCallData);
41915
42111
  }
41916
42112
  openToolBlocks.add(fnCallData);
42113
+ opts.onToolCallObserved?.(fnName);
41917
42114
  if (pendingReasoning.length > 0) {
41918
42115
  rememberReasoningForCall(callId, pendingReasoning);
41919
42116
  pendingReasoning = [];
@@ -41926,6 +42123,7 @@ data: ${JSON.stringify(data)}
41926
42123
  hasToolUse = true;
41927
42124
  }
41928
42125
  } else if (event.type === "response.reasoning_summary_text.delta") {
42126
+ opts.onAssistantText?.(event.delta ?? "", "reasoning");
41929
42127
  const summaryIndex = typeof event.summary_index === "number" ? event.summary_index : 0;
41930
42128
  if (reasoningIdx < 0) {
41931
42129
  closeText();
@@ -42088,6 +42286,7 @@ data: ${JSON.stringify(data)}
42088
42286
  if (opts.middlewareManager) {
42089
42287
  await opts.middlewareManager.afterStreamComplete(opts.modelName, streamMetadata);
42090
42288
  }
42289
+ opts.onTurnEnd?.();
42091
42290
  safeClose();
42092
42291
  } catch (error46) {
42093
42292
  if (pingInterval) {
@@ -42904,7 +43103,10 @@ class ComposedHandler {
42904
43103
  case "openai-sse":
42905
43104
  return createStreamingResponseHandler(c, response, adapter, this.bareModelName, this.middlewareManager, onTokenUpdate, claudeRequest.tools, toolNameMap, priorInputTokens, behaviorSession && {
42906
43105
  shouldBufferTool: (name) => behaviorSession.interceptsTool(name),
42907
- onToolCall: (name, argsJson) => behaviorSession.repairToolCall(name, argsJson)
43106
+ onToolCall: (name, argsJson) => behaviorSession.repairToolCall(name, argsJson),
43107
+ onAssistantText: (text, kind) => behaviorSession.observeText(text, kind),
43108
+ onToolCallObserved: (name) => behaviorSession.observeToolCall(name),
43109
+ onTurnEnd: () => behaviorSession.finishTurn()
42908
43110
  });
42909
43111
  case "openai-responses-sse":
42910
43112
  return createResponsesStreamHandler(c, response, {
@@ -42916,7 +43118,10 @@ class ComposedHandler {
42916
43118
  priorInputTokens,
42917
43119
  middlewareManager: this.middlewareManager,
42918
43120
  shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
42919
- onToolCall: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null
43121
+ onToolCall: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
43122
+ onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
43123
+ onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
43124
+ onTurnEnd: () => behaviorSession?.finishTurn()
42920
43125
  });
42921
43126
  case "anthropic-sse":
42922
43127
  return createAnthropicPassthroughStream(c, response, {
@@ -42924,7 +43129,10 @@ class ComposedHandler {
42924
43129
  onTokenUpdate,
42925
43130
  adapter: this.modelAdapter ?? adapter,
42926
43131
  shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
42927
- repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null
43132
+ repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
43133
+ onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
43134
+ onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
43135
+ onTurnEnd: () => behaviorSession?.finishTurn()
42928
43136
  });
42929
43137
  case "gemini-sse": {
42930
43138
  const onToolCall = (toolId, name, thoughtSignature) => {
@@ -42939,6 +43147,9 @@ class ComposedHandler {
42939
43147
  onTokenUpdate,
42940
43148
  onToolCall,
42941
43149
  repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
43150
+ onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
43151
+ onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
43152
+ onTurnEnd: () => behaviorSession?.finishTurn(),
42942
43153
  unwrapResponse: this.options.unwrapGeminiResponse,
42943
43154
  priorInputTokens
42944
43155
  });
@@ -48981,6 +49192,28 @@ Behavior rules
48981
49192
  console.log(` ${dim2(`observer model: ${obs.model}`)}`);
48982
49193
  console.log();
48983
49194
  }
49195
+ function printByModel(records) {
49196
+ const byModel = new Map;
49197
+ for (const r of records) {
49198
+ const m = r.model ?? "unknown";
49199
+ const e = byModel.get(m) ?? { ok: 0, degraded: 0 };
49200
+ if (r.outcome === "degraded")
49201
+ e.degraded++;
49202
+ else
49203
+ e.ok++;
49204
+ byModel.set(m, e);
49205
+ }
49206
+ if (byModel.size === 0)
49207
+ return;
49208
+ console.log(bold2(` by model (degraded / ok)
49209
+ `));
49210
+ const ranked = [...byModel.entries()].sort((a, b) => b[1].degraded - a[1].degraded || b[1].ok - a[1].ok);
49211
+ for (const [model, v] of ranked) {
49212
+ const flag = v.degraded > 0 ? yellow(String(v.degraded)) : dim2(String(v.degraded));
49213
+ console.log(` ${model.padEnd(26)} ${flag} / ${v.ok}`);
49214
+ }
49215
+ console.log();
49216
+ }
48984
49217
  function showCorpus(write, json2) {
48985
49218
  const result = buildCorpus({ write });
48986
49219
  if (json2) {
@@ -48999,25 +49232,7 @@ Behavior divergence corpus
48999
49232
  console.log(` ${yellow("degraded (no plan)")} : ${degraded.length}`);
49000
49233
  console.log(` of those, a rule would have fired on ${catchable.length}
49001
49234
  `);
49002
- const byModel = new Map;
49003
- for (const r of result.records) {
49004
- const m = r.model ?? "unknown";
49005
- const e = byModel.get(m) ?? { ok: 0, degraded: 0 };
49006
- if (r.outcome === "degraded")
49007
- e.degraded++;
49008
- else
49009
- e.ok++;
49010
- byModel.set(m, e);
49011
- }
49012
- if (byModel.size > 0) {
49013
- console.log(bold2(` by model (degraded / ok)
49014
- `));
49015
- for (const [model, v] of [...byModel.entries()].sort((a, b) => b[1].degraded - a[1].degraded || b[1].ok - a[1].ok)) {
49016
- const flag = v.degraded > 0 ? yellow(String(v.degraded)) : dim2("0");
49017
- console.log(` ${model.padEnd(26)} ${flag} / ${v.ok}`);
49018
- }
49019
- console.log();
49020
- }
49235
+ printByModel(result.records);
49021
49236
  if (result.outputPath) {
49022
49237
  console.log(dim2(` appended to ${result.outputPath}
49023
49238
  `));
@@ -49043,9 +49258,9 @@ async function behaviorCommand(argv) {
49043
49258
  default:
49044
49259
  console.error(`Unknown action "${action}".
49045
49260
 
49046
- ` + `Usage:
49047
- ` + ` claudish behavior rules [--json]
49048
- ` + ` claudish behavior corpus [--write] [--json]
49261
+ Usage:
49262
+ claudish behavior rules [--json]
49263
+ claudish behavior corpus [--write] [--json]
49049
49264
  `);
49050
49265
  process.exit(1);
49051
49266
  }
@@ -74829,9 +75044,9 @@ function managedSettingsPath() {
74829
75044
  }
74830
75045
  return "/etc/claude-code/managed-settings.json";
74831
75046
  }
74832
- function managedSettingsForcesClaudeAi(readFile = readFileSync24) {
75047
+ function managedSettingsForcesClaudeAi(readFile2 = readFileSync24) {
74833
75048
  try {
74834
- const raw2 = readFile(managedSettingsPath(), "utf-8");
75049
+ const raw2 = readFile2(managedSettingsPath(), "utf-8");
74835
75050
  const parsed = JSON.parse(raw2);
74836
75051
  return parsed.forceLoginMethod === "claudeai";
74837
75052
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.31.0",
3
+ "version": "7.32.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "7.31.0",
64
- "@claudish/magmux-darwin-x64": "7.31.0",
65
- "@claudish/magmux-linux-arm64": "7.31.0",
66
- "@claudish/magmux-linux-x64": "7.31.0"
63
+ "@claudish/magmux-darwin-arm64": "7.32.0",
64
+ "@claudish/magmux-darwin-x64": "7.32.0",
65
+ "@claudish/magmux-linux-arm64": "7.32.0",
66
+ "@claudish/magmux-linux-x64": "7.32.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",