@cabane/companion 0.6.51 → 0.6.53
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 +244 -3
- package/dist/runtime.js +244 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -232,6 +232,9 @@ function realAccountHome() {
|
|
|
232
232
|
}
|
|
233
233
|
return realHomeCache;
|
|
234
234
|
}
|
|
235
|
+
function agentCompanionHome() {
|
|
236
|
+
return join(cabaneDir(), "agent-home");
|
|
237
|
+
}
|
|
235
238
|
function cabaneDir() {
|
|
236
239
|
const dir2 = join(process.env.CABANE_COMPANION_HOME || homedir(), ".cabane");
|
|
237
240
|
if (process.env.VITEST) {
|
|
@@ -5530,6 +5533,7 @@ function readToolItem(item, eventType) {
|
|
|
5530
5533
|
if (type === "mcp_tool_call") {
|
|
5531
5534
|
const tool2 = str(item.tool) ?? "tool";
|
|
5532
5535
|
const server = str(item.server);
|
|
5536
|
+
const result = readMcpResult(item.result);
|
|
5533
5537
|
return {
|
|
5534
5538
|
id,
|
|
5535
5539
|
// A cabane tool reads best as its bare verb (`read`); a non-cabane server's
|
|
@@ -5537,7 +5541,7 @@ function readToolItem(item, eventType) {
|
|
|
5537
5541
|
name: server && server !== "cabane" ? `${server}.${tool2}` : tool2,
|
|
5538
5542
|
input: item.arguments,
|
|
5539
5543
|
status: toStatus(item.status),
|
|
5540
|
-
...item.status === "completed" ? { result
|
|
5544
|
+
...item.status === "completed" ? { result } : item.status === "failed" ? { result: hasMcpReceipt(result) ? result : readMcpError(item.error) } : {}
|
|
5541
5545
|
};
|
|
5542
5546
|
}
|
|
5543
5547
|
if (type === "web_search") {
|
|
@@ -5577,9 +5581,38 @@ function toStatus(v) {
|
|
|
5577
5581
|
}
|
|
5578
5582
|
function readMcpResult(result) {
|
|
5579
5583
|
const rec2 = asRecord2(result);
|
|
5580
|
-
if (rec2
|
|
5584
|
+
if (!rec2) return result;
|
|
5585
|
+
if (hasOnlyNonTextContentBlocks(rec2.content)) return rec2.content;
|
|
5586
|
+
if (rec2.structured_content !== void 0 && rec2.structured_content !== null) {
|
|
5587
|
+
return rec2.structured_content;
|
|
5588
|
+
}
|
|
5589
|
+
if (rec2.structuredContent !== void 0 && rec2.structuredContent !== null) {
|
|
5590
|
+
return rec2.structuredContent;
|
|
5591
|
+
}
|
|
5592
|
+
if ("content" in rec2) return rec2.content;
|
|
5581
5593
|
return result;
|
|
5582
5594
|
}
|
|
5595
|
+
function hasOnlyNonTextContentBlocks(content) {
|
|
5596
|
+
if (!Array.isArray(content) || content.length === 0) return false;
|
|
5597
|
+
let sawTypedBlock = false;
|
|
5598
|
+
for (const raw of content) {
|
|
5599
|
+
const block = asRecord2(raw);
|
|
5600
|
+
const type = block && str(block.type);
|
|
5601
|
+
if (!type) continue;
|
|
5602
|
+
sawTypedBlock = true;
|
|
5603
|
+
if (type === "text" && typeof block?.text === "string" && block.text.trim().length > 0) {
|
|
5604
|
+
return false;
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
return sawTypedBlock;
|
|
5608
|
+
}
|
|
5609
|
+
function hasMcpReceipt(result) {
|
|
5610
|
+
if (result === void 0 || result === null) return false;
|
|
5611
|
+
if (typeof result === "string") return result.trim().length > 0;
|
|
5612
|
+
if (Array.isArray(result)) return result.length > 0;
|
|
5613
|
+
if (typeof result === "object") return Object.keys(result).length > 0;
|
|
5614
|
+
return true;
|
|
5615
|
+
}
|
|
5583
5616
|
function readMcpError(error) {
|
|
5584
5617
|
const rec2 = asRecord2(error);
|
|
5585
5618
|
return rec2 && str(rec2.message) || "error";
|
|
@@ -6103,6 +6136,25 @@ var errorItem = (id, message) => ({
|
|
|
6103
6136
|
type: "item.completed",
|
|
6104
6137
|
item: { id, type: "error", message }
|
|
6105
6138
|
});
|
|
6139
|
+
var SDK_SUCCESS = { ok: true, result: { source: "native" }, logs: "", truncated: false };
|
|
6140
|
+
var SDK_FAILURE = {
|
|
6141
|
+
ok: false,
|
|
6142
|
+
result: null,
|
|
6143
|
+
preview: '{"rows":[1,2,3]}',
|
|
6144
|
+
logs: "",
|
|
6145
|
+
truncated: false,
|
|
6146
|
+
error: { kind: "output_too_large", bytes: 169167, limit: 65536 },
|
|
6147
|
+
warnings: [],
|
|
6148
|
+
committed: {
|
|
6149
|
+
calls: [
|
|
6150
|
+
{
|
|
6151
|
+
method: "conversations.update",
|
|
6152
|
+
args: { id: "conv-1", opts: { title: "Renamed before failure" } }
|
|
6153
|
+
}
|
|
6154
|
+
]
|
|
6155
|
+
},
|
|
6156
|
+
inFlight: null
|
|
6157
|
+
};
|
|
6106
6158
|
var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint = fingerprintPrompt(PROMPT)) => ({
|
|
6107
6159
|
type: "session",
|
|
6108
6160
|
state: encodeSession3({ threadId, cwd, promptFingerprint }),
|
|
@@ -6182,6 +6234,194 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6182
6234
|
{ type: "result", ok: true }
|
|
6183
6235
|
]
|
|
6184
6236
|
},
|
|
6237
|
+
{
|
|
6238
|
+
// CT1211: recorded SDK success shape. Codex exposes the text compatibility
|
|
6239
|
+
// copy and the native object together; normalization must keep the object.
|
|
6240
|
+
name: "MCP success prefers native structured content",
|
|
6241
|
+
request: makeRequest3(),
|
|
6242
|
+
nativeStream: [
|
|
6243
|
+
threadStarted(NEW_THREAD_ID),
|
|
6244
|
+
toolFrame("item.started", {
|
|
6245
|
+
id: "sdk-success",
|
|
6246
|
+
type: "mcp_tool_call",
|
|
6247
|
+
server: "cabane",
|
|
6248
|
+
tool: "sdk",
|
|
6249
|
+
arguments: { code: 'return { source: "native" }' },
|
|
6250
|
+
status: "in_progress"
|
|
6251
|
+
}),
|
|
6252
|
+
toolFrame("item.completed", {
|
|
6253
|
+
id: "sdk-success",
|
|
6254
|
+
type: "mcp_tool_call",
|
|
6255
|
+
server: "cabane",
|
|
6256
|
+
tool: "sdk",
|
|
6257
|
+
arguments: { code: 'return { source: "native" }' },
|
|
6258
|
+
status: "completed",
|
|
6259
|
+
result: {
|
|
6260
|
+
content: [{ type: "text", text: JSON.stringify(SDK_SUCCESS) }],
|
|
6261
|
+
structured_content: SDK_SUCCESS
|
|
6262
|
+
}
|
|
6263
|
+
}),
|
|
6264
|
+
turnCompleted()
|
|
6265
|
+
],
|
|
6266
|
+
expected: [
|
|
6267
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6268
|
+
{
|
|
6269
|
+
type: "tool",
|
|
6270
|
+
id: "sdk-success",
|
|
6271
|
+
name: "sdk",
|
|
6272
|
+
phase: "start",
|
|
6273
|
+
summary: "",
|
|
6274
|
+
input: { code: 'return { source: "native" }' }
|
|
6275
|
+
},
|
|
6276
|
+
{
|
|
6277
|
+
type: "tool",
|
|
6278
|
+
id: "sdk-success",
|
|
6279
|
+
name: "sdk",
|
|
6280
|
+
phase: "done",
|
|
6281
|
+
summary: "",
|
|
6282
|
+
input: { code: 'return { source: "native" }' },
|
|
6283
|
+
result: SDK_SUCCESS
|
|
6284
|
+
},
|
|
6285
|
+
{ type: "result", ok: true }
|
|
6286
|
+
]
|
|
6287
|
+
},
|
|
6288
|
+
{
|
|
6289
|
+
// CT1211 incident shape: MCP `isError` keeps the failed phase, while the raw
|
|
6290
|
+
// camel-case structured payload survives instead of the generic `error` token.
|
|
6291
|
+
name: "failed SDK call preserves structured receipt while remaining error phase",
|
|
6292
|
+
request: makeRequest3(),
|
|
6293
|
+
nativeStream: [
|
|
6294
|
+
threadStarted(NEW_THREAD_ID),
|
|
6295
|
+
toolFrame("item.started", {
|
|
6296
|
+
id: "sdk-failed",
|
|
6297
|
+
type: "mcp_tool_call",
|
|
6298
|
+
server: "cabane",
|
|
6299
|
+
tool: "sdk",
|
|
6300
|
+
arguments: { code: "return largeResult" },
|
|
6301
|
+
status: "in_progress"
|
|
6302
|
+
}),
|
|
6303
|
+
toolFrame("item.completed", {
|
|
6304
|
+
id: "sdk-failed",
|
|
6305
|
+
type: "mcp_tool_call",
|
|
6306
|
+
server: "cabane",
|
|
6307
|
+
tool: "sdk",
|
|
6308
|
+
arguments: { code: "return largeResult" },
|
|
6309
|
+
status: "failed",
|
|
6310
|
+
result: {
|
|
6311
|
+
content: [{ type: "text", text: JSON.stringify(SDK_FAILURE) }],
|
|
6312
|
+
structuredContent: SDK_FAILURE,
|
|
6313
|
+
isError: true
|
|
6314
|
+
},
|
|
6315
|
+
error: { message: "error" }
|
|
6316
|
+
}),
|
|
6317
|
+
turnCompleted()
|
|
6318
|
+
],
|
|
6319
|
+
expected: [
|
|
6320
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6321
|
+
{
|
|
6322
|
+
type: "tool",
|
|
6323
|
+
id: "sdk-failed",
|
|
6324
|
+
name: "sdk",
|
|
6325
|
+
phase: "start",
|
|
6326
|
+
summary: "",
|
|
6327
|
+
input: { code: "return largeResult" }
|
|
6328
|
+
},
|
|
6329
|
+
{
|
|
6330
|
+
type: "tool",
|
|
6331
|
+
id: "sdk-failed",
|
|
6332
|
+
name: "sdk",
|
|
6333
|
+
phase: "error",
|
|
6334
|
+
summary: "",
|
|
6335
|
+
input: { code: "return largeResult" },
|
|
6336
|
+
result: SDK_FAILURE
|
|
6337
|
+
},
|
|
6338
|
+
{ type: "result", ok: true }
|
|
6339
|
+
]
|
|
6340
|
+
},
|
|
6341
|
+
{
|
|
6342
|
+
// A transport/tool failure has no MCP result payload and still uses the real
|
|
6343
|
+
// adapter error message. CT1211 must not replace this fallback with `undefined`.
|
|
6344
|
+
name: "failed MCP call without result keeps error message fallback",
|
|
6345
|
+
request: makeRequest3(),
|
|
6346
|
+
nativeStream: [
|
|
6347
|
+
threadStarted(NEW_THREAD_ID),
|
|
6348
|
+
toolFrame("item.completed", {
|
|
6349
|
+
id: "mcp-failed",
|
|
6350
|
+
type: "mcp_tool_call",
|
|
6351
|
+
server: "other",
|
|
6352
|
+
tool: "lookup",
|
|
6353
|
+
arguments: { q: "receipt" },
|
|
6354
|
+
status: "failed",
|
|
6355
|
+
result: { content: [], isError: true },
|
|
6356
|
+
error: { message: "connection closed" }
|
|
6357
|
+
}),
|
|
6358
|
+
turnCompleted()
|
|
6359
|
+
],
|
|
6360
|
+
expected: [
|
|
6361
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6362
|
+
{
|
|
6363
|
+
type: "tool",
|
|
6364
|
+
id: "mcp-failed",
|
|
6365
|
+
name: "other.lookup",
|
|
6366
|
+
phase: "start",
|
|
6367
|
+
summary: "receipt",
|
|
6368
|
+
input: { q: "receipt" }
|
|
6369
|
+
},
|
|
6370
|
+
{
|
|
6371
|
+
type: "tool",
|
|
6372
|
+
id: "mcp-failed",
|
|
6373
|
+
name: "other.lookup",
|
|
6374
|
+
phase: "error",
|
|
6375
|
+
summary: "receipt",
|
|
6376
|
+
input: { q: "receipt" },
|
|
6377
|
+
result: "connection closed"
|
|
6378
|
+
},
|
|
6379
|
+
{ type: "result", ok: true }
|
|
6380
|
+
]
|
|
6381
|
+
},
|
|
6382
|
+
{
|
|
6383
|
+
// CT1147 stays intact when a connector includes structured metadata beside an
|
|
6384
|
+
// image-only MCP response: no image/base64 payload enters activity receipts.
|
|
6385
|
+
name: "image-only MCP result remains empty despite structured metadata",
|
|
6386
|
+
request: makeRequest3(),
|
|
6387
|
+
nativeStream: [
|
|
6388
|
+
threadStarted(NEW_THREAD_ID),
|
|
6389
|
+
toolFrame("item.completed", {
|
|
6390
|
+
id: "mcp-image",
|
|
6391
|
+
type: "mcp_tool_call",
|
|
6392
|
+
server: "other",
|
|
6393
|
+
tool: "render",
|
|
6394
|
+
arguments: {},
|
|
6395
|
+
status: "completed",
|
|
6396
|
+
result: {
|
|
6397
|
+
content: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }],
|
|
6398
|
+
structured_content: { image: "base64-bytes" }
|
|
6399
|
+
}
|
|
6400
|
+
}),
|
|
6401
|
+
turnCompleted()
|
|
6402
|
+
],
|
|
6403
|
+
expected: [
|
|
6404
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
6405
|
+
{
|
|
6406
|
+
type: "tool",
|
|
6407
|
+
id: "mcp-image",
|
|
6408
|
+
name: "other.render",
|
|
6409
|
+
phase: "start",
|
|
6410
|
+
summary: "",
|
|
6411
|
+
input: {}
|
|
6412
|
+
},
|
|
6413
|
+
{
|
|
6414
|
+
type: "tool",
|
|
6415
|
+
id: "mcp-image",
|
|
6416
|
+
name: "other.render",
|
|
6417
|
+
phase: "done",
|
|
6418
|
+
summary: "",
|
|
6419
|
+
input: {},
|
|
6420
|
+
result: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }]
|
|
6421
|
+
},
|
|
6422
|
+
{ type: "result", ok: true }
|
|
6423
|
+
]
|
|
6424
|
+
},
|
|
6185
6425
|
{
|
|
6186
6426
|
// A streaming tool whose args arrive on a later snapshot: `item.started` carries
|
|
6187
6427
|
// empty args, `item.updated` fills them, `item.completed` finishes. The `start`
|
|
@@ -8010,7 +8250,8 @@ ${reason}`,
|
|
|
8010
8250
|
}
|
|
8011
8251
|
const turnEnv = {
|
|
8012
8252
|
...hookEnv,
|
|
8013
|
-
CABANE_HOST_ACCESS: turnContext.policy.hostFs ? "1" : "0"
|
|
8253
|
+
CABANE_HOST_ACCESS: turnContext.policy.hostFs ? "1" : "0",
|
|
8254
|
+
CABANE_COMPANION_HOME: agentCompanionHome()
|
|
8014
8255
|
};
|
|
8015
8256
|
const key = runKey(payload.conversationId, payload.agentId);
|
|
8016
8257
|
const abortController = new AbortController();
|
package/dist/runtime.js
CHANGED
|
@@ -224,6 +224,9 @@ function realAccountHome() {
|
|
|
224
224
|
}
|
|
225
225
|
return realHomeCache;
|
|
226
226
|
}
|
|
227
|
+
function agentCompanionHome() {
|
|
228
|
+
return join(cabaneDir(), "agent-home");
|
|
229
|
+
}
|
|
227
230
|
function cabaneDir() {
|
|
228
231
|
const dir2 = join(process.env.CABANE_COMPANION_HOME || homedir(), ".cabane");
|
|
229
232
|
if (process.env.VITEST) {
|
|
@@ -5029,6 +5032,7 @@ function readToolItem(item, eventType) {
|
|
|
5029
5032
|
if (type === "mcp_tool_call") {
|
|
5030
5033
|
const tool2 = str(item.tool) ?? "tool";
|
|
5031
5034
|
const server = str(item.server);
|
|
5035
|
+
const result = readMcpResult(item.result);
|
|
5032
5036
|
return {
|
|
5033
5037
|
id,
|
|
5034
5038
|
// A cabane tool reads best as its bare verb (`read`); a non-cabane server's
|
|
@@ -5036,7 +5040,7 @@ function readToolItem(item, eventType) {
|
|
|
5036
5040
|
name: server && server !== "cabane" ? `${server}.${tool2}` : tool2,
|
|
5037
5041
|
input: item.arguments,
|
|
5038
5042
|
status: toStatus(item.status),
|
|
5039
|
-
...item.status === "completed" ? { result
|
|
5043
|
+
...item.status === "completed" ? { result } : item.status === "failed" ? { result: hasMcpReceipt(result) ? result : readMcpError(item.error) } : {}
|
|
5040
5044
|
};
|
|
5041
5045
|
}
|
|
5042
5046
|
if (type === "web_search") {
|
|
@@ -5076,9 +5080,38 @@ function toStatus(v) {
|
|
|
5076
5080
|
}
|
|
5077
5081
|
function readMcpResult(result) {
|
|
5078
5082
|
const rec = asRecord2(result);
|
|
5079
|
-
if (rec
|
|
5083
|
+
if (!rec) return result;
|
|
5084
|
+
if (hasOnlyNonTextContentBlocks(rec.content)) return rec.content;
|
|
5085
|
+
if (rec.structured_content !== void 0 && rec.structured_content !== null) {
|
|
5086
|
+
return rec.structured_content;
|
|
5087
|
+
}
|
|
5088
|
+
if (rec.structuredContent !== void 0 && rec.structuredContent !== null) {
|
|
5089
|
+
return rec.structuredContent;
|
|
5090
|
+
}
|
|
5091
|
+
if ("content" in rec) return rec.content;
|
|
5080
5092
|
return result;
|
|
5081
5093
|
}
|
|
5094
|
+
function hasOnlyNonTextContentBlocks(content) {
|
|
5095
|
+
if (!Array.isArray(content) || content.length === 0) return false;
|
|
5096
|
+
let sawTypedBlock = false;
|
|
5097
|
+
for (const raw of content) {
|
|
5098
|
+
const block = asRecord2(raw);
|
|
5099
|
+
const type = block && str(block.type);
|
|
5100
|
+
if (!type) continue;
|
|
5101
|
+
sawTypedBlock = true;
|
|
5102
|
+
if (type === "text" && typeof block?.text === "string" && block.text.trim().length > 0) {
|
|
5103
|
+
return false;
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
return sawTypedBlock;
|
|
5107
|
+
}
|
|
5108
|
+
function hasMcpReceipt(result) {
|
|
5109
|
+
if (result === void 0 || result === null) return false;
|
|
5110
|
+
if (typeof result === "string") return result.trim().length > 0;
|
|
5111
|
+
if (Array.isArray(result)) return result.length > 0;
|
|
5112
|
+
if (typeof result === "object") return Object.keys(result).length > 0;
|
|
5113
|
+
return true;
|
|
5114
|
+
}
|
|
5082
5115
|
function readMcpError(error) {
|
|
5083
5116
|
const rec = asRecord2(error);
|
|
5084
5117
|
return rec && str(rec.message) || "error";
|
|
@@ -5602,6 +5635,25 @@ var errorItem = (id, message) => ({
|
|
|
5602
5635
|
type: "item.completed",
|
|
5603
5636
|
item: { id, type: "error", message }
|
|
5604
5637
|
});
|
|
5638
|
+
var SDK_SUCCESS = { ok: true, result: { source: "native" }, logs: "", truncated: false };
|
|
5639
|
+
var SDK_FAILURE = {
|
|
5640
|
+
ok: false,
|
|
5641
|
+
result: null,
|
|
5642
|
+
preview: '{"rows":[1,2,3]}',
|
|
5643
|
+
logs: "",
|
|
5644
|
+
truncated: false,
|
|
5645
|
+
error: { kind: "output_too_large", bytes: 169167, limit: 65536 },
|
|
5646
|
+
warnings: [],
|
|
5647
|
+
committed: {
|
|
5648
|
+
calls: [
|
|
5649
|
+
{
|
|
5650
|
+
method: "conversations.update",
|
|
5651
|
+
args: { id: "conv-1", opts: { title: "Renamed before failure" } }
|
|
5652
|
+
}
|
|
5653
|
+
]
|
|
5654
|
+
},
|
|
5655
|
+
inFlight: null
|
|
5656
|
+
};
|
|
5605
5657
|
var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint = fingerprintPrompt(PROMPT)) => ({
|
|
5606
5658
|
type: "session",
|
|
5607
5659
|
state: encodeSession3({ threadId, cwd, promptFingerprint }),
|
|
@@ -5681,6 +5733,194 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
5681
5733
|
{ type: "result", ok: true }
|
|
5682
5734
|
]
|
|
5683
5735
|
},
|
|
5736
|
+
{
|
|
5737
|
+
// CT1211: recorded SDK success shape. Codex exposes the text compatibility
|
|
5738
|
+
// copy and the native object together; normalization must keep the object.
|
|
5739
|
+
name: "MCP success prefers native structured content",
|
|
5740
|
+
request: makeRequest3(),
|
|
5741
|
+
nativeStream: [
|
|
5742
|
+
threadStarted(NEW_THREAD_ID),
|
|
5743
|
+
toolFrame("item.started", {
|
|
5744
|
+
id: "sdk-success",
|
|
5745
|
+
type: "mcp_tool_call",
|
|
5746
|
+
server: "cabane",
|
|
5747
|
+
tool: "sdk",
|
|
5748
|
+
arguments: { code: 'return { source: "native" }' },
|
|
5749
|
+
status: "in_progress"
|
|
5750
|
+
}),
|
|
5751
|
+
toolFrame("item.completed", {
|
|
5752
|
+
id: "sdk-success",
|
|
5753
|
+
type: "mcp_tool_call",
|
|
5754
|
+
server: "cabane",
|
|
5755
|
+
tool: "sdk",
|
|
5756
|
+
arguments: { code: 'return { source: "native" }' },
|
|
5757
|
+
status: "completed",
|
|
5758
|
+
result: {
|
|
5759
|
+
content: [{ type: "text", text: JSON.stringify(SDK_SUCCESS) }],
|
|
5760
|
+
structured_content: SDK_SUCCESS
|
|
5761
|
+
}
|
|
5762
|
+
}),
|
|
5763
|
+
turnCompleted()
|
|
5764
|
+
],
|
|
5765
|
+
expected: [
|
|
5766
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
5767
|
+
{
|
|
5768
|
+
type: "tool",
|
|
5769
|
+
id: "sdk-success",
|
|
5770
|
+
name: "sdk",
|
|
5771
|
+
phase: "start",
|
|
5772
|
+
summary: "",
|
|
5773
|
+
input: { code: 'return { source: "native" }' }
|
|
5774
|
+
},
|
|
5775
|
+
{
|
|
5776
|
+
type: "tool",
|
|
5777
|
+
id: "sdk-success",
|
|
5778
|
+
name: "sdk",
|
|
5779
|
+
phase: "done",
|
|
5780
|
+
summary: "",
|
|
5781
|
+
input: { code: 'return { source: "native" }' },
|
|
5782
|
+
result: SDK_SUCCESS
|
|
5783
|
+
},
|
|
5784
|
+
{ type: "result", ok: true }
|
|
5785
|
+
]
|
|
5786
|
+
},
|
|
5787
|
+
{
|
|
5788
|
+
// CT1211 incident shape: MCP `isError` keeps the failed phase, while the raw
|
|
5789
|
+
// camel-case structured payload survives instead of the generic `error` token.
|
|
5790
|
+
name: "failed SDK call preserves structured receipt while remaining error phase",
|
|
5791
|
+
request: makeRequest3(),
|
|
5792
|
+
nativeStream: [
|
|
5793
|
+
threadStarted(NEW_THREAD_ID),
|
|
5794
|
+
toolFrame("item.started", {
|
|
5795
|
+
id: "sdk-failed",
|
|
5796
|
+
type: "mcp_tool_call",
|
|
5797
|
+
server: "cabane",
|
|
5798
|
+
tool: "sdk",
|
|
5799
|
+
arguments: { code: "return largeResult" },
|
|
5800
|
+
status: "in_progress"
|
|
5801
|
+
}),
|
|
5802
|
+
toolFrame("item.completed", {
|
|
5803
|
+
id: "sdk-failed",
|
|
5804
|
+
type: "mcp_tool_call",
|
|
5805
|
+
server: "cabane",
|
|
5806
|
+
tool: "sdk",
|
|
5807
|
+
arguments: { code: "return largeResult" },
|
|
5808
|
+
status: "failed",
|
|
5809
|
+
result: {
|
|
5810
|
+
content: [{ type: "text", text: JSON.stringify(SDK_FAILURE) }],
|
|
5811
|
+
structuredContent: SDK_FAILURE,
|
|
5812
|
+
isError: true
|
|
5813
|
+
},
|
|
5814
|
+
error: { message: "error" }
|
|
5815
|
+
}),
|
|
5816
|
+
turnCompleted()
|
|
5817
|
+
],
|
|
5818
|
+
expected: [
|
|
5819
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
5820
|
+
{
|
|
5821
|
+
type: "tool",
|
|
5822
|
+
id: "sdk-failed",
|
|
5823
|
+
name: "sdk",
|
|
5824
|
+
phase: "start",
|
|
5825
|
+
summary: "",
|
|
5826
|
+
input: { code: "return largeResult" }
|
|
5827
|
+
},
|
|
5828
|
+
{
|
|
5829
|
+
type: "tool",
|
|
5830
|
+
id: "sdk-failed",
|
|
5831
|
+
name: "sdk",
|
|
5832
|
+
phase: "error",
|
|
5833
|
+
summary: "",
|
|
5834
|
+
input: { code: "return largeResult" },
|
|
5835
|
+
result: SDK_FAILURE
|
|
5836
|
+
},
|
|
5837
|
+
{ type: "result", ok: true }
|
|
5838
|
+
]
|
|
5839
|
+
},
|
|
5840
|
+
{
|
|
5841
|
+
// A transport/tool failure has no MCP result payload and still uses the real
|
|
5842
|
+
// adapter error message. CT1211 must not replace this fallback with `undefined`.
|
|
5843
|
+
name: "failed MCP call without result keeps error message fallback",
|
|
5844
|
+
request: makeRequest3(),
|
|
5845
|
+
nativeStream: [
|
|
5846
|
+
threadStarted(NEW_THREAD_ID),
|
|
5847
|
+
toolFrame("item.completed", {
|
|
5848
|
+
id: "mcp-failed",
|
|
5849
|
+
type: "mcp_tool_call",
|
|
5850
|
+
server: "other",
|
|
5851
|
+
tool: "lookup",
|
|
5852
|
+
arguments: { q: "receipt" },
|
|
5853
|
+
status: "failed",
|
|
5854
|
+
result: { content: [], isError: true },
|
|
5855
|
+
error: { message: "connection closed" }
|
|
5856
|
+
}),
|
|
5857
|
+
turnCompleted()
|
|
5858
|
+
],
|
|
5859
|
+
expected: [
|
|
5860
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
5861
|
+
{
|
|
5862
|
+
type: "tool",
|
|
5863
|
+
id: "mcp-failed",
|
|
5864
|
+
name: "other.lookup",
|
|
5865
|
+
phase: "start",
|
|
5866
|
+
summary: "receipt",
|
|
5867
|
+
input: { q: "receipt" }
|
|
5868
|
+
},
|
|
5869
|
+
{
|
|
5870
|
+
type: "tool",
|
|
5871
|
+
id: "mcp-failed",
|
|
5872
|
+
name: "other.lookup",
|
|
5873
|
+
phase: "error",
|
|
5874
|
+
summary: "receipt",
|
|
5875
|
+
input: { q: "receipt" },
|
|
5876
|
+
result: "connection closed"
|
|
5877
|
+
},
|
|
5878
|
+
{ type: "result", ok: true }
|
|
5879
|
+
]
|
|
5880
|
+
},
|
|
5881
|
+
{
|
|
5882
|
+
// CT1147 stays intact when a connector includes structured metadata beside an
|
|
5883
|
+
// image-only MCP response: no image/base64 payload enters activity receipts.
|
|
5884
|
+
name: "image-only MCP result remains empty despite structured metadata",
|
|
5885
|
+
request: makeRequest3(),
|
|
5886
|
+
nativeStream: [
|
|
5887
|
+
threadStarted(NEW_THREAD_ID),
|
|
5888
|
+
toolFrame("item.completed", {
|
|
5889
|
+
id: "mcp-image",
|
|
5890
|
+
type: "mcp_tool_call",
|
|
5891
|
+
server: "other",
|
|
5892
|
+
tool: "render",
|
|
5893
|
+
arguments: {},
|
|
5894
|
+
status: "completed",
|
|
5895
|
+
result: {
|
|
5896
|
+
content: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }],
|
|
5897
|
+
structured_content: { image: "base64-bytes" }
|
|
5898
|
+
}
|
|
5899
|
+
}),
|
|
5900
|
+
turnCompleted()
|
|
5901
|
+
],
|
|
5902
|
+
expected: [
|
|
5903
|
+
sessionEvent3(NEW_THREAD_ID),
|
|
5904
|
+
{
|
|
5905
|
+
type: "tool",
|
|
5906
|
+
id: "mcp-image",
|
|
5907
|
+
name: "other.render",
|
|
5908
|
+
phase: "start",
|
|
5909
|
+
summary: "",
|
|
5910
|
+
input: {}
|
|
5911
|
+
},
|
|
5912
|
+
{
|
|
5913
|
+
type: "tool",
|
|
5914
|
+
id: "mcp-image",
|
|
5915
|
+
name: "other.render",
|
|
5916
|
+
phase: "done",
|
|
5917
|
+
summary: "",
|
|
5918
|
+
input: {},
|
|
5919
|
+
result: [{ type: "image", data: "base64-bytes", mimeType: "image/png" }]
|
|
5920
|
+
},
|
|
5921
|
+
{ type: "result", ok: true }
|
|
5922
|
+
]
|
|
5923
|
+
},
|
|
5684
5924
|
{
|
|
5685
5925
|
// A streaming tool whose args arrive on a later snapshot: `item.started` carries
|
|
5686
5926
|
// empty args, `item.updated` fills them, `item.completed` finishes. The `start`
|
|
@@ -7509,7 +7749,8 @@ ${reason}`,
|
|
|
7509
7749
|
}
|
|
7510
7750
|
const turnEnv = {
|
|
7511
7751
|
...hookEnv,
|
|
7512
|
-
CABANE_HOST_ACCESS: turnContext.policy.hostFs ? "1" : "0"
|
|
7752
|
+
CABANE_HOST_ACCESS: turnContext.policy.hostFs ? "1" : "0",
|
|
7753
|
+
CABANE_COMPANION_HOME: agentCompanionHome()
|
|
7513
7754
|
};
|
|
7514
7755
|
const key = runKey(payload.conversationId, payload.agentId);
|
|
7515
7756
|
const abortController = new AbortController();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cabane/companion",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.53",
|
|
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",
|