@cabane/companion 0.6.139 → 0.6.141
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/README.md +1 -1
- package/dist/cli.js +41 -4
- package/dist/runtime.js +40 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ Run `cabane-companion start --foreground`. Configure the supervisor itself for r
|
|
|
212
212
|
|
|
213
213
|
There's a deliberate split. **What the agent is** — its mode, MCP servers, model, reasoning, and charter — is configured in **Cabane** (Workspace settings → Agents) and delivered to the Companion per turn, so an edit in Cabane lands on the next turn with **no restart**. The Companion holds only the bits that can't travel from a server: the **working directory** an agent runs in (a path on _this_ machine) and your **secrets**.
|
|
214
214
|
|
|
215
|
-
An agent can reach MCP servers two ways, and they don't overlap. **The agent's own MCP servers** are part of what the agent is: set on its Agents page, they go with it into every room. **Connections** belong to a person instead: you add one under Workspace settings → Connections (an endpoint and a pasted key, stored encrypted in Cabane), then
|
|
215
|
+
An agent can reach MCP servers two ways, and they don't overlap. **The agent's own MCP servers** are part of what the agent is: set on its Agents page, they go with it into every room. **Connections** belong to a person instead: you add one under Workspace settings → Connections (an endpoint and a pasted key, stored encrypted in Cabane), then add it to a room from the room's menu and pick which agents there may use it. On each turn in that room, Cabane attaches the room's connections for that agent and sends the key with the turn, into the harness's environment and never onto its command line. Nothing is configured on this machine. Replacing, disconnecting or deleting a key takes effect on the agent's next turn, and nothing is revoked at the service itself.
|
|
216
216
|
|
|
217
217
|
### Agent modes (set in Cabane)
|
|
218
218
|
|
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
|
|
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;
|
|
@@ -2903,6 +2906,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2903
2906
|
let lastRequestContextTokens;
|
|
2904
2907
|
let resolvedModel;
|
|
2905
2908
|
const signals = { sawRejectedLimit: false };
|
|
2909
|
+
let preInitNotice = null;
|
|
2906
2910
|
try {
|
|
2907
2911
|
for await (const msg of iter) {
|
|
2908
2912
|
if (ctx.signal.aborted) return;
|
|
@@ -2928,12 +2932,19 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2928
2932
|
if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
|
|
2929
2933
|
const failure = classifyPreInitFrame(msg, signals);
|
|
2930
2934
|
resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
|
|
2935
|
+
if (msg.type === "assistant" && isHarnessAuthored(msg))
|
|
2936
|
+
preInitNotice = harnessNoticeText(msg);
|
|
2931
2937
|
break;
|
|
2932
2938
|
} else if (msg.type === "assistant") {
|
|
2933
2939
|
const assistantErr = msg.error;
|
|
2934
2940
|
if (typeof assistantErr === "string" && assistantErr.length > 0) {
|
|
2935
2941
|
signals.lastAssistantError = assistantErr;
|
|
2936
2942
|
}
|
|
2943
|
+
if (isHarnessAuthored(msg)) {
|
|
2944
|
+
await emitHarnessMessage(msg, emit, pending, buffer);
|
|
2945
|
+
yield* drain(out);
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2937
2948
|
const reqContext = readRequestContextTokens(msg);
|
|
2938
2949
|
if (reqContext !== void 0) lastRequestContextTokens = reqContext;
|
|
2939
2950
|
await processAssistantMessage(msg, emit, pending, buffer);
|
|
@@ -2995,6 +3006,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
2995
3006
|
const failure = classifyFailure(signals, { text: "" });
|
|
2996
3007
|
resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
|
|
2997
3008
|
}
|
|
3009
|
+
if (preInitNotice) yield { type: "runtime_notice", body: preInitNotice };
|
|
2998
3010
|
}
|
|
2999
3011
|
const heldFailure = !ok ? classifyRuntimeNoticeText(buffer.held) : null;
|
|
3000
3012
|
const terminalFailure = !ok ? decodeFailureReason(resultReason) : null;
|
|
@@ -3061,6 +3073,29 @@ function preInitToolActivity(msg) {
|
|
|
3061
3073
|
const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
|
|
3062
3074
|
return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
|
|
3063
3075
|
}
|
|
3076
|
+
function isHarnessAuthored(msg) {
|
|
3077
|
+
const error = msg.error;
|
|
3078
|
+
if (typeof error === "string" && error.length > 0) return true;
|
|
3079
|
+
return msg.message?.model === "<synthetic>";
|
|
3080
|
+
}
|
|
3081
|
+
function harnessNoticeText(msg) {
|
|
3082
|
+
const body = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join("\n\n").trim();
|
|
3083
|
+
return body && !HARNESS_PLACEHOLDERS.has(body) ? body : null;
|
|
3084
|
+
}
|
|
3085
|
+
async function emitHarnessMessage(msg, emit, pending, buffer) {
|
|
3086
|
+
const body = harnessNoticeText(msg);
|
|
3087
|
+
if (body) emit({ type: "runtime_notice", body });
|
|
3088
|
+
const toolUses = contentBlocks(msg).filter((b) => b.type === "tool_use");
|
|
3089
|
+
if (toolUses.length > 0) {
|
|
3090
|
+
const inner = msg.message ?? {};
|
|
3091
|
+
await processAssistantMessage(
|
|
3092
|
+
{ message: { ...inner, content: toolUses } },
|
|
3093
|
+
emit,
|
|
3094
|
+
pending,
|
|
3095
|
+
buffer
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3064
3099
|
function contentBlocks(msg) {
|
|
3065
3100
|
const content = msg.message?.content;
|
|
3066
3101
|
return Array.isArray(content) ? content : [];
|
|
@@ -3102,12 +3137,14 @@ function readContextWindow(msg, resolvedModel) {
|
|
|
3102
3137
|
}
|
|
3103
3138
|
return max;
|
|
3104
3139
|
}
|
|
3140
|
+
var HARNESS_PLACEHOLDERS;
|
|
3105
3141
|
var init_loop = __esm({
|
|
3106
3142
|
"packages/agent-runtime/src/claude-code/loop.ts"() {
|
|
3107
3143
|
"use strict";
|
|
3108
3144
|
init_transcript();
|
|
3109
3145
|
init_failure();
|
|
3110
3146
|
init_session();
|
|
3147
|
+
HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
|
|
3111
3148
|
}
|
|
3112
3149
|
});
|
|
3113
3150
|
|
|
@@ -3180,7 +3217,7 @@ async function* runWithResumeRecovery(queryFn, req, signal, initial, augmentOpti
|
|
|
3180
3217
|
yield* sdkEvents(queryFn, fresh.req, signal, options, fresh.degraded);
|
|
3181
3218
|
return;
|
|
3182
3219
|
}
|
|
3183
|
-
emitted = true;
|
|
3220
|
+
if (event.type !== "runtime_notice") emitted = true;
|
|
3184
3221
|
yield event;
|
|
3185
3222
|
}
|
|
3186
3223
|
}
|
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
|
|
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;
|
|
@@ -4295,6 +4298,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4295
4298
|
let lastRequestContextTokens;
|
|
4296
4299
|
let resolvedModel;
|
|
4297
4300
|
const signals = { sawRejectedLimit: false };
|
|
4301
|
+
let preInitNotice = null;
|
|
4298
4302
|
try {
|
|
4299
4303
|
for await (const msg of iter) {
|
|
4300
4304
|
if (ctx.signal.aborted) return;
|
|
@@ -4320,12 +4324,19 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4320
4324
|
if (preInitToolActivity(msg) && ctx.startEvidence) ctx.startEvidence.toolActivity = true;
|
|
4321
4325
|
const failure = classifyPreInitFrame(msg, signals);
|
|
4322
4326
|
resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
|
|
4327
|
+
if (msg.type === "assistant" && isHarnessAuthored(msg))
|
|
4328
|
+
preInitNotice = harnessNoticeText(msg);
|
|
4323
4329
|
break;
|
|
4324
4330
|
} else if (msg.type === "assistant") {
|
|
4325
4331
|
const assistantErr = msg.error;
|
|
4326
4332
|
if (typeof assistantErr === "string" && assistantErr.length > 0) {
|
|
4327
4333
|
signals.lastAssistantError = assistantErr;
|
|
4328
4334
|
}
|
|
4335
|
+
if (isHarnessAuthored(msg)) {
|
|
4336
|
+
await emitHarnessMessage(msg, emit, pending, buffer);
|
|
4337
|
+
yield* drain(out);
|
|
4338
|
+
continue;
|
|
4339
|
+
}
|
|
4329
4340
|
const reqContext = readRequestContextTokens(msg);
|
|
4330
4341
|
if (reqContext !== void 0) lastRequestContextTokens = reqContext;
|
|
4331
4342
|
await processAssistantMessage(msg, emit, pending, buffer);
|
|
@@ -4387,6 +4398,7 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4387
4398
|
const failure = classifyFailure(signals, { text: "" });
|
|
4388
4399
|
resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
|
|
4389
4400
|
}
|
|
4401
|
+
if (preInitNotice) yield { type: "runtime_notice", body: preInitNotice };
|
|
4390
4402
|
}
|
|
4391
4403
|
const heldFailure = !ok ? classifyRuntimeNoticeText(buffer.held) : null;
|
|
4392
4404
|
const terminalFailure = !ok ? decodeFailureReason(resultReason) : null;
|
|
@@ -4453,6 +4465,30 @@ function preInitToolActivity(msg) {
|
|
|
4453
4465
|
const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
|
|
4454
4466
|
return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
|
|
4455
4467
|
}
|
|
4468
|
+
function isHarnessAuthored(msg) {
|
|
4469
|
+
const error = msg.error;
|
|
4470
|
+
if (typeof error === "string" && error.length > 0) return true;
|
|
4471
|
+
return msg.message?.model === "<synthetic>";
|
|
4472
|
+
}
|
|
4473
|
+
var HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
|
|
4474
|
+
function harnessNoticeText(msg) {
|
|
4475
|
+
const body = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join("\n\n").trim();
|
|
4476
|
+
return body && !HARNESS_PLACEHOLDERS.has(body) ? body : null;
|
|
4477
|
+
}
|
|
4478
|
+
async function emitHarnessMessage(msg, emit, pending, buffer) {
|
|
4479
|
+
const body = harnessNoticeText(msg);
|
|
4480
|
+
if (body) emit({ type: "runtime_notice", body });
|
|
4481
|
+
const toolUses = contentBlocks(msg).filter((b) => b.type === "tool_use");
|
|
4482
|
+
if (toolUses.length > 0) {
|
|
4483
|
+
const inner = msg.message ?? {};
|
|
4484
|
+
await processAssistantMessage(
|
|
4485
|
+
{ message: { ...inner, content: toolUses } },
|
|
4486
|
+
emit,
|
|
4487
|
+
pending,
|
|
4488
|
+
buffer
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4456
4492
|
function contentBlocks(msg) {
|
|
4457
4493
|
const content = msg.message?.content;
|
|
4458
4494
|
return Array.isArray(content) ? content : [];
|
|
@@ -4556,7 +4592,7 @@ async function* runWithResumeRecovery(queryFn, req, signal, initial, augmentOpti
|
|
|
4556
4592
|
yield* sdkEvents(queryFn, fresh.req, signal, options, fresh.degraded);
|
|
4557
4593
|
return;
|
|
4558
4594
|
}
|
|
4559
|
-
emitted = true;
|
|
4595
|
+
if (event.type !== "runtime_notice") emitted = true;
|
|
4560
4596
|
yield event;
|
|
4561
4597
|
}
|
|
4562
4598
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.141",
|
|
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",
|