@cabane/companion 0.6.28 → 0.6.29

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
@@ -7200,6 +7200,9 @@ var TurnCommitter = class {
7200
7200
 
7201
7201
  // src/workspace-readiness.ts
7202
7202
  var CLASSIC_REQUIRED = ["read", "list", "search", "write", "edit"];
7203
+ var INITIALIZE_RETRY_DELAYS_MS = [250, 750, 1500];
7204
+ var INITIALIZE_ATTEMPT_TIMEOUT_MS = 4e3;
7205
+ var INITIALIZE_RETRY_BUDGET_MS = 1e4;
7203
7206
  async function proveWorkspaceTools(req, runtime, opts = {}) {
7204
7207
  const base = {
7205
7208
  ok: false,
@@ -7225,7 +7228,7 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
7225
7228
  "x-cabane-active-conversation": req.cabane.activeConversationId
7226
7229
  };
7227
7230
  try {
7228
- const initialized = await rpc(fetchImpl, req.cabane.mcpUrl, headers, {
7231
+ const initialized = await initializeWithRetry(fetchImpl, req.cabane.mcpUrl, headers, {
7229
7232
  jsonrpc: "2.0",
7230
7233
  id: 1,
7231
7234
  method: "initialize",
@@ -7237,6 +7240,8 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
7237
7240
  });
7238
7241
  if (initialized.status === 401 || initialized.status === 403)
7239
7242
  return fail(base, "authentication_failed", `initialize returned HTTP ${initialized.status}`);
7243
+ if (initialized.transient)
7244
+ return fail(base, "workspace_endpoint_unreachable", initialized.detail);
7240
7245
  if (!initialized.ok) return fail(base, "initialization_failed", initialized.detail);
7241
7246
  base.initialized = true;
7242
7247
  base.authenticated = true;
@@ -7271,11 +7276,47 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
7271
7276
  } catch (error) {
7272
7277
  return fail(
7273
7278
  base,
7274
- "initialization_failed",
7279
+ "workspace_endpoint_unreachable",
7275
7280
  error instanceof Error ? error.message : String(error)
7276
7281
  );
7277
7282
  }
7278
7283
  }
7284
+ async function initializeWithRetry(fetchImpl, url, headers, body) {
7285
+ let lastFailure = null;
7286
+ const deadline = Date.now() + INITIALIZE_RETRY_BUDGET_MS;
7287
+ for (let attempt = 0; attempt <= INITIALIZE_RETRY_DELAYS_MS.length; attempt += 1) {
7288
+ try {
7289
+ const remainingMs = deadline - Date.now();
7290
+ if (remainingMs <= 0) break;
7291
+ const result = await rpc(
7292
+ fetchImpl,
7293
+ url,
7294
+ headers,
7295
+ body,
7296
+ Math.min(INITIALIZE_ATTEMPT_TIMEOUT_MS, remainingMs)
7297
+ );
7298
+ if (result.status === 401 || result.status === 403 || result.ok) return result;
7299
+ if (result.status < 500) return result;
7300
+ lastFailure = { ...result, transient: true };
7301
+ } catch (error) {
7302
+ lastFailure = {
7303
+ ok: false,
7304
+ status: 0,
7305
+ sessionId: null,
7306
+ value: null,
7307
+ detail: error instanceof Error ? error.message : String(error),
7308
+ transient: true
7309
+ };
7310
+ }
7311
+ const retryDelayMs = INITIALIZE_RETRY_DELAYS_MS[attempt];
7312
+ if (retryDelayMs === void 0 || Date.now() + retryDelayMs >= deadline) break;
7313
+ await delay(retryDelayMs);
7314
+ }
7315
+ return lastFailure;
7316
+ }
7317
+ function delay(ms) {
7318
+ return new Promise((resolve) => setTimeout(resolve, ms));
7319
+ }
7279
7320
  function fail(proof, capability, detail) {
7280
7321
  proof.failedCapability = capability;
7281
7322
  proof.detail = detail.slice(0, 300);
@@ -7289,8 +7330,13 @@ function safeEndpoint(value) {
7289
7330
  return null;
7290
7331
  }
7291
7332
  }
7292
- async function rpc(fetchImpl, url, headers, body) {
7293
- const response = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body) });
7333
+ async function rpc(fetchImpl, url, headers, body, timeoutMs) {
7334
+ const response = await fetchImpl(url, {
7335
+ method: "POST",
7336
+ headers,
7337
+ body: JSON.stringify(body),
7338
+ ...timeoutMs ? { signal: AbortSignal.timeout(timeoutMs) } : {}
7339
+ });
7294
7340
  const text = await response.text();
7295
7341
  const value = parseRpcBody(text);
7296
7342
  return {
@@ -7927,7 +7973,8 @@ ${reason}`,
7927
7973
  );
7928
7974
  }
7929
7975
  if (!proof.ok) {
7930
- const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd}; runtime=${adapter.name}; recovery=restart the connector after restoring the Cabane workspace tool mount`;
7976
+ 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";
7977
+ const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd}; runtime=${adapter.name}; recovery=${recovery}`;
7931
7978
  closeTurnReceipt(false, reason);
7932
7979
  try {
7933
7980
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
package/dist/runtime.js CHANGED
@@ -6312,6 +6312,9 @@ var TurnCommitter = class {
6312
6312
 
6313
6313
  // src/workspace-readiness.ts
6314
6314
  var CLASSIC_REQUIRED = ["read", "list", "search", "write", "edit"];
6315
+ var INITIALIZE_RETRY_DELAYS_MS = [250, 750, 1500];
6316
+ var INITIALIZE_ATTEMPT_TIMEOUT_MS = 4e3;
6317
+ var INITIALIZE_RETRY_BUDGET_MS = 1e4;
6315
6318
  async function proveWorkspaceTools(req, runtime, opts = {}) {
6316
6319
  const base = {
6317
6320
  ok: false,
@@ -6337,7 +6340,7 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
6337
6340
  "x-cabane-active-conversation": req.cabane.activeConversationId
6338
6341
  };
6339
6342
  try {
6340
- const initialized = await rpc(fetchImpl, req.cabane.mcpUrl, headers, {
6343
+ const initialized = await initializeWithRetry(fetchImpl, req.cabane.mcpUrl, headers, {
6341
6344
  jsonrpc: "2.0",
6342
6345
  id: 1,
6343
6346
  method: "initialize",
@@ -6349,6 +6352,8 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
6349
6352
  });
6350
6353
  if (initialized.status === 401 || initialized.status === 403)
6351
6354
  return fail(base, "authentication_failed", `initialize returned HTTP ${initialized.status}`);
6355
+ if (initialized.transient)
6356
+ return fail(base, "workspace_endpoint_unreachable", initialized.detail);
6352
6357
  if (!initialized.ok) return fail(base, "initialization_failed", initialized.detail);
6353
6358
  base.initialized = true;
6354
6359
  base.authenticated = true;
@@ -6383,11 +6388,47 @@ async function proveWorkspaceTools(req, runtime, opts = {}) {
6383
6388
  } catch (error) {
6384
6389
  return fail(
6385
6390
  base,
6386
- "initialization_failed",
6391
+ "workspace_endpoint_unreachable",
6387
6392
  error instanceof Error ? error.message : String(error)
6388
6393
  );
6389
6394
  }
6390
6395
  }
6396
+ async function initializeWithRetry(fetchImpl, url, headers, body) {
6397
+ let lastFailure = null;
6398
+ const deadline = Date.now() + INITIALIZE_RETRY_BUDGET_MS;
6399
+ for (let attempt = 0; attempt <= INITIALIZE_RETRY_DELAYS_MS.length; attempt += 1) {
6400
+ try {
6401
+ const remainingMs = deadline - Date.now();
6402
+ if (remainingMs <= 0) break;
6403
+ const result = await rpc(
6404
+ fetchImpl,
6405
+ url,
6406
+ headers,
6407
+ body,
6408
+ Math.min(INITIALIZE_ATTEMPT_TIMEOUT_MS, remainingMs)
6409
+ );
6410
+ if (result.status === 401 || result.status === 403 || result.ok) return result;
6411
+ if (result.status < 500) return result;
6412
+ lastFailure = { ...result, transient: true };
6413
+ } catch (error) {
6414
+ lastFailure = {
6415
+ ok: false,
6416
+ status: 0,
6417
+ sessionId: null,
6418
+ value: null,
6419
+ detail: error instanceof Error ? error.message : String(error),
6420
+ transient: true
6421
+ };
6422
+ }
6423
+ const retryDelayMs = INITIALIZE_RETRY_DELAYS_MS[attempt];
6424
+ if (retryDelayMs === void 0 || Date.now() + retryDelayMs >= deadline) break;
6425
+ await delay(retryDelayMs);
6426
+ }
6427
+ return lastFailure;
6428
+ }
6429
+ function delay(ms) {
6430
+ return new Promise((resolve) => setTimeout(resolve, ms));
6431
+ }
6391
6432
  function fail(proof, capability, detail) {
6392
6433
  proof.failedCapability = capability;
6393
6434
  proof.detail = detail.slice(0, 300);
@@ -6401,8 +6442,13 @@ function safeEndpoint(value) {
6401
6442
  return null;
6402
6443
  }
6403
6444
  }
6404
- async function rpc(fetchImpl, url, headers, body) {
6405
- const response = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body) });
6445
+ async function rpc(fetchImpl, url, headers, body, timeoutMs) {
6446
+ const response = await fetchImpl(url, {
6447
+ method: "POST",
6448
+ headers,
6449
+ body: JSON.stringify(body),
6450
+ ...timeoutMs ? { signal: AbortSignal.timeout(timeoutMs) } : {}
6451
+ });
6406
6452
  const text = await response.text();
6407
6453
  const value = parseRpcBody(text);
6408
6454
  return {
@@ -7039,7 +7085,8 @@ ${reason}`,
7039
7085
  );
7040
7086
  }
7041
7087
  if (!proof.ok) {
7042
- const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd}; runtime=${adapter.name}; recovery=restart the connector after restoring the Cabane workspace tool mount`;
7088
+ 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";
7089
+ const reason = `workspace_tools_missing: ${proof.failedCapability}; checkout=${effectiveCwd}; runtime=${adapter.name}; recovery=${recovery}`;
7043
7090
  closeTurnReceipt(false, reason);
7044
7091
  try {
7045
7092
  await this.opts.api.postTurnMessage(workspaceId, payload.conversationId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.28",
3
+ "version": "0.6.29",
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",