@cabane/companion 0.6.61 → 0.6.63
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 +162 -349
- package/dist/runtime.js +130 -340
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3061,6 +3061,15 @@ var turnEventSchema = z5.discriminatedUnion("type", [
|
|
|
3061
3061
|
effort: z5.string().optional(),
|
|
3062
3062
|
thinking: z5.string().optional(),
|
|
3063
3063
|
reasoningEffort: z5.string().optional()
|
|
3064
|
+
}).optional(),
|
|
3065
|
+
// CT1275: the harness-reported MCP inventory from this turn's init frame.
|
|
3066
|
+
// This is deliberately diagnostic-only: names, statuses and a count, never
|
|
3067
|
+
// server definitions, credentials or session ids. `initReceived:false`
|
|
3068
|
+
// distinguishes a missing init frame from a real empty inventory.
|
|
3069
|
+
mcpInventory: z5.object({
|
|
3070
|
+
initReceived: z5.boolean(),
|
|
3071
|
+
servers: z5.array(z5.object({ name: z5.string(), status: z5.string() })),
|
|
3072
|
+
toolCount: z5.number().int().nonnegative()
|
|
3064
3073
|
}).optional()
|
|
3065
3074
|
})
|
|
3066
3075
|
]);
|
|
@@ -3079,6 +3088,7 @@ var turnResultReasonSchema = z6.discriminatedUnion("kind", [
|
|
|
3079
3088
|
z6.object({ kind: z6.literal("timeout_total") }),
|
|
3080
3089
|
z6.object({ kind: z6.literal("cancelled") }),
|
|
3081
3090
|
z6.object({ kind: z6.literal("skipped") }),
|
|
3091
|
+
z6.object({ kind: z6.literal("workspace_tools_missing") }),
|
|
3082
3092
|
z6.object({ kind: z6.literal("runtime_error") })
|
|
3083
3093
|
]);
|
|
3084
3094
|
var turnOutcomes = ["success", "failure", "cancelled", "skipped"];
|
|
@@ -3104,7 +3114,12 @@ var turnDiagnosticsSchema = z6.object({
|
|
|
3104
3114
|
result: z6.number().int().nonnegative()
|
|
3105
3115
|
}),
|
|
3106
3116
|
runtimeResultKind: z6.enum(turnRuntimeResultKinds).nullable(),
|
|
3107
|
-
finalSource: z6.enum(turnFinalSources)
|
|
3117
|
+
finalSource: z6.enum(turnFinalSources),
|
|
3118
|
+
mcpInventory: z6.object({
|
|
3119
|
+
initReceived: z6.boolean(),
|
|
3120
|
+
servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
|
|
3121
|
+
toolCount: z6.number().int().nonnegative()
|
|
3122
|
+
}).optional()
|
|
3108
3123
|
});
|
|
3109
3124
|
|
|
3110
3125
|
// packages/agent-runtime/src/failure.ts
|
|
@@ -3238,6 +3253,8 @@ function normalizeTurnResultReason(reason) {
|
|
|
3238
3253
|
return { kind: "cancelled" };
|
|
3239
3254
|
case "skipped":
|
|
3240
3255
|
return { kind: "skipped" };
|
|
3256
|
+
case "workspace_tools_missing":
|
|
3257
|
+
return { kind: "workspace_tools_missing" };
|
|
3241
3258
|
default:
|
|
3242
3259
|
return { kind: "runtime_error" };
|
|
3243
3260
|
}
|
|
@@ -3342,12 +3359,7 @@ var turnRequestSchema = z8.object({
|
|
|
3342
3359
|
// adapters' conformance fixtures, tests) keeps parsing unchanged — the companion
|
|
3343
3360
|
// always populates it (`build-options.ts`), and the native adapter fails the
|
|
3344
3361
|
// turn loudly when it is somehow absent rather than guessing.
|
|
3345
|
-
workspaceId: z8.string().optional()
|
|
3346
|
-
// CT752: the server-resolved workspace surface this credential exposes.
|
|
3347
|
-
// Readiness uses this explicit fact to require `sdk` for code mode and the
|
|
3348
|
-
// granular floor for classic mode; inventory contents alone cannot infer it
|
|
3349
|
-
// because `sdk` is intentionally also available on the classic surface.
|
|
3350
|
-
workspaceToolSurface: z8.enum(["code", "classic"]).optional()
|
|
3362
|
+
workspaceId: z8.string().optional()
|
|
3351
3363
|
}),
|
|
3352
3364
|
// Machine-local resolution (host-filled): the checkout cwd, extra env from a
|
|
3353
3365
|
// prepare hook, and the resolved user MCP servers.
|
|
@@ -3932,6 +3944,8 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
3932
3944
|
out.push(event);
|
|
3933
3945
|
};
|
|
3934
3946
|
let sessionEmitted = false;
|
|
3947
|
+
let workspaceProven = false;
|
|
3948
|
+
let mcpInventory = { initReceived: false, servers: [], toolCount: 0 };
|
|
3935
3949
|
let ok = false;
|
|
3936
3950
|
let resultReason;
|
|
3937
3951
|
let sawResult = false;
|
|
@@ -3949,6 +3963,12 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
3949
3963
|
if (msg.type === "system" && msg.subtype === "init") {
|
|
3950
3964
|
const initModel = msg.model;
|
|
3951
3965
|
if (typeof initModel === "string" && initModel.length > 0) resolvedModel = initModel;
|
|
3966
|
+
mcpInventory = readMcpInventory(msg);
|
|
3967
|
+
workspaceProven = provesWorkspaceTools(msg);
|
|
3968
|
+
if (!workspaceProven) {
|
|
3969
|
+
resultReason = "workspace_tools_missing";
|
|
3970
|
+
break;
|
|
3971
|
+
}
|
|
3952
3972
|
const sdkSessionId = msg.session_id;
|
|
3953
3973
|
if (!sessionEmitted && sdkSessionId && sdkSessionId !== ctx.resumedSessionId) {
|
|
3954
3974
|
sessionEmitted = true;
|
|
@@ -3958,6 +3978,9 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
3958
3978
|
...ctx.degraded ? { degraded: true } : {}
|
|
3959
3979
|
};
|
|
3960
3980
|
}
|
|
3981
|
+
} else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
|
|
3982
|
+
resultReason = "workspace_tools_missing";
|
|
3983
|
+
break;
|
|
3961
3984
|
} else if (msg.type === "assistant") {
|
|
3962
3985
|
const assistantErr = msg.error;
|
|
3963
3986
|
if (typeof assistantErr === "string" && assistantErr.length > 0) {
|
|
@@ -4021,6 +4044,10 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4021
4044
|
sawResult = true;
|
|
4022
4045
|
}
|
|
4023
4046
|
if (ctx.signal.aborted) return;
|
|
4047
|
+
if (!workspaceProven) {
|
|
4048
|
+
ok = false;
|
|
4049
|
+
resultReason ??= "workspace_tools_missing";
|
|
4050
|
+
}
|
|
4024
4051
|
await flushHeldText(buffer, emit, ok);
|
|
4025
4052
|
yield* drain(out);
|
|
4026
4053
|
if (!ok && !resultReason && !sawResult) resultReason = "no_result";
|
|
@@ -4029,9 +4056,25 @@ async function* decodeSdkStream(iter, ctx) {
|
|
|
4029
4056
|
ok,
|
|
4030
4057
|
...resultReason ? { reason: resultReason } : {},
|
|
4031
4058
|
...usage ? { usage } : {},
|
|
4032
|
-
...resolvedModel ? { resolvedModel } : {}
|
|
4059
|
+
...resolvedModel ? { resolvedModel } : {},
|
|
4060
|
+
mcpInventory
|
|
4033
4061
|
};
|
|
4034
4062
|
}
|
|
4063
|
+
function readMcpInventory(msg) {
|
|
4064
|
+
const frame = msg;
|
|
4065
|
+
const servers = Array.isArray(frame.mcp_servers) ? frame.mcp_servers.flatMap(
|
|
4066
|
+
(server) => typeof server?.name === "string" && typeof server.status === "string" ? [{ name: server.name, status: server.status }] : []
|
|
4067
|
+
) : [];
|
|
4068
|
+
return {
|
|
4069
|
+
initReceived: true,
|
|
4070
|
+
servers,
|
|
4071
|
+
toolCount: Array.isArray(frame.tools) ? frame.tools.length : 0
|
|
4072
|
+
};
|
|
4073
|
+
}
|
|
4074
|
+
function provesWorkspaceTools(msg) {
|
|
4075
|
+
const tools = msg.tools;
|
|
4076
|
+
return Array.isArray(tools) && tools.includes("mcp__cabane__sdk");
|
|
4077
|
+
}
|
|
4035
4078
|
function isSubscriptionWindow(value) {
|
|
4036
4079
|
return value === "five_hour" || value === "seven_day" || value === "seven_day_opus" || value === "seven_day_sonnet" || value === "overage";
|
|
4037
4080
|
}
|
|
@@ -4130,6 +4173,11 @@ var CABANE_POLICY = {
|
|
|
4130
4173
|
uiPrompts: "never"
|
|
4131
4174
|
};
|
|
4132
4175
|
var CWD = "/env/here";
|
|
4176
|
+
var HEALTHY_MCP_INVENTORY = {
|
|
4177
|
+
initReceived: true,
|
|
4178
|
+
servers: [{ name: "cabane", status: "connected" }],
|
|
4179
|
+
toolCount: 1
|
|
4180
|
+
};
|
|
4133
4181
|
function makeRequest(overrides = {}) {
|
|
4134
4182
|
return {
|
|
4135
4183
|
systemPrompt: "system",
|
|
@@ -4150,7 +4198,8 @@ var init = (sessionId, model) => ({
|
|
|
4150
4198
|
type: "system",
|
|
4151
4199
|
subtype: "init",
|
|
4152
4200
|
session_id: sessionId,
|
|
4153
|
-
mcp_servers:
|
|
4201
|
+
mcp_servers: HEALTHY_MCP_INVENTORY.servers,
|
|
4202
|
+
tools: ["mcp__cabane__sdk"],
|
|
4154
4203
|
...model ? { model } : {}
|
|
4155
4204
|
});
|
|
4156
4205
|
var assistantText = (text) => ({
|
|
@@ -4211,7 +4260,7 @@ var sessionEvent = (sdkSessionId, cwd = CWD, degraded = false) => ({
|
|
|
4211
4260
|
state: encodeSession({ sdkSessionId, cwd }),
|
|
4212
4261
|
...degraded ? { degraded: true } : {}
|
|
4213
4262
|
});
|
|
4214
|
-
var
|
|
4263
|
+
var BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES = [
|
|
4215
4264
|
{
|
|
4216
4265
|
// A plain text reply: the held text-only block flushes as the terminal final.
|
|
4217
4266
|
name: "clean turn",
|
|
@@ -4591,6 +4640,12 @@ var CLAUDE_CODE_CONFORMANCE_FIXTURES = [
|
|
|
4591
4640
|
]
|
|
4592
4641
|
}
|
|
4593
4642
|
];
|
|
4643
|
+
var CLAUDE_CODE_CONFORMANCE_FIXTURES = BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES.map((fixture) => ({
|
|
4644
|
+
...fixture,
|
|
4645
|
+
expected: fixture.expected.map(
|
|
4646
|
+
(event) => event.type === "result" ? { ...event, mcpInventory: HEALTHY_MCP_INVENTORY } : event
|
|
4647
|
+
)
|
|
4648
|
+
}));
|
|
4594
4649
|
|
|
4595
4650
|
// packages/agent-runtime/src/registry.ts
|
|
4596
4651
|
function createAdapterRegistry(adapters) {
|
|
@@ -7096,7 +7151,7 @@ var ConnectorHealthStore = class {
|
|
|
7096
7151
|
|
|
7097
7152
|
// src/dispatcher.ts
|
|
7098
7153
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
7099
|
-
import {
|
|
7154
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
7100
7155
|
import { join as join14 } from "path";
|
|
7101
7156
|
|
|
7102
7157
|
// src/turn-control-tools.ts
|
|
@@ -7118,16 +7173,32 @@ var WAKE_ME_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WAKE_ME_TOOL}`;
|
|
|
7118
7173
|
var CANCEL_WAKE_TOOL = "cancel_wake";
|
|
7119
7174
|
var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_TOOL}`;
|
|
7120
7175
|
function createReplyState() {
|
|
7121
|
-
return { answersMessageId: null };
|
|
7176
|
+
return { answersMessageId: null, order: null };
|
|
7122
7177
|
}
|
|
7123
7178
|
function createSendState() {
|
|
7124
|
-
return { agentId: null, message: null };
|
|
7179
|
+
return { agentId: null, message: null, order: null };
|
|
7180
|
+
}
|
|
7181
|
+
function createTurnControlOrder() {
|
|
7182
|
+
let calls = 0;
|
|
7183
|
+
return {
|
|
7184
|
+
next: () => {
|
|
7185
|
+
calls += 1;
|
|
7186
|
+
return calls;
|
|
7187
|
+
}
|
|
7188
|
+
};
|
|
7125
7189
|
}
|
|
7126
7190
|
function createSkipState() {
|
|
7127
7191
|
return { skipped: false, reason: null };
|
|
7128
7192
|
}
|
|
7129
7193
|
function createAskState() {
|
|
7130
|
-
return {
|
|
7194
|
+
return {
|
|
7195
|
+
targetUserId: null,
|
|
7196
|
+
question: null,
|
|
7197
|
+
headline: null,
|
|
7198
|
+
options: null,
|
|
7199
|
+
questions: null,
|
|
7200
|
+
order: null
|
|
7201
|
+
};
|
|
7131
7202
|
}
|
|
7132
7203
|
function createWakeState() {
|
|
7133
7204
|
return { afterSeconds: null, at: null, note: null, cancelled: false };
|
|
@@ -7157,7 +7228,7 @@ function wakeCommitField(state) {
|
|
|
7157
7228
|
}
|
|
7158
7229
|
};
|
|
7159
7230
|
}
|
|
7160
|
-
function createTurnControlMcpServer(sendState, skipState, askState, subAgentCreate, wakeState, replyState) {
|
|
7231
|
+
function createTurnControlMcpServer(sendState, skipState, askState, subAgentCreate, wakeState, replyState, controlOrder) {
|
|
7161
7232
|
return createSdkMcpServer({
|
|
7162
7233
|
name: COMPANION_LOCAL_MCP_SERVER,
|
|
7163
7234
|
version: "0.0.0",
|
|
@@ -7174,6 +7245,7 @@ function createTurnControlMcpServer(sendState, skipState, askState, subAgentCrea
|
|
|
7174
7245
|
async (args) => {
|
|
7175
7246
|
sendState.agentId = args.agentId;
|
|
7176
7247
|
sendState.message = args.message;
|
|
7248
|
+
sendState.order = controlOrder?.next() ?? null;
|
|
7177
7249
|
return {
|
|
7178
7250
|
content: [{ type: "text", text: JSON.stringify({ sent: args.agentId }) }]
|
|
7179
7251
|
};
|
|
@@ -7189,6 +7261,7 @@ function createTurnControlMcpServer(sendState, skipState, askState, subAgentCrea
|
|
|
7189
7261
|
},
|
|
7190
7262
|
async (args) => {
|
|
7191
7263
|
replyState.answersMessageId = args.messageId;
|
|
7264
|
+
replyState.order = controlOrder?.next() ?? null;
|
|
7192
7265
|
return {
|
|
7193
7266
|
content: [
|
|
7194
7267
|
{
|
|
@@ -7271,6 +7344,7 @@ function createTurnControlMcpServer(sendState, skipState, askState, subAgentCrea
|
|
|
7271
7344
|
};
|
|
7272
7345
|
}
|
|
7273
7346
|
askState.targetUserId = args.targetUserId;
|
|
7347
|
+
askState.order = controlOrder?.next() ?? null;
|
|
7274
7348
|
if (hasArray) {
|
|
7275
7349
|
askState.questions = args.questions;
|
|
7276
7350
|
askState.question = null;
|
|
@@ -7433,7 +7507,6 @@ function buildCompanionTurnRequest(params) {
|
|
|
7433
7507
|
bearer: params.turnToken ?? params.agentPat,
|
|
7434
7508
|
activeConversationId: params.activeConversationId,
|
|
7435
7509
|
workspaceId: params.workspaceId,
|
|
7436
|
-
...t.workspaceToolSurface ? { workspaceToolSurface: t.workspaceToolSurface } : {},
|
|
7437
7510
|
// CT714: mount the turn-control surface ONLY when a real turn token backs
|
|
7438
7511
|
// this turn — the surface admits `turn_token` auth exclusively, so a
|
|
7439
7512
|
// PAT-fallback bearer would be rejected there. Absent it, external adapters
|
|
@@ -7835,12 +7908,31 @@ var TurnCommitter = class {
|
|
|
7835
7908
|
// a wordless turn has no answer to bind, and the server's mute-settle
|
|
7836
7909
|
// notice speaks for it.
|
|
7837
7910
|
turnControlFields(kind) {
|
|
7838
|
-
|
|
7911
|
+
const fields = {
|
|
7839
7912
|
...this.answersField(kind),
|
|
7840
7913
|
...this.sendField(),
|
|
7841
7914
|
...this.askField(),
|
|
7842
7915
|
...this.wakeField()
|
|
7843
7916
|
};
|
|
7917
|
+
return { ...fields, ...this.orderField(fields) };
|
|
7918
|
+
}
|
|
7919
|
+
// CT1281: the order the control tools were CALLED, for the addressed rows the
|
|
7920
|
+
// server writes from this one commit. It reports a position only for an intent
|
|
7921
|
+
// that actually made it into the commit — an ask whose payload was incomplete,
|
|
7922
|
+
// or a self-send the committer stripped, contributes no row and so has no place
|
|
7923
|
+
// in the line. An intent with no recorded call (the runtime's auto-declared
|
|
7924
|
+
// reply) is deliberately absent: the server sorts it last, which is what it is.
|
|
7925
|
+
orderField(fields) {
|
|
7926
|
+
const order = {};
|
|
7927
|
+
const replyOrder = this.deps.replyState.order;
|
|
7928
|
+
if (fields.answersMessageId !== void 0 && replyOrder !== null) order.reply = replyOrder;
|
|
7929
|
+
if (fields.dispatch !== void 0 && this.deps.sendState.order !== null) {
|
|
7930
|
+
order.send = this.deps.sendState.order;
|
|
7931
|
+
}
|
|
7932
|
+
if (fields.ask !== void 0 && this.deps.askState.order !== null) {
|
|
7933
|
+
order.ask = this.deps.askState.order;
|
|
7934
|
+
}
|
|
7935
|
+
return Object.keys(order).length > 0 ? { turnControlOrder: order } : {};
|
|
7844
7936
|
}
|
|
7845
7937
|
// The declared reply: explicit `reply_to` first, else the runtime's own
|
|
7846
7938
|
// declaration for the turn that just answers — see
|
|
@@ -7909,173 +8001,6 @@ var TurnCommitter = class {
|
|
|
7909
8001
|
}
|
|
7910
8002
|
};
|
|
7911
8003
|
|
|
7912
|
-
// src/workspace-readiness.ts
|
|
7913
|
-
var CLASSIC_REQUIRED = ["read", "list", "search", "write", "edit"];
|
|
7914
|
-
var INITIALIZE_RETRY_DELAYS_MS = [250, 750, 1500];
|
|
7915
|
-
var INITIALIZE_ATTEMPT_TIMEOUT_MS = 4e3;
|
|
7916
|
-
var INITIALIZE_RETRY_BUDGET_MS = 1e4;
|
|
7917
|
-
async function proveWorkspaceTools(req, runtime, opts = {}) {
|
|
7918
|
-
const base = {
|
|
7919
|
-
ok: false,
|
|
7920
|
-
proofType: "authenticated_mcp_tools_list",
|
|
7921
|
-
runtime,
|
|
7922
|
-
harnessFingerprint: opts.harnessFingerprint ?? runtime,
|
|
7923
|
-
endpoint: safeEndpoint(req.cabane.mcpUrl),
|
|
7924
|
-
initialized: false,
|
|
7925
|
-
authenticated: false,
|
|
7926
|
-
discoveredTools: [],
|
|
7927
|
-
requiredTools: [],
|
|
7928
|
-
acceptedNames: ["sdk", "mcp__cabane__sdk"],
|
|
7929
|
-
failedCapability: null,
|
|
7930
|
-
detail: null
|
|
7931
|
-
};
|
|
7932
|
-
if (!req.cabane.mcpUrl) return fail(base, "server_not_configured", "Cabane MCP URL absent");
|
|
7933
|
-
if (!req.cabane.bearer) return fail(base, "authentication_failed", "Cabane bearer absent");
|
|
7934
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
7935
|
-
const headers = {
|
|
7936
|
-
authorization: `Bearer ${req.cabane.bearer}`,
|
|
7937
|
-
accept: "application/json, text/event-stream",
|
|
7938
|
-
"content-type": "application/json",
|
|
7939
|
-
"x-cabane-active-conversation": req.cabane.activeConversationId
|
|
7940
|
-
};
|
|
7941
|
-
try {
|
|
7942
|
-
const initialized = await initializeWithRetry(
|
|
7943
|
-
fetchImpl,
|
|
7944
|
-
req.cabane.mcpUrl,
|
|
7945
|
-
headers,
|
|
7946
|
-
{
|
|
7947
|
-
jsonrpc: "2.0",
|
|
7948
|
-
id: 1,
|
|
7949
|
-
method: "initialize",
|
|
7950
|
-
params: {
|
|
7951
|
-
protocolVersion: "2025-03-26",
|
|
7952
|
-
capabilities: {},
|
|
7953
|
-
clientInfo: { name: "cabane-companion-readiness", version: "1" }
|
|
7954
|
-
}
|
|
7955
|
-
},
|
|
7956
|
-
opts.retryDelaysMs ?? INITIALIZE_RETRY_DELAYS_MS
|
|
7957
|
-
);
|
|
7958
|
-
if (initialized.status === 401 || initialized.status === 403)
|
|
7959
|
-
return fail(base, "authentication_failed", `initialize returned HTTP ${initialized.status}`);
|
|
7960
|
-
if (initialized.transient)
|
|
7961
|
-
return fail(base, "workspace_endpoint_unreachable", initialized.detail);
|
|
7962
|
-
if (!initialized.ok) return fail(base, "initialization_failed", initialized.detail);
|
|
7963
|
-
base.initialized = true;
|
|
7964
|
-
base.authenticated = true;
|
|
7965
|
-
if (initialized.sessionId) headers["mcp-session-id"] = initialized.sessionId;
|
|
7966
|
-
const listed = await rpc(fetchImpl, req.cabane.mcpUrl, headers, {
|
|
7967
|
-
jsonrpc: "2.0",
|
|
7968
|
-
id: 2,
|
|
7969
|
-
method: "tools/list",
|
|
7970
|
-
params: {}
|
|
7971
|
-
});
|
|
7972
|
-
if (listed.status === 401 || listed.status === 403)
|
|
7973
|
-
return fail(base, "authentication_failed", `tools/list returned HTTP ${listed.status}`);
|
|
7974
|
-
if (!listed.ok) return fail(base, "tool_discovery_failed", listed.detail);
|
|
7975
|
-
const result = asRecord3(asRecord3(listed.value)?.result);
|
|
7976
|
-
const tools = Array.isArray(result?.tools) ? result.tools : null;
|
|
7977
|
-
if (!tools) return fail(base, "tool_discovery_failed", "tools/list returned no tool inventory");
|
|
7978
|
-
base.discoveredTools = tools.map(
|
|
7979
|
-
(tool2) => tool2 && typeof tool2 === "object" && typeof tool2.name === "string" ? tool2.name : null
|
|
7980
|
-
).filter((name) => name !== null).sort();
|
|
7981
|
-
if (!req.cabane.workspaceToolSurface)
|
|
7982
|
-
return fail(base, "required_tool_missing", "resolved workspace tool surface absent");
|
|
7983
|
-
base.requiredTools = req.cabane.workspaceToolSurface === "code" ? ["sdk"] : CLASSIC_REQUIRED;
|
|
7984
|
-
const missing = base.requiredTools.filter((name) => !base.discoveredTools.includes(name));
|
|
7985
|
-
if (missing.length > 0)
|
|
7986
|
-
return fail(
|
|
7987
|
-
base,
|
|
7988
|
-
"required_tool_missing",
|
|
7989
|
-
`missing initialized tools: ${missing.join(", ")}`
|
|
7990
|
-
);
|
|
7991
|
-
base.ok = true;
|
|
7992
|
-
return base;
|
|
7993
|
-
} catch (error) {
|
|
7994
|
-
return fail(
|
|
7995
|
-
base,
|
|
7996
|
-
"workspace_endpoint_unreachable",
|
|
7997
|
-
error instanceof Error ? error.message : String(error)
|
|
7998
|
-
);
|
|
7999
|
-
}
|
|
8000
|
-
}
|
|
8001
|
-
async function initializeWithRetry(fetchImpl, url, headers, body, retryDelaysMs) {
|
|
8002
|
-
let lastFailure = null;
|
|
8003
|
-
const deadline = Date.now() + INITIALIZE_RETRY_BUDGET_MS;
|
|
8004
|
-
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt += 1) {
|
|
8005
|
-
try {
|
|
8006
|
-
const remainingMs = deadline - Date.now();
|
|
8007
|
-
if (remainingMs <= 0) break;
|
|
8008
|
-
const result = await rpc(
|
|
8009
|
-
fetchImpl,
|
|
8010
|
-
url,
|
|
8011
|
-
headers,
|
|
8012
|
-
body,
|
|
8013
|
-
Math.min(INITIALIZE_ATTEMPT_TIMEOUT_MS, remainingMs)
|
|
8014
|
-
);
|
|
8015
|
-
if (result.status === 401 || result.status === 403 || result.ok) return result;
|
|
8016
|
-
if (result.status < 500) return result;
|
|
8017
|
-
lastFailure = { ...result, transient: true };
|
|
8018
|
-
} catch (error) {
|
|
8019
|
-
lastFailure = {
|
|
8020
|
-
ok: false,
|
|
8021
|
-
status: 0,
|
|
8022
|
-
sessionId: null,
|
|
8023
|
-
value: null,
|
|
8024
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
8025
|
-
transient: true
|
|
8026
|
-
};
|
|
8027
|
-
}
|
|
8028
|
-
const retryDelayMs = retryDelaysMs[attempt];
|
|
8029
|
-
if (retryDelayMs === void 0 || Date.now() + retryDelayMs >= deadline) break;
|
|
8030
|
-
await delay(retryDelayMs);
|
|
8031
|
-
}
|
|
8032
|
-
return lastFailure;
|
|
8033
|
-
}
|
|
8034
|
-
function delay(ms) {
|
|
8035
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8036
|
-
}
|
|
8037
|
-
function fail(proof, capability, detail) {
|
|
8038
|
-
proof.failedCapability = capability;
|
|
8039
|
-
proof.detail = detail.slice(0, 300);
|
|
8040
|
-
return proof;
|
|
8041
|
-
}
|
|
8042
|
-
function safeEndpoint(value) {
|
|
8043
|
-
try {
|
|
8044
|
-
const url = new URL(value);
|
|
8045
|
-
return `${url.origin}${url.pathname}`;
|
|
8046
|
-
} catch {
|
|
8047
|
-
return null;
|
|
8048
|
-
}
|
|
8049
|
-
}
|
|
8050
|
-
async function rpc(fetchImpl, url, headers, body, timeoutMs) {
|
|
8051
|
-
const response = await fetchImpl(url, {
|
|
8052
|
-
method: "POST",
|
|
8053
|
-
headers,
|
|
8054
|
-
body: JSON.stringify(body),
|
|
8055
|
-
...timeoutMs ? { signal: AbortSignal.timeout(timeoutMs) } : {}
|
|
8056
|
-
});
|
|
8057
|
-
const text = await response.text();
|
|
8058
|
-
const value = parseRpcBody(text);
|
|
8059
|
-
return {
|
|
8060
|
-
ok: response.ok && !!value && !value.error,
|
|
8061
|
-
status: response.status,
|
|
8062
|
-
sessionId: response.headers.get("mcp-session-id"),
|
|
8063
|
-
value,
|
|
8064
|
-
detail: typeof asRecord3(value?.error)?.message === "string" ? String(asRecord3(value?.error)?.message) : `HTTP ${response.status}`
|
|
8065
|
-
};
|
|
8066
|
-
}
|
|
8067
|
-
function parseRpcBody(text) {
|
|
8068
|
-
const trimmed = text.trim();
|
|
8069
|
-
if (trimmed.startsWith("{")) return JSON.parse(trimmed);
|
|
8070
|
-
for (const line of trimmed.split("\n")) {
|
|
8071
|
-
if (line.startsWith("data:")) return JSON.parse(line.slice(5).trim());
|
|
8072
|
-
}
|
|
8073
|
-
return null;
|
|
8074
|
-
}
|
|
8075
|
-
function asRecord3(value) {
|
|
8076
|
-
return value !== null && typeof value === "object" ? value : null;
|
|
8077
|
-
}
|
|
8078
|
-
|
|
8079
8004
|
// src/dispatcher.ts
|
|
8080
8005
|
var PREPARING_TOOL_NAME = "preparing";
|
|
8081
8006
|
var PREPARE_FAILED_PREFIX = "**Couldn't prepare your environment.** I wasn't able to provision a working directory for this conversation, so I can't run this turn. The provisioning command reported:";
|
|
@@ -8466,6 +8391,7 @@ ${reason}`,
|
|
|
8466
8391
|
const askState = createAskState();
|
|
8467
8392
|
const wakeState = createWakeState();
|
|
8468
8393
|
const replyState = createReplyState();
|
|
8394
|
+
const controlOrder = createTurnControlOrder();
|
|
8469
8395
|
let spawnedSubAgent = false;
|
|
8470
8396
|
const subAgentCreate = async (args) => {
|
|
8471
8397
|
const dispatchTarget = args.agentId ?? payload.agentId;
|
|
@@ -8497,7 +8423,8 @@ ${reason}`,
|
|
|
8497
8423
|
askState,
|
|
8498
8424
|
subAgentCreate,
|
|
8499
8425
|
wakeState,
|
|
8500
|
-
replyState
|
|
8426
|
+
replyState,
|
|
8427
|
+
controlOrder
|
|
8501
8428
|
);
|
|
8502
8429
|
const request = buildCompanionTurnRequest({
|
|
8503
8430
|
turnContext,
|
|
@@ -8568,152 +8495,7 @@ ${reason}`,
|
|
|
8568
8495
|
`runtime_unavailable:${err.runtime}`
|
|
8569
8496
|
);
|
|
8570
8497
|
}
|
|
8571
|
-
let turnReceiptPath = null;
|
|
8572
8498
|
const totalTimeoutMs = this.opts.totalTimeoutMs ?? DEFAULT_AGENT_TOTAL_TIMEOUT_MS;
|
|
8573
|
-
const closeTurnReceipt = (ok, reason) => {
|
|
8574
|
-
if (!turnReceiptPath) return;
|
|
8575
|
-
const target = turnReceiptPath;
|
|
8576
|
-
turnReceiptPath = null;
|
|
8577
|
-
try {
|
|
8578
|
-
appendFileSync2(
|
|
8579
|
-
target,
|
|
8580
|
-
`${JSON.stringify({
|
|
8581
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8582
|
-
event: "settled",
|
|
8583
|
-
turnId,
|
|
8584
|
-
agentId: payload.agentId,
|
|
8585
|
-
conversationId: payload.conversationId,
|
|
8586
|
-
ok,
|
|
8587
|
-
reason
|
|
8588
|
-
})}
|
|
8589
|
-
`,
|
|
8590
|
-
{ mode: 384 }
|
|
8591
|
-
);
|
|
8592
|
-
} catch (error) {
|
|
8593
|
-
turnLog.warn(
|
|
8594
|
-
{ err: error instanceof Error ? error.message : String(error) },
|
|
8595
|
-
"dispatcher: turn-settled diagnostic write failed"
|
|
8596
|
-
);
|
|
8597
|
-
}
|
|
8598
|
-
};
|
|
8599
|
-
if (prepareHook && hookEnv?.CABANE_TASK_ID) {
|
|
8600
|
-
const checkout = checkoutState(effectiveCwd);
|
|
8601
|
-
if (!effectiveCwd || !checkout.ok) {
|
|
8602
|
-
const reason = `checkout_missing: ${checkout.reason}; task=${hookEnv.CABANE_TASK_ID}; recovery=re-dispatch this conversation (the prepare hook re-provisions the environment)`;
|
|
8603
|
-
turnLog.error({ checkout: effectiveCwd ?? null, checkoutState: checkout }, reason);
|
|
8604
|
-
try {
|
|
8605
|
-
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
8606
|
-
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
8607
|
-
kind: "final",
|
|
8608
|
-
turnId,
|
|
8609
|
-
parentMessageId: payload.messageId
|
|
8610
|
-
});
|
|
8611
|
-
} catch (postErr) {
|
|
8612
|
-
turnLog.warn(
|
|
8613
|
-
{ err: postErr instanceof Error ? postErr.message : String(postErr) },
|
|
8614
|
-
"dispatcher: checkout-missing notice post failed"
|
|
8615
|
-
);
|
|
8616
|
-
}
|
|
8617
|
-
return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
|
|
8618
|
-
}
|
|
8619
|
-
const receiptPath = join14(effectiveCwd, ".git", "cabane", "readiness.jsonl");
|
|
8620
|
-
const receiptLine = (fields) => `${JSON.stringify({
|
|
8621
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8622
|
-
taskId: hookEnv.CABANE_TASK_ID,
|
|
8623
|
-
binding: hookEnv.CABANE_TASK_BINDING ?? null,
|
|
8624
|
-
checkout: effectiveCwd,
|
|
8625
|
-
// CT1022: the two fields the environment reaper reads — which turn this
|
|
8626
|
-
// is (so its settle can be matched among interleaved agents) and how long
|
|
8627
|
-
// it may legitimately run (so an unclosed receipt expires on this turn's
|
|
8628
|
-
// real deadline, not the reaper's guess).
|
|
8629
|
-
turnId,
|
|
8630
|
-
totalTimeoutMs,
|
|
8631
|
-
// CT1062: WHOSE turn. A task env is shared — between agents, and between
|
|
8632
|
-
// an agent's own sequential conversations — so a reader asking "is a turn
|
|
8633
|
-
// of THIS agent, other than mine, running here?" (the host's run-lock
|
|
8634
|
-
// does, before it lets a second conversation into the tree) can only
|
|
8635
|
-
// answer it if the line says who. An unattributed open line has to count
|
|
8636
|
-
// for everyone, which refuses work that should have been admitted.
|
|
8637
|
-
agentId: payload.agentId,
|
|
8638
|
-
conversationId: payload.conversationId,
|
|
8639
|
-
...fields
|
|
8640
|
-
})}
|
|
8641
|
-
`;
|
|
8642
|
-
try {
|
|
8643
|
-
mkdirSync10(join14(effectiveCwd, ".git", "cabane"), { recursive: true });
|
|
8644
|
-
appendFileSync2(
|
|
8645
|
-
receiptPath,
|
|
8646
|
-
// `starting` is the honest classification before the proof has run. The
|
|
8647
|
-
// line the proof appends below carries the same `turnId`, so a reader
|
|
8648
|
-
// replaying the file sees one turn, not two.
|
|
8649
|
-
receiptLine({ classification: "starting" }),
|
|
8650
|
-
{ mode: 384 }
|
|
8651
|
-
);
|
|
8652
|
-
turnReceiptPath = receiptPath;
|
|
8653
|
-
} catch (error) {
|
|
8654
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
8655
|
-
const reason = `turn_receipt_unwritable: ${detail}; task=${hookEnv.CABANE_TASK_ID}; checkout=${effectiveCwd}; recovery=restore write access to the checkout's .git/cabane, then re-dispatch`;
|
|
8656
|
-
turnLog.error({ err: detail, receiptPath }, reason);
|
|
8657
|
-
try {
|
|
8658
|
-
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
8659
|
-
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
8660
|
-
kind: "final",
|
|
8661
|
-
turnId,
|
|
8662
|
-
parentMessageId: payload.messageId
|
|
8663
|
-
});
|
|
8664
|
-
} catch (postErr) {
|
|
8665
|
-
turnLog.warn(
|
|
8666
|
-
{ err: postErr instanceof Error ? postErr.message : String(postErr) },
|
|
8667
|
-
"dispatcher: turn-receipt failure notice post failed"
|
|
8668
|
-
);
|
|
8669
|
-
}
|
|
8670
|
-
return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
|
|
8671
|
-
}
|
|
8672
|
-
const proof = await proveWorkspaceTools(request, adapter.name, {
|
|
8673
|
-
...this.opts.workspaceProofFetch ? { fetchImpl: this.opts.workspaceProofFetch } : {},
|
|
8674
|
-
...this.opts.workspaceProofRetryDelaysMs ? { retryDelaysMs: this.opts.workspaceProofRetryDelaysMs } : {},
|
|
8675
|
-
harnessFingerprint: turnContext.runtime
|
|
8676
|
-
});
|
|
8677
|
-
turnLog[proof.ok ? "info" : "error"](
|
|
8678
|
-
{ workspaceProof: proof, checkout: effectiveCwd },
|
|
8679
|
-
`dispatcher: workspace tool proof ${proof.ok ? "passed" : "failed"}, checkout usable`
|
|
8680
|
-
);
|
|
8681
|
-
try {
|
|
8682
|
-
appendFileSync2(
|
|
8683
|
-
receiptPath,
|
|
8684
|
-
receiptLine({
|
|
8685
|
-
classification: proof.ok ? "ready" : "workspace_tools_missing",
|
|
8686
|
-
failedCapability: proof.failedCapability,
|
|
8687
|
-
workspaceTools: proof
|
|
8688
|
-
}),
|
|
8689
|
-
{ mode: 384 }
|
|
8690
|
-
);
|
|
8691
|
-
} catch (error) {
|
|
8692
|
-
turnLog.warn(
|
|
8693
|
-
{ err: error instanceof Error ? error.message : String(error) },
|
|
8694
|
-
"dispatcher: workspace-proof diagnostic write failed (the turn receipt is open)"
|
|
8695
|
-
);
|
|
8696
|
-
}
|
|
8697
|
-
if (!proof.ok) {
|
|
8698
|
-
const recovery = proof.failedCapability === "workspace_endpoint_unreachable" ? "the Cabane workspace endpoint was unreachable; retry this dispatch" : "restart the connector after restoring the Cabane workspace tool mount";
|
|
8699
|
-
const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd}; runtime=${adapter.name}; recovery=${recovery}`;
|
|
8700
|
-
closeTurnReceipt(false, reason);
|
|
8701
|
-
try {
|
|
8702
|
-
await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
|
|
8703
|
-
body: `**Couldn't prepare your environment.** ${reason}`,
|
|
8704
|
-
kind: "final",
|
|
8705
|
-
turnId,
|
|
8706
|
-
parentMessageId: payload.messageId
|
|
8707
|
-
});
|
|
8708
|
-
} catch (postErr) {
|
|
8709
|
-
turnLog.warn(
|
|
8710
|
-
{ err: postErr instanceof Error ? postErr.message : String(postErr) },
|
|
8711
|
-
"dispatcher: workspace-proof failure notice post failed"
|
|
8712
|
-
);
|
|
8713
|
-
}
|
|
8714
|
-
return this.concludeBeforeRun(payload, turnLog, startedAt, reason);
|
|
8715
|
-
}
|
|
8716
|
-
}
|
|
8717
8499
|
const transcript2 = this.opts.transcriptDir ? new TranscriptWriter(
|
|
8718
8500
|
this.opts.transcriptDir,
|
|
8719
8501
|
{
|
|
@@ -8736,6 +8518,7 @@ ${reason}`,
|
|
|
8736
8518
|
let turnUsage;
|
|
8737
8519
|
let turnResolvedModel;
|
|
8738
8520
|
let turnResolvedConfig;
|
|
8521
|
+
let turnMcpInventory;
|
|
8739
8522
|
const eventCounts = {
|
|
8740
8523
|
session: 0,
|
|
8741
8524
|
text: 0,
|
|
@@ -8760,11 +8543,12 @@ ${reason}`,
|
|
|
8760
8543
|
signal: abortController.signal,
|
|
8761
8544
|
log: turnLog,
|
|
8762
8545
|
nextSeq,
|
|
8763
|
-
// The committer reads this at commit to
|
|
8764
|
-
//
|
|
8546
|
+
// The committer reads this at commit to carry the addressed send on the
|
|
8547
|
+
// terminal row; the server writes the send itself as a distinct message.
|
|
8765
8548
|
sendState,
|
|
8766
|
-
// CT326: likewise the ask payload
|
|
8767
|
-
//
|
|
8549
|
+
// CT326: likewise the ask payload. CT1281: the server writes the ask as its
|
|
8550
|
+
// own addressed message to the human — which ENQUEUES like any other send —
|
|
8551
|
+
// and creates the `asks` row against that carrier, not against turn speech.
|
|
8768
8552
|
askState,
|
|
8769
8553
|
// CT442: likewise the wake payload — attached to the `final` row so the
|
|
8770
8554
|
// server arms the wake schedule atomically with the reply it rode on.
|
|
@@ -8789,6 +8573,7 @@ ${reason}`,
|
|
|
8789
8573
|
);
|
|
8790
8574
|
if (intent.ask) {
|
|
8791
8575
|
askState.targetUserId = intent.ask.targetUserId;
|
|
8576
|
+
askState.order = intent.askOrder ?? null;
|
|
8792
8577
|
if (intent.ask.questions && intent.ask.questions.length > 0) {
|
|
8793
8578
|
askState.questions = intent.ask.questions;
|
|
8794
8579
|
askState.question = null;
|
|
@@ -8817,8 +8602,12 @@ ${reason}`,
|
|
|
8817
8602
|
if (intent.sendAgentId && intent.sendBody) {
|
|
8818
8603
|
sendState.agentId = intent.sendAgentId;
|
|
8819
8604
|
sendState.message = intent.sendBody;
|
|
8605
|
+
sendState.order = intent.sendOrder ?? null;
|
|
8606
|
+
}
|
|
8607
|
+
if (intent.answersMessageId) {
|
|
8608
|
+
replyState.answersMessageId = intent.answersMessageId;
|
|
8609
|
+
replyState.order = intent.replyOrder ?? null;
|
|
8820
8610
|
}
|
|
8821
|
-
if (intent.answersMessageId) replyState.answersMessageId = intent.answersMessageId;
|
|
8822
8611
|
if (intent.skipped) {
|
|
8823
8612
|
skipState.skipped = true;
|
|
8824
8613
|
skipState.reason = intent.skipReason;
|
|
@@ -8901,6 +8690,7 @@ ${reason}`,
|
|
|
8901
8690
|
turnUsage = event.usage;
|
|
8902
8691
|
turnResolvedModel = event.resolvedModel;
|
|
8903
8692
|
turnResolvedConfig = event.resolvedConfig;
|
|
8693
|
+
turnMcpInventory = event.mcpInventory;
|
|
8904
8694
|
runtimeResultKind = event.ok ? "success" : event.reason === "no_terminal" ? "no_terminal" : "error";
|
|
8905
8695
|
} else if (event.type === "text" && skipState.skipped) {
|
|
8906
8696
|
} else {
|
|
@@ -8977,7 +8767,6 @@ ${reason}`,
|
|
|
8977
8767
|
} finally {
|
|
8978
8768
|
if (idleTimer) clearTimeout(idleTimer);
|
|
8979
8769
|
clearTimeout(totalTimer);
|
|
8980
|
-
closeTurnReceipt(okResult, resultReason ?? null);
|
|
8981
8770
|
const userCancelled = abortController.signal.aborted && timeoutReason === null;
|
|
8982
8771
|
if (timeoutReason !== null) {
|
|
8983
8772
|
resultReason = timeoutReason;
|
|
@@ -9059,6 +8848,7 @@ ${reason}`,
|
|
|
9059
8848
|
sessionFingerprint: fingerprintSessionState(latestSessionState),
|
|
9060
8849
|
eventCounts,
|
|
9061
8850
|
runtimeResultKind,
|
|
8851
|
+
...turnMcpInventory ? { mcpInventory: turnMcpInventory } : {},
|
|
9062
8852
|
finalSource: outcome === "skipped" || outcome === "cancelled" || silentMarkerEmitted ? "marker" : committer.finalSource
|
|
9063
8853
|
};
|
|
9064
8854
|
body.diagnostics = settledDiagnostics;
|
|
@@ -9208,7 +8998,7 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
|
|
|
9208
8998
|
// src/outbox.ts
|
|
9209
8999
|
import {
|
|
9210
9000
|
existsSync as existsSync11,
|
|
9211
|
-
mkdirSync as
|
|
9001
|
+
mkdirSync as mkdirSync10,
|
|
9212
9002
|
readdirSync as readdirSync3,
|
|
9213
9003
|
readFileSync as readFileSync8,
|
|
9214
9004
|
renameSync as renameSync3,
|
|
@@ -9238,7 +9028,7 @@ var Outbox = class {
|
|
|
9238
9028
|
// per-workspace bounds.
|
|
9239
9029
|
persist(entry) {
|
|
9240
9030
|
const dir2 = this.dir();
|
|
9241
|
-
|
|
9031
|
+
mkdirSync10(dir2, { recursive: true });
|
|
9242
9032
|
const target = this.fileFor(entry.turnId, entry.seq);
|
|
9243
9033
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
9244
9034
|
try {
|
|
@@ -10509,14 +10299,14 @@ function handleUncaught(log, err, origin) {
|
|
|
10509
10299
|
}
|
|
10510
10300
|
|
|
10511
10301
|
// src/crash-marker.ts
|
|
10512
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
10302
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync9, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
10513
10303
|
import { join as join16 } from "path";
|
|
10514
10304
|
function crashMarkerPath() {
|
|
10515
10305
|
return join16(cabaneDir(), "last-error.json");
|
|
10516
10306
|
}
|
|
10517
10307
|
function recordCrash(rec2) {
|
|
10518
10308
|
try {
|
|
10519
|
-
|
|
10309
|
+
mkdirSync11(cabaneDir(), { recursive: true });
|
|
10520
10310
|
writeFileSync8(crashMarkerPath(), JSON.stringify(rec2, null, 2) + "\n");
|
|
10521
10311
|
} catch {
|
|
10522
10312
|
}
|
|
@@ -10696,7 +10486,7 @@ async function closeSurfaces(control, dashboard) {
|
|
|
10696
10486
|
|
|
10697
10487
|
// src/commands/daemon.ts
|
|
10698
10488
|
import { spawn as spawn4 } from "child_process";
|
|
10699
|
-
import { closeSync as closeSync3, mkdirSync as
|
|
10489
|
+
import { closeSync as closeSync3, mkdirSync as mkdirSync12, openSync as openSync3 } from "fs";
|
|
10700
10490
|
|
|
10701
10491
|
// src/cli-entry.ts
|
|
10702
10492
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -10799,7 +10589,7 @@ Stop: cabane-companion stop
|
|
|
10799
10589
|
}
|
|
10800
10590
|
function defaultSpawnDetached(args) {
|
|
10801
10591
|
const cliPath = companionCliEntry();
|
|
10802
|
-
|
|
10592
|
+
mkdirSync12(cabaneDir(), { recursive: true });
|
|
10803
10593
|
const logFd = openSync3(companionLogPath(), "a");
|
|
10804
10594
|
try {
|
|
10805
10595
|
return spawn4(process.execPath, [cliPath, ...args], {
|
|
@@ -11439,12 +11229,12 @@ function renderTranscript(jsonlLines) {
|
|
|
11439
11229
|
case "system":
|
|
11440
11230
|
if (str2(obj.subtype) === "init") {
|
|
11441
11231
|
out.push(`[session ${str2(obj.session_id) || "?"} \xB7 model ${str2(obj.model) || "?"}]`);
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11232
|
+
out.push(
|
|
11233
|
+
...mcpInventoryLines(
|
|
11234
|
+
obj.mcp_servers,
|
|
11235
|
+
Array.isArray(obj.tools) ? obj.tools.length : void 0
|
|
11236
|
+
)
|
|
11237
|
+
);
|
|
11448
11238
|
out.push("");
|
|
11449
11239
|
}
|
|
11450
11240
|
break;
|
|
@@ -11477,11 +11267,24 @@ function renderTranscript(jsonlLines) {
|
|
|
11477
11267
|
break;
|
|
11478
11268
|
}
|
|
11479
11269
|
case "result": {
|
|
11480
|
-
const isErr = obj.is_error === true || str2(obj.subtype) !== "success";
|
|
11270
|
+
const isErr = typeof obj.ok === "boolean" ? obj.ok !== true : obj.is_error === true || str2(obj.subtype) !== "success";
|
|
11481
11271
|
const dur = typeof obj.duration_ms === "number" ? ` \xB7 ${obj.duration_ms}ms` : "";
|
|
11482
11272
|
out.push(
|
|
11483
11273
|
`[result ${isErr ? "error" : "ok"}${str2(obj.subtype) ? ` \xB7 ${str2(obj.subtype)}` : ""}${dur}]`
|
|
11484
11274
|
);
|
|
11275
|
+
const inventory = rec(obj.mcpInventory);
|
|
11276
|
+
if (inventory) {
|
|
11277
|
+
if (inventory.initReceived === false) {
|
|
11278
|
+
out.push(" MCP init: missing");
|
|
11279
|
+
} else {
|
|
11280
|
+
out.push(
|
|
11281
|
+
...mcpInventoryLines(
|
|
11282
|
+
inventory.servers,
|
|
11283
|
+
typeof inventory.toolCount === "number" ? inventory.toolCount : void 0
|
|
11284
|
+
)
|
|
11285
|
+
);
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11485
11288
|
if (isErr && str2(obj.result).trim()) out.push(` ${indent(str2(obj.result))}`);
|
|
11486
11289
|
break;
|
|
11487
11290
|
}
|
|
@@ -11496,6 +11299,16 @@ function renderTranscript(jsonlLines) {
|
|
|
11496
11299
|
}
|
|
11497
11300
|
return out.join("\n");
|
|
11498
11301
|
}
|
|
11302
|
+
function mcpInventoryLines(serversValue, toolCount) {
|
|
11303
|
+
const servers = Array.isArray(serversValue) ? serversValue.map((server) => {
|
|
11304
|
+
const value = rec(server);
|
|
11305
|
+
return value ? `${str2(value.name) || "?"}${str2(value.status) ? `(${str2(value.status)})` : ""}` : "";
|
|
11306
|
+
}).filter(Boolean).join(", ") : "";
|
|
11307
|
+
return [
|
|
11308
|
+
...servers ? [` MCP servers: ${servers}`] : [],
|
|
11309
|
+
...typeof toolCount === "number" ? [` tools: ${toolCount} available`] : []
|
|
11310
|
+
];
|
|
11311
|
+
}
|
|
11499
11312
|
function safeParse(s) {
|
|
11500
11313
|
try {
|
|
11501
11314
|
return JSON.parse(s);
|