@cabane/companion 0.6.140 → 0.6.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1763,7 +1763,8 @@ var init_turn_event = __esm({
1763
1763
  // `progress` row); `terminal: true` is the turn's closing reply (commits as
1764
1764
  // the `final` row).
1765
1765
  z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
1766
- // Runtime/provider-authored prose surfaced alongside a failed turn. Unlike
1766
+ // Runtime/provider-authored prose about the turn — a failure's explanation, or
1767
+ // a line the harness wrote on a turn it recovered (CT1750). Unlike
1767
1768
  // `text`, this is not the agent's narration or reply: the pump persists it as
1768
1769
  // `runtime_notice`, and the transcript renders it in the shared SystemNote
1769
1770
  // voice. Adapters emit it only from positive runtime evidence; the web never
@@ -1971,10 +1972,12 @@ function isContentBearingEvent(event) {
1971
1972
  return normalizeCommitText(event.body) !== "";
1972
1973
  case "thinking":
1973
1974
  return normalizeCommitText(event.text) !== "";
1974
- case "runtime_notice":
1975
- return normalizeCommitText(event.body) !== "";
1976
1975
  case "tool":
1977
1976
  return true;
1977
+ // CT1750: a runtime notice is shown, but it is the harness speaking about
1978
+ // the turn, not the turn's output — a success whose only output is a CLI
1979
+ // line still produced nothing.
1980
+ case "runtime_notice":
1978
1981
  case "session":
1979
1982
  case "result":
1980
1983
  return false;
@@ -2561,8 +2564,7 @@ var init_addendum = __esm({
2561
2564
  CLAUDE_CODE_ADDENDUM = [
2562
2565
  "On this harness the Cabane tools register under MCP prefixes. The workspace tools are",
2563
2566
  "`mcp__cabane__query`, `mcp__cabane__mutate` and `mcp__cabane__describe`; the ancillary",
2564
- "tools are `mcp__cabane__list_workspaces`,",
2565
- "`mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
2567
+ "tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
2566
2568
  "`mcp__cabane__finalize_upload`, and `mcp__cabane__mint_render_token`.",
2567
2569
  turnControlPromptGuidance("mcp__cabane_companion__"),
2568
2570
  "Where this prompt names a tool by its bare verb, call",
@@ -2903,6 +2905,7 @@ async function* decodeSdkStream(iter, ctx) {
2903
2905
  let lastRequestContextTokens;
2904
2906
  let resolvedModel;
2905
2907
  const signals = { sawRejectedLimit: false };
2908
+ let preInitNotice = null;
2906
2909
  try {
2907
2910
  for await (const msg of iter) {
2908
2911
  if (ctx.signal.aborted) return;
@@ -2928,12 +2931,19 @@ async function* decodeSdkStream(iter, ctx) {
2928
2931
  if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
2929
2932
  const failure = classifyPreInitFrame(msg, signals);
2930
2933
  resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
2934
+ if (msg.type === "assistant" && isHarnessAuthored(msg))
2935
+ preInitNotice = harnessNoticeText(msg);
2931
2936
  break;
2932
2937
  } else if (msg.type === "assistant") {
2933
2938
  const assistantErr = msg.error;
2934
2939
  if (typeof assistantErr === "string" && assistantErr.length > 0) {
2935
2940
  signals.lastAssistantError = assistantErr;
2936
2941
  }
2942
+ if (isHarnessAuthored(msg)) {
2943
+ await emitHarnessMessage(msg, emit, pending, buffer);
2944
+ yield* drain(out);
2945
+ continue;
2946
+ }
2937
2947
  const reqContext = readRequestContextTokens(msg);
2938
2948
  if (reqContext !== void 0) lastRequestContextTokens = reqContext;
2939
2949
  await processAssistantMessage(msg, emit, pending, buffer);
@@ -2995,6 +3005,7 @@ async function* decodeSdkStream(iter, ctx) {
2995
3005
  const failure = classifyFailure(signals, { text: "" });
2996
3006
  resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
2997
3007
  }
3008
+ if (preInitNotice) yield { type: "runtime_notice", body: preInitNotice };
2998
3009
  }
2999
3010
  const heldFailure = !ok ? classifyRuntimeNoticeText(buffer.held) : null;
3000
3011
  const terminalFailure = !ok ? decodeFailureReason(resultReason) : null;
@@ -3061,6 +3072,29 @@ function preInitToolActivity(msg) {
3061
3072
  const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
3062
3073
  return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
3063
3074
  }
3075
+ function isHarnessAuthored(msg) {
3076
+ const error = msg.error;
3077
+ if (typeof error === "string" && error.length > 0) return true;
3078
+ return msg.message?.model === "<synthetic>";
3079
+ }
3080
+ function harnessNoticeText(msg) {
3081
+ const body = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join("\n\n").trim();
3082
+ return body && !HARNESS_PLACEHOLDERS.has(body) ? body : null;
3083
+ }
3084
+ async function emitHarnessMessage(msg, emit, pending, buffer) {
3085
+ const body = harnessNoticeText(msg);
3086
+ if (body) emit({ type: "runtime_notice", body });
3087
+ const toolUses = contentBlocks(msg).filter((b) => b.type === "tool_use");
3088
+ if (toolUses.length > 0) {
3089
+ const inner = msg.message ?? {};
3090
+ await processAssistantMessage(
3091
+ { message: { ...inner, content: toolUses } },
3092
+ emit,
3093
+ pending,
3094
+ buffer
3095
+ );
3096
+ }
3097
+ }
3064
3098
  function contentBlocks(msg) {
3065
3099
  const content = msg.message?.content;
3066
3100
  return Array.isArray(content) ? content : [];
@@ -3102,12 +3136,14 @@ function readContextWindow(msg, resolvedModel) {
3102
3136
  }
3103
3137
  return max;
3104
3138
  }
3139
+ var HARNESS_PLACEHOLDERS;
3105
3140
  var init_loop = __esm({
3106
3141
  "packages/agent-runtime/src/claude-code/loop.ts"() {
3107
3142
  "use strict";
3108
3143
  init_transcript();
3109
3144
  init_failure();
3110
3145
  init_session();
3146
+ HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
3111
3147
  }
3112
3148
  });
3113
3149
 
@@ -3180,7 +3216,7 @@ async function* runWithResumeRecovery(queryFn, req, signal, initial, augmentOpti
3180
3216
  yield* sdkEvents(queryFn, fresh.req, signal, options, fresh.degraded);
3181
3217
  return;
3182
3218
  }
3183
- emitted = true;
3219
+ if (event.type !== "runtime_notice") emitted = true;
3184
3220
  yield event;
3185
3221
  }
3186
3222
  }
@@ -3853,7 +3889,7 @@ var init_addendum2 = __esm({
3853
3889
  "Every tool here goes by its plain name: the workspace tools are `query`, `mutate` and",
3854
3890
  "`describe`.",
3855
3891
  turnControlPromptGuidance(""),
3856
- "The ancillary tools are `list_workspaces`, `read_binary`, `upload`, `begin_upload`,",
3892
+ "The ancillary tools are `read_binary`, `upload`, `begin_upload`,",
3857
3893
  "`finalize_upload`, `mint_render_token`; and the host tools are plain verbs too (`bash`,",
3858
3894
  "`read`, `edit`). Mind the collision: a bare `read` or `edit` is the HOST tool, acting on the",
3859
3895
  "local machine \u2014 a workspace read is `query` with the operation name `files.read`. The",
@@ -5038,8 +5074,8 @@ var init_addendum3 = __esm({
5038
5074
  // in sat two lines under the instruction to invoke the exact qualified names,
5039
5075
  // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
5040
5076
  // that sentence is about operation names, after the qualified forms are given.
5041
- "The ancillary tools are `mcp__cabane__list_workspaces`, `mcp__cabane__read_binary`,",
5042
- "`mcp__cabane__upload`, `mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
5077
+ "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
5078
+ "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
5043
5079
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
5044
5080
  "There is no Cabane `read`/`write`/`search` tool \u2014 those are",
5045
5081
  "operation NAMES you pass to `query` / `mutate`. One more harness fact: you can interleave",
package/dist/runtime.js CHANGED
@@ -3256,7 +3256,8 @@ var turnEventSchema = z6.discriminatedUnion("type", [
3256
3256
  // `progress` row); `terminal: true` is the turn's closing reply (commits as
3257
3257
  // the `final` row).
3258
3258
  z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
3259
- // Runtime/provider-authored prose surfaced alongside a failed turn. Unlike
3259
+ // Runtime/provider-authored prose about the turn — a failure's explanation, or
3260
+ // a line the harness wrote on a turn it recovered (CT1750). Unlike
3260
3261
  // `text`, this is not the agent's narration or reply: the pump persists it as
3261
3262
  // `runtime_notice`, and the transcript renders it in the shared SystemNote
3262
3263
  // voice. Adapters emit it only from positive runtime evidence; the web never
@@ -3450,10 +3451,12 @@ function isContentBearingEvent(event) {
3450
3451
  return normalizeCommitText(event.body) !== "";
3451
3452
  case "thinking":
3452
3453
  return normalizeCommitText(event.text) !== "";
3453
- case "runtime_notice":
3454
- return normalizeCommitText(event.body) !== "";
3455
3454
  case "tool":
3456
3455
  return true;
3456
+ // CT1750: a runtime notice is shown, but it is the harness speaking about
3457
+ // the turn, not the turn's output — a success whose only output is a CLI
3458
+ // line still produced nothing.
3459
+ case "runtime_notice":
3457
3460
  case "session":
3458
3461
  case "result":
3459
3462
  return false;
@@ -3987,8 +3990,7 @@ function turnControlPromptGuidance(prefix) {
3987
3990
  var CLAUDE_CODE_ADDENDUM = [
3988
3991
  "On this harness the Cabane tools register under MCP prefixes. The workspace tools are",
3989
3992
  "`mcp__cabane__query`, `mcp__cabane__mutate` and `mcp__cabane__describe`; the ancillary",
3990
- "tools are `mcp__cabane__list_workspaces`,",
3991
- "`mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
3993
+ "tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`, `mcp__cabane__begin_upload`,",
3992
3994
  "`mcp__cabane__finalize_upload`, and `mcp__cabane__mint_render_token`.",
3993
3995
  turnControlPromptGuidance("mcp__cabane_companion__"),
3994
3996
  "Where this prompt names a tool by its bare verb, call",
@@ -4295,6 +4297,7 @@ async function* decodeSdkStream(iter, ctx) {
4295
4297
  let lastRequestContextTokens;
4296
4298
  let resolvedModel;
4297
4299
  const signals = { sawRejectedLimit: false };
4300
+ let preInitNotice = null;
4298
4301
  try {
4299
4302
  for await (const msg of iter) {
4300
4303
  if (ctx.signal.aborted) return;
@@ -4320,12 +4323,19 @@ async function* decodeSdkStream(iter, ctx) {
4320
4323
  if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
4321
4324
  const failure = classifyPreInitFrame(msg, signals);
4322
4325
  resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
4326
+ if (msg.type === "assistant" && isHarnessAuthored(msg))
4327
+ preInitNotice = harnessNoticeText(msg);
4323
4328
  break;
4324
4329
  } else if (msg.type === "assistant") {
4325
4330
  const assistantErr = msg.error;
4326
4331
  if (typeof assistantErr === "string" && assistantErr.length > 0) {
4327
4332
  signals.lastAssistantError = assistantErr;
4328
4333
  }
4334
+ if (isHarnessAuthored(msg)) {
4335
+ await emitHarnessMessage(msg, emit, pending, buffer);
4336
+ yield* drain(out);
4337
+ continue;
4338
+ }
4329
4339
  const reqContext = readRequestContextTokens(msg);
4330
4340
  if (reqContext !== void 0) lastRequestContextTokens = reqContext;
4331
4341
  await processAssistantMessage(msg, emit, pending, buffer);
@@ -4387,6 +4397,7 @@ async function* decodeSdkStream(iter, ctx) {
4387
4397
  const failure = classifyFailure(signals, { text: "" });
4388
4398
  resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
4389
4399
  }
4400
+ if (preInitNotice) yield { type: "runtime_notice", body: preInitNotice };
4390
4401
  }
4391
4402
  const heldFailure = !ok ? classifyRuntimeNoticeText(buffer.held) : null;
4392
4403
  const terminalFailure = !ok ? decodeFailureReason(resultReason) : null;
@@ -4453,6 +4464,30 @@ function preInitToolActivity(msg) {
4453
4464
  const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
4454
4465
  return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
4455
4466
  }
4467
+ function isHarnessAuthored(msg) {
4468
+ const error = msg.error;
4469
+ if (typeof error === "string" && error.length > 0) return true;
4470
+ return msg.message?.model === "<synthetic>";
4471
+ }
4472
+ var HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
4473
+ function harnessNoticeText(msg) {
4474
+ const body = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join("\n\n").trim();
4475
+ return body && !HARNESS_PLACEHOLDERS.has(body) ? body : null;
4476
+ }
4477
+ async function emitHarnessMessage(msg, emit, pending, buffer) {
4478
+ const body = harnessNoticeText(msg);
4479
+ if (body) emit({ type: "runtime_notice", body });
4480
+ const toolUses = contentBlocks(msg).filter((b) => b.type === "tool_use");
4481
+ if (toolUses.length > 0) {
4482
+ const inner = msg.message ?? {};
4483
+ await processAssistantMessage(
4484
+ { message: { ...inner, content: toolUses } },
4485
+ emit,
4486
+ pending,
4487
+ buffer
4488
+ );
4489
+ }
4490
+ }
4456
4491
  function contentBlocks(msg) {
4457
4492
  const content = msg.message?.content;
4458
4493
  return Array.isArray(content) ? content : [];
@@ -4556,7 +4591,7 @@ async function* runWithResumeRecovery(queryFn, req, signal, initial, augmentOpti
4556
4591
  yield* sdkEvents(queryFn, fresh.req, signal, options, fresh.degraded);
4557
4592
  return;
4558
4593
  }
4559
- emitted = true;
4594
+ if (event.type !== "runtime_notice") emitted = true;
4560
4595
  yield event;
4561
4596
  }
4562
4597
  }
@@ -5192,7 +5227,7 @@ var OPENCODE_ADDENDUM = [
5192
5227
  "Every tool here goes by its plain name: the workspace tools are `query`, `mutate` and",
5193
5228
  "`describe`.",
5194
5229
  turnControlPromptGuidance(""),
5195
- "The ancillary tools are `list_workspaces`, `read_binary`, `upload`, `begin_upload`,",
5230
+ "The ancillary tools are `read_binary`, `upload`, `begin_upload`,",
5196
5231
  "`finalize_upload`, `mint_render_token`; and the host tools are plain verbs too (`bash`,",
5197
5232
  "`read`, `edit`). Mind the collision: a bare `read` or `edit` is the HOST tool, acting on the",
5198
5233
  "local machine \u2014 a workspace read is `query` with the operation name `files.read`. The",
@@ -6297,8 +6332,8 @@ var CODEX_ADDENDUM = [
6297
6332
  // in sat two lines under the instruction to invoke the exact qualified names,
6298
6333
  // so the sentence contradicted itself. The bare `query` / `mutate` below stay:
6299
6334
  // that sentence is about operation names, after the qualified forms are given.
6300
- "The ancillary tools are `mcp__cabane__list_workspaces`, `mcp__cabane__read_binary`,",
6301
- "`mcp__cabane__upload`, `mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
6335
+ "The ancillary tools are `mcp__cabane__read_binary`, `mcp__cabane__upload`,",
6336
+ "`mcp__cabane__begin_upload`, `mcp__cabane__finalize_upload` and",
6302
6337
  "`mcp__cabane__mint_render_token` \u2014 all possibly deferred too.",
6303
6338
  "There is no Cabane `read`/`write`/`search` tool \u2014 those are",
6304
6339
  "operation NAMES you pass to `query` / `mutate`. One more harness fact: you can interleave",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.140",
3
+ "version": "0.6.142",
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",