@codeagentswarm/cas-cloud 0.0.6 → 0.0.15

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.
Files changed (3) hide show
  1. package/README.md +14 -1
  2. package/dist/cas.js +523 -98
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,19 @@ bundled CodeAgentSwarm MCP. It also installs the guarded global instructions
22
22
  that publish each session's title, activity and work-phase status. Run
23
23
  `cas-cli setup` to perform that same setup explicitly.
24
24
 
25
+ To connect this host to CodeAgentSwarm Desktop, keep `serve` running and ask it
26
+ for a temporary Desktop link:
27
+
28
+ ```sh
29
+ cas-cli connect
30
+ ```
31
+
32
+ Open the printed link on the Mac, or paste it into **Remote devices**. Desktop
33
+ reviews and saves the host; its projects and open sessions then appear in the
34
+ normal Agents list and New Agent launcher while the VPS is online. The link is
35
+ single-use, expires after five minutes, uses the existing encrypted relay and
36
+ does not open an inbound VPS port.
37
+
25
38
  To let a Cloud session read or start work on your Mac, enable **Session
26
39
  communication** on the Mac, create a Mobile Connect pairing code, and keep the
27
40
  Cloud service running while you link it:
@@ -38,7 +51,7 @@ list eligible Mac sessions, read a bounded user/assistant transcript, or list an
38
51
  opaque project and start one new Mac session with a prompt when you explicitly
39
52
  ask. Only assistant prose returns; paths, reasoning and tool output do not.
40
53
 
41
- Desktop's ordinary CAS Cloud pairing provides the reverse direction. With both
54
+ The Desktop connection created by `cas-cli connect` provides the reverse direction. With both
42
55
  links approved, a Mac session can perform the same explicit read, remote start,
43
56
  or focused request/response exchange with an eligible CAS Cloud session. Those
44
57
  messages stay end-to-end encrypted and are not retained for replay by the relay.
package/dist/cas.js CHANGED
@@ -8393,6 +8393,9 @@ var require_mcp_entry_ownership = __commonJS({
8393
8393
  function hasStaleSourceCheckoutRuntime(text) {
8394
8394
  const candidates = [text.trim(), ...[...text.matchAll(/["']([^"']+)["']/g)].map((match) => match[1])];
8395
8395
  return candidates.some((candidate) => {
8396
+ if (candidate.includes(WORKTREE_MARKER)) return false;
8397
+ const ownedPaths = [OWNED_SOURCE_RUNTIME, OWNED_SOURCE_LAUNCHER].map(normalizeRuntimePath);
8398
+ if (ownedPaths.some((owned) => candidate !== owned && candidate.includes(owned))) return false;
8396
8399
  if (!SOURCE_CHECKOUT_SUFFIXES.some((suffix) => candidate.endsWith(suffix))) return false;
8397
8400
  if (!/^(?:[a-z]:\/|\/)/i.test(candidate)) return false;
8398
8401
  const withoutRoot = candidate.replace(/^[a-z]:\//i, "").replace(/^\/+/, "");
@@ -18501,10 +18504,144 @@ var require_jsonrpc_line_parser = __commonJS({
18501
18504
  }
18502
18505
  });
18503
18506
 
18507
+ // src/shared/provider-error-presentation.js
18508
+ var require_provider_error_presentation = __commonJS({
18509
+ "src/shared/provider-error-presentation.js"(exports2, module2) {
18510
+ var MODEL_USAGE_LIMIT_CODE = "model_usage_limit_reached";
18511
+ var MODEL_USAGE_LIMIT_MESSAGE = "This model's usage limit has been reached. Switch models or try again after the limit resets.";
18512
+ var USAGE_LIMIT_CODES = /* @__PURE__ */ new Set([
18513
+ "billinghardlimitreached",
18514
+ "creditsexhausted",
18515
+ "insufficientquota",
18516
+ "modelusagelimitreached",
18517
+ "quotaexhausted",
18518
+ "ratelimit",
18519
+ "ratelimited",
18520
+ "ratelimiterror",
18521
+ "ratelimitreached",
18522
+ "resourceexhausted",
18523
+ "usagelimit",
18524
+ "usagelimitexceeded",
18525
+ "workspaceownerusagelimitreached",
18526
+ "workspacememberusagelimitreached"
18527
+ ]);
18528
+ var USAGE_LIMIT_MESSAGE_PATTERN = /(?:credit balance is too low|credits? (?:have been )?exhausted|insufficient[ _-]quota|too many requests|(?:model|rate|usage) limit (?:has been )?(?:exceeded|reached)|(?:hit|reached|exceeded) (?:(?:your|the) )?(?:model|rate|usage) limit|quota (?:has been )?(?:exceeded|exhausted|reached)|resource[ _-]exhausted)/i;
18529
+ function structuredErrorInfoValues(value) {
18530
+ if (!value || typeof value !== "object") return [value];
18531
+ return Object.entries(value).flatMap(([kind, detail]) => [
18532
+ kind,
18533
+ typeof detail === "string" || typeof detail === "number" ? detail : void 0,
18534
+ detail == null ? void 0 : detail.httpStatusCode,
18535
+ detail == null ? void 0 : detail.statusCode,
18536
+ detail == null ? void 0 : detail.http_status
18537
+ ]);
18538
+ }
18539
+ function errorSignalValues(event) {
18540
+ var _a, _b;
18541
+ const payload = (event == null ? void 0 : event.payload) || {};
18542
+ const payloadErrorData = payload.errorData || {};
18543
+ const detail = payload.detail || {};
18544
+ const detailError = detail.error || {};
18545
+ const detailData = detail.data || {};
18546
+ const detailErrorData = detailError.data || {};
18547
+ const rawPayload = ((_a = event == null ? void 0 : event.raw) == null ? void 0 : _a.payload) || {};
18548
+ const rawError = rawPayload.error || ((_b = rawPayload.turn) == null ? void 0 : _b.error) || {};
18549
+ return [
18550
+ payload.message,
18551
+ payload.errorMessage,
18552
+ ...structuredErrorInfoValues(payload.code),
18553
+ ...structuredErrorInfoValues(payload.errorCode),
18554
+ payloadErrorData,
18555
+ payloadErrorData.message,
18556
+ payloadErrorData.code,
18557
+ payloadErrorData.errorCode,
18558
+ payloadErrorData.httpStatusCode,
18559
+ payloadErrorData.statusCode,
18560
+ payloadErrorData.http_status,
18561
+ payloadErrorData.reason,
18562
+ payloadErrorData.stopReason,
18563
+ detail.code,
18564
+ detail.message,
18565
+ detail.errorCode,
18566
+ detail.httpStatusCode,
18567
+ detail.statusCode,
18568
+ detail.http_status,
18569
+ ...structuredErrorInfoValues(detail.codexErrorInfo),
18570
+ detail.stopReason,
18571
+ detailError.code,
18572
+ detailError.message,
18573
+ detailError.errorCode,
18574
+ detailError.httpStatusCode,
18575
+ detailError.statusCode,
18576
+ detailError.http_status,
18577
+ ...structuredErrorInfoValues(detailError.codexErrorInfo),
18578
+ detailData.code,
18579
+ detailData.message,
18580
+ detailData.errorCode,
18581
+ detailData.httpStatusCode,
18582
+ detailData.statusCode,
18583
+ detailData.http_status,
18584
+ detailData.reason,
18585
+ detailData.stopReason,
18586
+ detailErrorData.code,
18587
+ detailErrorData.message,
18588
+ detailErrorData.errorCode,
18589
+ detailErrorData.httpStatusCode,
18590
+ detailErrorData.statusCode,
18591
+ detailErrorData.http_status,
18592
+ detailErrorData.reason,
18593
+ detailErrorData.stopReason,
18594
+ rawPayload.stopReason,
18595
+ rawError.message,
18596
+ rawError.code,
18597
+ rawError.errorCode,
18598
+ rawError.httpStatusCode,
18599
+ rawError.statusCode,
18600
+ rawError.http_status,
18601
+ ...structuredErrorInfoValues(rawError.codexErrorInfo)
18602
+ ];
18603
+ }
18604
+ function isModelUsageLimitError(event) {
18605
+ const values = errorSignalValues(event);
18606
+ if (values.some((value) => typeof value === "string" && USAGE_LIMIT_MESSAGE_PATTERN.test(value))) {
18607
+ return true;
18608
+ }
18609
+ if (values.some((value) => typeof value === "string" && USAGE_LIMIT_CODES.has(value.toLowerCase().replace(/[^a-z0-9]/g, "")))) return true;
18610
+ if (values.some((value) => Number(value) === 429)) return true;
18611
+ return (event == null ? void 0 : event.provider) === "grok" && values.some((value) => Number(value) === -32003);
18612
+ }
18613
+ function normalizeProviderErrorEvent(event) {
18614
+ if (!event || !["runtime.error", "turn.completed", "session.exited"].includes(event.type)) {
18615
+ return event;
18616
+ }
18617
+ if (!isModelUsageLimitError(event)) return event;
18618
+ const payload = { ...event.payload || {}, code: MODEL_USAGE_LIMIT_CODE };
18619
+ if (event.type === "runtime.error") {
18620
+ payload.message = MODEL_USAGE_LIMIT_MESSAGE;
18621
+ payload.class = "usage_limit";
18622
+ } else if (event.type === "turn.completed") {
18623
+ payload.errorMessage = MODEL_USAGE_LIMIT_MESSAGE;
18624
+ payload.errorCode = MODEL_USAGE_LIMIT_CODE;
18625
+ } else {
18626
+ payload.reason = MODEL_USAGE_LIMIT_MESSAGE;
18627
+ payload.errorCode = MODEL_USAGE_LIMIT_CODE;
18628
+ }
18629
+ return { ...event, payload };
18630
+ }
18631
+ module2.exports = {
18632
+ MODEL_USAGE_LIMIT_CODE,
18633
+ MODEL_USAGE_LIMIT_MESSAGE,
18634
+ isModelUsageLimitError,
18635
+ normalizeProviderErrorEvent
18636
+ };
18637
+ }
18638
+ });
18639
+
18504
18640
  // src/infrastructure/agent-drivers/provider-events.js
18505
18641
  var require_provider_events = __commonJS({
18506
18642
  "src/infrastructure/agent-drivers/provider-events.js"(exports2, module2) {
18507
18643
  var crypto = require("crypto");
18644
+ var { normalizeProviderErrorEvent } = require_provider_error_presentation();
18508
18645
  var PROVIDER_EVENT_TYPES = Object.freeze([
18509
18646
  "session.state.changed",
18510
18647
  "session.config.updated",
@@ -18596,7 +18733,7 @@ var require_provider_events = __commonJS({
18596
18733
  if (bare.itemId !== void 0) event.itemId = bare.itemId;
18597
18734
  if (bare.requestId !== void 0) event.requestId = bare.requestId;
18598
18735
  if (bare.raw !== void 0) event.raw = bare.raw;
18599
- return event;
18736
+ return normalizeProviderErrorEvent(event);
18600
18737
  }
18601
18738
  function normalizeExecutionOrigin(value) {
18602
18739
  return EXECUTION_ORIGINS.includes(value) ? value : "unknown";
@@ -18674,6 +18811,7 @@ var require_provider_auth = __commonJS({
18674
18811
  /\bunauthenticated\b/i,
18675
18812
  /\bauthentication required\b/i,
18676
18813
  /\bplease (?:run|use) [^\n]*(?:login|log in|sign in)\b/i,
18814
+ /\brun [^\n]*(?:login|log in|sign in)\b/i,
18677
18815
  /\b(?:login|log in|sign in) (?:is )?required\b/i,
18678
18816
  /\bmissing (?:an? )?(?:api key|access token|auth token|credentials?)\b/i,
18679
18817
  /\b(?:invalid|expired) (?:api key|access token|auth token|credentials?)\b/i,
@@ -20864,17 +21002,19 @@ ${text}`;
20864
21002
  }];
20865
21003
  },
20866
21004
  "turn/completed": ({ method, params }) => {
20867
- var _a;
21005
+ var _a, _b;
20868
21006
  const turn = params == null ? void 0 : params.turn;
20869
21007
  if (!(turn == null ? void 0 : turn.id)) return [];
20870
21008
  const errorMessage = (_a = turn.error) == null ? void 0 : _a.message;
21009
+ const errorCode = (_b = turn.error) == null ? void 0 : _b.codexErrorInfo;
20871
21010
  return [{
20872
21011
  type: "turn.completed",
20873
21012
  threadId: params.threadId,
20874
21013
  turnId: turn.id,
20875
21014
  payload: {
20876
21015
  state: mapTurnStatus(turn.status),
20877
- ...errorMessage ? { errorMessage } : {}
21016
+ ...errorMessage ? { errorMessage } : {},
21017
+ ...errorCode ? { errorCode } : {}
20878
21018
  },
20879
21019
  raw: buildRaw(method, params)
20880
21020
  }];
@@ -20992,8 +21132,9 @@ ${text}`;
20992
21132
  raw: buildRaw(method, params)
20993
21133
  }],
20994
21134
  error: ({ method, params }) => {
20995
- var _a;
21135
+ var _a, _b;
20996
21136
  const message = (_a = params == null ? void 0 : params.error) == null ? void 0 : _a.message;
21137
+ const code = (_b = params == null ? void 0 : params.error) == null ? void 0 : _b.codexErrorInfo;
20997
21138
  const base = {
20998
21139
  threadId: params == null ? void 0 : params.threadId,
20999
21140
  turnId: params == null ? void 0 : params.turnId,
@@ -21005,7 +21146,7 @@ ${text}`;
21005
21146
  return [{
21006
21147
  ...base,
21007
21148
  type: "runtime.error",
21008
- payload: { message, class: "provider_error", detail: params }
21149
+ payload: { message, class: "provider_error", ...code ? { code } : {}, detail: params }
21009
21150
  }];
21010
21151
  }
21011
21152
  });
@@ -25778,12 +25919,15 @@ var require_acp_agent_driver = __commonJS({
25778
25919
  try {
25779
25920
  const result = await prompt;
25780
25921
  const stopReason = result && result.stopReason;
25781
- completion = {
25782
- state: stopReason === "cancelled" ? "cancelled" : "completed"
25783
- };
25922
+ completion = stopReason === "rate_limit" ? { state: "failed", errorMessage: "Rate limit reached", errorCode: "rate_limit" } : { state: stopReason === "cancelled" ? "cancelled" : "completed" };
25784
25923
  } catch (error) {
25785
25924
  if (this._stopping || this._state === "stopped") return;
25786
- completion = { state: "failed", errorMessage: error.message };
25925
+ completion = {
25926
+ state: "failed",
25927
+ errorMessage: error.message,
25928
+ ...error.rpcCode !== void 0 ? { errorCode: error.rpcCode } : {},
25929
+ ...error.rpcData !== void 0 ? { errorData: error.rpcData } : {}
25930
+ };
25787
25931
  } finally {
25788
25932
  this._promptsInFlight = Math.max(0, this._promptsInFlight - 1);
25789
25933
  }
@@ -25792,7 +25936,11 @@ var require_acp_agent_driver = __commonJS({
25792
25936
  this._emit({
25793
25937
  type: "runtime.error",
25794
25938
  turnId,
25795
- payload: { message: completion.errorMessage }
25939
+ payload: {
25940
+ message: completion.errorMessage,
25941
+ ...completion.errorCode !== void 0 ? { code: completion.errorCode } : {},
25942
+ ...completion.errorData !== void 0 ? { detail: { data: completion.errorData } } : {}
25943
+ }
25796
25944
  });
25797
25945
  }
25798
25946
  return;
@@ -34806,7 +34954,18 @@ var require_mobile_runtime = __commonJS({
34806
34954
  } = require_chat_history_pagination();
34807
34955
  var PROTOCOL_VERSION = 2;
34808
34956
  var SESSION_SUBSCRIPTIONS_FEATURE = "session-subscriptions";
34809
- var SUBSCRIPTION_ONLY_EVENT_TYPES = /* @__PURE__ */ new Set(["content.delta", "item.updated", "turn.diff.updated"]);
34957
+ var SUBSCRIPTION_ONLY_EVENT_TYPES = /* @__PURE__ */ new Set([
34958
+ "session.config.updated",
34959
+ "session.commands.updated",
34960
+ "thread.started",
34961
+ "thread.token-usage.updated",
34962
+ "turn.diff.updated",
34963
+ "item.started",
34964
+ "item.updated",
34965
+ "item.completed",
34966
+ "content.delta",
34967
+ "account.rate-limits.updated"
34968
+ ]);
34810
34969
  var STREAM_METRICS_INTERVAL_MS = 6e4;
34811
34970
  var MAX_MESSAGE_BYTES = 1024 * 1024;
34812
34971
  var MAX_ITEMS_PER_SESSION = 500;
@@ -36453,9 +36612,17 @@ var require_mobile_runtime = __commonJS({
36453
36612
  }
36454
36613
  if (command.type === "project.clone") {
36455
36614
  if (typeof this.cloneProject !== "function") throw new Error("Remote project cloning is unavailable");
36456
- exactPayload(["rootId", "url", "relativePath", "requestId"]);
36615
+ exactPayload(["rootId", "url", "relativePath", "displayName", "color", "icon", "requestId"]);
36457
36616
  if (typeof payload.requestId !== "string" || !payload.requestId) throw new Error("A clone requestId is required");
36458
- return this.cloneProject({ rootId: payload.rootId, url: payload.url, relativePath: payload.relativePath, requestId: payload.requestId });
36617
+ return this.cloneProject({
36618
+ rootId: payload.rootId,
36619
+ url: payload.url,
36620
+ relativePath: payload.relativePath,
36621
+ displayName: payload.displayName,
36622
+ color: payload.color,
36623
+ icon: payload.icon,
36624
+ requestId: payload.requestId
36625
+ });
36459
36626
  }
36460
36627
  if (command.type === "project.clone.cancel") {
36461
36628
  if (typeof this.cancelProjectClone !== "function") throw new Error("Remote clone cancellation is unavailable");
@@ -36980,7 +37147,8 @@ var require_mobile_runtime = __commonJS({
36980
37147
  await this.manager.interruptTurn(sessionId);
36981
37148
  return { interrupted: true };
36982
37149
  case "session.stop": {
36983
- const session = this._session(sessionId);
37150
+ const session = this.sessions.get(sessionId);
37151
+ if (!session || session.state === "stopped") return { stopped: true };
36984
37152
  let result;
36985
37153
  if (typeof this.closeSession === "function") {
36986
37154
  const closed = await this.closeSession({
@@ -37296,7 +37464,7 @@ var require_mobile_relay_client = __commonJS({
37296
37464
  var PEER_BATCH_MAX_BYTES = 5 * 1024 * 1024;
37297
37465
  var PEER_BATCH_ITEM_MAX_BYTES = 256 * 1024;
37298
37466
  var PEER_METRICS_INTERVAL_MS = 6e4;
37299
- var BATCHABLE_RUNTIME_EVENT_TYPES = /* @__PURE__ */ new Set(["content.delta", "item.updated", "turn.diff.updated"]);
37467
+ var BATCHABLE_RUNTIME_KINDS = /* @__PURE__ */ new Set(["session.event", "cursor.advanced"]);
37300
37468
  var COMPRESSION_THRESHOLD_BYTES = 4096;
37301
37469
  var ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
37302
37470
  var RELAY_GROUP_PATTERN = /^[A-Za-z0-9_-]{43}$/;
@@ -37340,7 +37508,7 @@ var require_mobile_relay_client = __commonJS({
37340
37508
  this.acceptsDeflate = false;
37341
37509
  }
37342
37510
  send(raw) {
37343
- var _a, _b;
37511
+ var _a;
37344
37512
  if (this.readyState !== 1) return;
37345
37513
  const json = String(raw);
37346
37514
  const bytes = Buffer.byteLength(json);
@@ -37357,7 +37525,7 @@ var require_mobile_relay_client = __commonJS({
37357
37525
  deviceId: this.device.id,
37358
37526
  ...codec ? { codec } : {},
37359
37527
  box: encryptJson(payload, this.client.keyPair.secretKey, this.device.publicKey, codec)
37360
- }, payload.kind === "session.event" && BATCHABLE_RUNTIME_EVENT_TYPES.has((_b = payload.event) == null ? void 0 : _b.type));
37528
+ }, BATCHABLE_RUNTIME_KINDS.has(payload.kind));
37361
37529
  }
37362
37530
  receive(box) {
37363
37531
  if (this.readyState !== 1) return;
@@ -38097,7 +38265,7 @@ var require_mobile_relay_client = __commonJS({
38097
38265
  });
38098
38266
  return;
38099
38267
  }
38100
- if (message.kind === "peer.message" || message.kind === "peer.offline") {
38268
+ if (["peer.message", "peer.offline", "peer.online"].includes(message.kind)) {
38101
38269
  if (message.kind === "peer.offline") {
38102
38270
  this.emit("diagnostic", { event: "peer.route_offline", peer: logRef(message.targetRuntimeId) });
38103
38271
  }
@@ -38356,6 +38524,7 @@ var require_remote_runtime_client = __commonJS({
38356
38524
  "session.identity.updated",
38357
38525
  "projects.updated",
38358
38526
  "projects.operation.updated",
38527
+ "project.icon.generated",
38359
38528
  "tasks.changed",
38360
38529
  "provider.login.event",
38361
38530
  "provider.operation.updated",
@@ -38415,6 +38584,8 @@ var require_remote_runtime_client = __commonJS({
38415
38584
  "session.resume",
38416
38585
  "history.older",
38417
38586
  "projects.list",
38587
+ "project.directories.list",
38588
+ "project.update",
38418
38589
  "project.register",
38419
38590
  "project.clone",
38420
38591
  "project.clone.cancel",
@@ -38446,6 +38617,7 @@ var require_remote_runtime_client = __commonJS({
38446
38617
  "coordination.transcript",
38447
38618
  "coordination.message",
38448
38619
  "projects.list",
38620
+ "project.directories.list",
38449
38621
  "tasks.list",
38450
38622
  "providers.list",
38451
38623
  "provider.login.describe",
@@ -38511,15 +38683,20 @@ var require_remote_runtime_client = __commonJS({
38511
38683
  assertPublicPayload(child, depth + 1);
38512
38684
  }
38513
38685
  }
38514
- function stripPathFields(value, depth = 0) {
38686
+ function stripPathFields(value, depth = 0, preserveRelativePaths = false) {
38515
38687
  if (depth > 40) throw new Error("Remote runtime payload is too deeply nested");
38516
- if (Array.isArray(value)) return value.map((item) => stripPathFields(item, depth + 1));
38688
+ if (Array.isArray(value)) return value.map((item) => stripPathFields(item, depth + 1, preserveRelativePaths));
38517
38689
  if (!value || typeof value !== "object") return value;
38518
38690
  const clean = {};
38519
38691
  for (const [key, child] of Object.entries(value)) {
38520
38692
  const normalized = normalizedKey(key);
38521
- if (PATH_KEYS.has(normalized) || normalized.endsWith("path")) continue;
38522
- clean[key] = stripPathFields(child, depth + 1);
38693
+ if (PATH_KEYS.has(normalized) || normalized.endsWith("path")) {
38694
+ if (preserveRelativePaths && (child === null || isSafeRelativePath(child))) {
38695
+ clean[key] = typeof child === "string" ? child.replace(/\\/g, "/") : null;
38696
+ }
38697
+ continue;
38698
+ }
38699
+ clean[key] = stripPathFields(child, depth + 1, preserveRelativePaths);
38523
38700
  }
38524
38701
  return clean;
38525
38702
  }
@@ -39064,7 +39241,7 @@ var require_remote_runtime_client = __commonJS({
39064
39241
  }
39065
39242
  if (command.payload !== void 0) {
39066
39243
  assertPublicPayload(command.payload);
39067
- assertPathlessCommand(command.payload, 0, command.type === "project.register" || command.type === "project.clone" || command.type.startsWith("workspace.files."));
39244
+ assertPathlessCommand(command.payload, 0, command.type === "project.directories.list" || command.type === "project.register" || command.type === "project.clone" || command.type.startsWith("workspace.files."));
39068
39245
  wire.payload = clone(command.payload);
39069
39246
  }
39070
39247
  return wire;
@@ -39316,12 +39493,12 @@ var require_remote_runtime_client = __commonJS({
39316
39493
  if (this.runtimeOnline) this._sendRuntimeHello();
39317
39494
  }
39318
39495
  _handleRuntimeEnvelope(envelope) {
39319
- var _a, _b;
39496
+ var _a, _b, _c, _d, _e;
39320
39497
  let safe;
39321
39498
  let bytes;
39322
39499
  try {
39323
39500
  bytes = Buffer.byteLength(JSON.stringify(envelope));
39324
- if (!envelope || typeof envelope !== "object" || bytes > MAX_RUNTIME_MESSAGE_BYTES || !RUNTIME_KINDS.has(envelope.kind)) {
39501
+ if (!envelope || typeof envelope !== "object" || bytes > MAX_RUNTIME_MESSAGE_BYTES) {
39325
39502
  throw new Error("Invalid runtime envelope");
39326
39503
  }
39327
39504
  assertPublicPayload(envelope);
@@ -39330,9 +39507,11 @@ var require_remote_runtime_client = __commonJS({
39330
39507
  throw new Error("Invalid runtime snapshot");
39331
39508
  }
39332
39509
  }
39333
- safe = stripPathFields(envelope);
39510
+ const commandType = envelope.kind === "command.result" ? (_c = (_b = (_a = this.pendingCommands.get(envelope.commandId)) == null ? void 0 : _a.message) == null ? void 0 : _b.command) == null ? void 0 : _c.type : null;
39511
+ safe = stripPathFields(envelope, 0, commandType === "project.directories.list");
39334
39512
  } catch {
39335
- this._diagnostic("remote.runtime_rejected", { reason: "invalid_envelope" });
39513
+ const kind = typeof (envelope == null ? void 0 : envelope.kind) === "string" && /^[a-z][a-z0-9.]{0,63}$/.test(envelope.kind) ? envelope.kind : void 0;
39514
+ this._diagnostic("remote.runtime_rejected", { reason: "invalid_envelope", kind });
39336
39515
  return this._protocolFailure(this.socket);
39337
39516
  }
39338
39517
  if (safe.kind === "command.accepted") {
@@ -39383,8 +39562,8 @@ var require_remote_runtime_client = __commonJS({
39383
39562
  totalMs: this.connectTrace ? this.now() - this.connectTrace.startedAt : void 0,
39384
39563
  reset: safe.reset === true,
39385
39564
  bytes,
39386
- sessions: Array.isArray((_a = safe.snapshot) == null ? void 0 : _a.sessions) ? safe.snapshot.sessions.length : void 0,
39387
- projects: Array.isArray((_b = safe.snapshot) == null ? void 0 : _b.projects) ? safe.snapshot.projects.length : void 0
39565
+ sessions: Array.isArray((_d = safe.snapshot) == null ? void 0 : _d.sessions) ? safe.snapshot.sessions.length : void 0,
39566
+ projects: Array.isArray((_e = safe.snapshot) == null ? void 0 : _e.projects) ? safe.snapshot.projects.length : void 0
39388
39567
  });
39389
39568
  this.connectTrace = null;
39390
39569
  this._emitEnvelope(safe);
@@ -39410,6 +39589,15 @@ var require_remote_runtime_client = __commonJS({
39410
39589
  return;
39411
39590
  }
39412
39591
  if (seq <= cursor.seq) return;
39592
+ if (!RUNTIME_KINDS.has(safe.kind)) {
39593
+ this._diagnostic("remote.runtime_ignored", { kind: safe.kind, seq });
39594
+ this._setState({
39595
+ ...this.state,
39596
+ cursor: { runtimeId: cursor.runtimeId, seq },
39597
+ error: null
39598
+ });
39599
+ return;
39600
+ }
39413
39601
  this._setState({
39414
39602
  ...this.state,
39415
39603
  cursor: { runtimeId: cursor.runtimeId, seq },
@@ -39871,6 +40059,83 @@ var require_remote_runtime_store = __commonJS({
39871
40059
  }
39872
40060
  });
39873
40061
 
40062
+ // src/infrastructure/mobile/desktop-connection-link.js
40063
+ var require_desktop_connection_link = __commonJS({
40064
+ "src/infrastructure/mobile/desktop-connection-link.js"(exports2, module2) {
40065
+ var DEFAULT_CONNECT_ORIGIN = "https://codeagentswarm-connect.elcaminodelprogramadorweb.workers.dev";
40066
+ var PUBLIC_CONNECT_ORIGIN = "https://connect.codeagentswarm.com";
40067
+ var PAIRING_CODE = /^[A-HJ-NP-Z2-9]{8}$/;
40068
+ function secureOrigin(value) {
40069
+ try {
40070
+ const url = new URL(value);
40071
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
40072
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && local) || url.username || url.password || url.origin !== value) return null;
40073
+ return url.origin;
40074
+ } catch (_) {
40075
+ return null;
40076
+ }
40077
+ }
40078
+ function normalizeCode(value) {
40079
+ const compact = String(value || "").trim().toUpperCase().replace(/[\s-]/g, "");
40080
+ return PAIRING_CODE.test(compact) ? `${compact.slice(0, 4)}-${compact.slice(4)}` : null;
40081
+ }
40082
+ function configuredOrigin() {
40083
+ const value = process.env.CAS_PAIRING_CODE_ORIGIN || DEFAULT_CONNECT_ORIGIN;
40084
+ const origin = secureOrigin(value);
40085
+ if (!origin) throw new Error("The pairing service is not secure");
40086
+ return origin;
40087
+ }
40088
+ function codeFromInput(raw) {
40089
+ const direct = normalizeCode(raw);
40090
+ if (direct) return { code: direct, origin: configuredOrigin() };
40091
+ let url;
40092
+ try {
40093
+ url = new URL(String(raw || "").trim());
40094
+ } catch (_) {
40095
+ return null;
40096
+ }
40097
+ if (["codeagentswarm:", "codeagentswarm-dev:"].includes(url.protocol) && url.hostname === "connect") {
40098
+ const code2 = normalizeCode(url.searchParams.get("code"));
40099
+ if (!code2) throw new Error("This pairing code is not valid");
40100
+ return { code: code2, origin: configuredOrigin() };
40101
+ }
40102
+ if (!/^\/(?:connect|c)\/[^/]+\/?$/.test(url.pathname)) return null;
40103
+ const trusted = /* @__PURE__ */ new Set([DEFAULT_CONNECT_ORIGIN, PUBLIC_CONNECT_ORIGIN, configuredOrigin()]);
40104
+ if (!trusted.has(url.origin) || url.search || url.hash) throw new Error("This pairing code is not valid");
40105
+ const code = normalizeCode(url.pathname.split("/").filter(Boolean).at(-1));
40106
+ if (!code) throw new Error("This pairing code is not valid");
40107
+ return { code, origin: url.origin };
40108
+ }
40109
+ async function resolvePairingInput(raw, fetchImpl = globalThis.fetch) {
40110
+ const connection = codeFromInput(raw);
40111
+ if (!connection) return String(raw || "").trim();
40112
+ try {
40113
+ const response = await fetchImpl(`${connection.origin}/api/mobile/pairing-code/${encodeURIComponent(connection.code)}`, {
40114
+ headers: { Accept: "application/json" },
40115
+ signal: AbortSignal.timeout(1e4)
40116
+ });
40117
+ if (!response.ok) throw new Error();
40118
+ const body = await response.json();
40119
+ if (typeof body.pairingUri !== "string") throw new Error();
40120
+ return body.pairingUri;
40121
+ } catch (_) {
40122
+ throw new Error("This pairing code is invalid or has expired");
40123
+ }
40124
+ }
40125
+ function desktopConnectionLink(pairing) {
40126
+ const code = normalizeCode(pairing == null ? void 0 : pairing.pairingCode);
40127
+ if (!code) throw new Error("CAS Cloud did not return a valid pairing code");
40128
+ return `${configuredOrigin()}/connect/${code}`;
40129
+ }
40130
+ module2.exports = {
40131
+ DEFAULT_CONNECT_ORIGIN,
40132
+ desktopConnectionLink,
40133
+ normalizeCode,
40134
+ resolvePairingInput
40135
+ };
40136
+ }
40137
+ });
40138
+
39874
40139
  // src/infrastructure/mobile/peer-runtime-network.js
39875
40140
  var require_peer_runtime_network = __commonJS({
39876
40141
  "src/infrastructure/mobile/peer-runtime-network.js"(exports2, module2) {
@@ -39883,6 +40148,7 @@ var require_peer_runtime_network = __commonJS({
39883
40148
  var MAX_PEERS = 32;
39884
40149
  var PEER_ACCESS_TTL_MS = 7 * 24 * 60 * 6e4;
39885
40150
  var PEER_HANDSHAKE_TIMEOUT_MS = 5e3;
40151
+ var PEER_DENIAL_FEATURE = "peer-denial";
39886
40152
  function peerRef(value) {
39887
40153
  return value ? crypto.createHash("sha256").update(String(value)).digest("hex").slice(0, 10) : void 0;
39888
40154
  }
@@ -39921,7 +40187,18 @@ var require_peer_runtime_network = __commonJS({
39921
40187
  return;
39922
40188
  }
39923
40189
  if (message.kind === "runtime.message" && message.box) {
39924
- if (!this.network.relay.sendPeerMessage(this.peer.runtimeId, message.box, "to-runtime")) {
40190
+ let box = message.box;
40191
+ try {
40192
+ const payload = decryptJson(box, this.network.keyPair.secretKey, this.peer.publicKey);
40193
+ if ((payload == null ? void 0 : payload.kind) === "hello" && !(Array.isArray(payload.features) && payload.features.includes(PEER_DENIAL_FEATURE))) {
40194
+ box = encryptJson({
40195
+ ...payload,
40196
+ features: [...Array.isArray(payload.features) ? payload.features : [], PEER_DENIAL_FEATURE]
40197
+ }, this.network.keyPair.secretKey, this.peer.publicKey);
40198
+ }
40199
+ } catch {
40200
+ }
40201
+ if (!this.network.relay.sendPeerMessage(this.peer.runtimeId, box, "to-runtime")) {
39925
40202
  this.network._diagnostic("peer.route_failed", { peer: peerRef(this.peer.runtimeId), stream: "to-runtime" });
39926
40203
  this.offline();
39927
40204
  } else if (this.readyState === 1 && !this.handshakeComplete && !this.handshakeTimer) {
@@ -39931,11 +40208,17 @@ var require_peer_runtime_network = __commonJS({
39931
40208
  }
39932
40209
  receive(box) {
39933
40210
  if (this.readyState !== 1) return;
40211
+ let payload;
39934
40212
  try {
39935
- decryptJson(box, this.network.keyPair.secretKey, this.peer.publicKey);
40213
+ payload = decryptJson(box, this.network.keyPair.secretKey, this.peer.publicKey);
39936
40214
  this.network.relay.emit("diagnostic", { event: "peer.response_verified", stream: "to-client" });
39937
40215
  } catch {
39938
40216
  this.network.relay.emit("diagnostic", { event: "peer.response_rejected", reason: "decrypt_failed" });
40217
+ return;
40218
+ }
40219
+ if ((payload == null ? void 0 : payload.kind) === "peer.denied") {
40220
+ this.network._denyPeer(this.peer.runtimeId);
40221
+ return;
39939
40222
  }
39940
40223
  this.handshakeComplete = true;
39941
40224
  if (this.handshakeTimer) clearTimeout(this.handshakeTimer);
@@ -39974,6 +40257,8 @@ var require_peer_runtime_network = __commonJS({
39974
40257
  this.loadRosters = loadRosters;
39975
40258
  this.saveRosters = saveRosters;
39976
40259
  this.rosters = /* @__PURE__ */ new Map();
40260
+ this.revokedPeers = /* @__PURE__ */ new Set();
40261
+ this.deniedPeers = /* @__PURE__ */ new Set();
39977
40262
  this.peers = /* @__PURE__ */ new Map();
39978
40263
  this.clients = /* @__PURE__ */ new Map();
39979
40264
  this.clientSockets = /* @__PURE__ */ new Map();
@@ -39987,7 +40272,11 @@ var require_peer_runtime_network = __commonJS({
39987
40272
  if (this.started) return;
39988
40273
  this.started = true;
39989
40274
  const saved = this.loadRosters() || {};
39990
- for (const [deviceId, peers] of Object.entries(saved)) {
40275
+ const savedRosters = saved.version === 1 && saved.rosters && typeof saved.rosters === "object" ? saved.rosters : saved;
40276
+ if (saved.version === 1 && Array.isArray(saved.revoked)) {
40277
+ this.revokedPeers = new Set(saved.revoked.filter((runtimeId) => ID_PATTERN.test(runtimeId || "") && runtimeId !== this.runtimeId));
40278
+ }
40279
+ for (const [deviceId, peers] of Object.entries(savedRosters)) {
39991
40280
  if (!ID_PATTERN.test(deviceId) || !Array.isArray(peers)) continue;
39992
40281
  this.rosters.set(deviceId, peers.slice(0, MAX_PEERS).filter((peer) => validPeer(peer, this.runtimeId)));
39993
40282
  }
@@ -40017,9 +40306,15 @@ var require_peer_runtime_network = __commonJS({
40017
40306
  publicKey: peer.publicKey,
40018
40307
  name: peer.name.trim().slice(0, 200) || "Connected host"
40019
40308
  }])).values()];
40309
+ for (const peer of unique) this.deniedPeers.delete(peer.runtimeId);
40020
40310
  if (unique.length) this.rosters.set(deviceId, unique);
40021
40311
  else this.rosters.delete(deviceId);
40022
- this.saveRosters(Object.fromEntries(this.rosters));
40312
+ for (const runtimeId of this.revokedPeers) {
40313
+ if (![...this.rosters.values()].some((roster) => roster.some((peer) => peer.runtimeId === runtimeId))) {
40314
+ this.revokedPeers.delete(runtimeId);
40315
+ }
40316
+ }
40317
+ this._saveState();
40023
40318
  this._rebuildPeers();
40024
40319
  this._diagnostic("peer.roster_replaced", {
40025
40320
  rosters: this.rosters.size,
@@ -40029,6 +40324,15 @@ var require_peer_runtime_network = __commonJS({
40029
40324
  });
40030
40325
  return { peers: unique.length };
40031
40326
  }
40327
+ removePeer(runtimeId) {
40328
+ if (!ID_PATTERN.test(runtimeId || "")) return false;
40329
+ if (![...this.rosters.values()].some((roster) => roster.some((peer) => peer.runtimeId === runtimeId))) return false;
40330
+ this.revokedPeers.add(runtimeId);
40331
+ this._saveState();
40332
+ this._rebuildPeers();
40333
+ this._diagnostic("peer.removed_by_user", { peer: peerRef(runtimeId) });
40334
+ return true;
40335
+ }
40032
40336
  getClients() {
40033
40337
  return [...this.clients.values()];
40034
40338
  }
@@ -40047,6 +40351,7 @@ var require_peer_runtime_network = __commonJS({
40047
40351
  const next = /* @__PURE__ */ new Map();
40048
40352
  for (const roster of this.rosters.values()) {
40049
40353
  for (const peer of roster) {
40354
+ if (this.revokedPeers.has(peer.runtimeId)) continue;
40050
40355
  const existing = next.get(peer.runtimeId);
40051
40356
  if (existing && existing.publicKey !== peer.publicKey) continue;
40052
40357
  next.set(peer.runtimeId, peer);
@@ -40056,14 +40361,22 @@ var require_peer_runtime_network = __commonJS({
40056
40361
  const previous = this.peers.get(runtimeId);
40057
40362
  const current = next.get(runtimeId);
40058
40363
  if ((current == null ? void 0 : current.publicKey) === previous.publicKey) continue;
40364
+ this.deniedPeers.delete(runtimeId);
40059
40365
  this._removePeer(runtimeId);
40060
40366
  }
40061
40367
  this.peers = next;
40062
40368
  for (const peer of this.peers.values()) {
40063
- if (!this.clients.has(peer.runtimeId)) this._addPeer(peer);
40369
+ if (!this.deniedPeers.has(peer.runtimeId) && !this.clients.has(peer.runtimeId)) this._addPeer(peer);
40064
40370
  }
40065
40371
  this._notifyClients();
40066
40372
  }
40373
+ _saveState() {
40374
+ this.saveRosters({
40375
+ version: 1,
40376
+ rosters: Object.fromEntries(this.rosters),
40377
+ revoked: [...this.revokedPeers]
40378
+ });
40379
+ }
40067
40380
  _addPeer(peer) {
40068
40381
  const network = this;
40069
40382
  let connection = {
@@ -40118,7 +40431,8 @@ var require_peer_runtime_network = __commonJS({
40118
40431
  ...details,
40119
40432
  scope: "peer",
40120
40433
  peer: peerRef(peer.runtimeId)
40121
- })
40434
+ }),
40435
+ timeouts: { reconnectMax: 5 * 6e4 }
40122
40436
  });
40123
40437
  client.subscribeEnvelopes((envelope) => {
40124
40438
  for (const listener of this.envelopeListeners) listener(peer.runtimeId, envelope, client);
@@ -40137,8 +40451,34 @@ var require_peer_runtime_network = __commonJS({
40137
40451
  this.serverSockets.delete(runtimeId);
40138
40452
  this._diagnostic("peer.removed", { peer: peerRef(runtimeId) });
40139
40453
  }
40454
+ _denyPeer(runtimeId) {
40455
+ if (!this.peers.has(runtimeId)) return;
40456
+ this.deniedPeers.add(runtimeId);
40457
+ this._removePeer(runtimeId);
40458
+ this._diagnostic("peer.connection_denied", { peer: peerRef(runtimeId) });
40459
+ this._notifyClients();
40460
+ }
40461
+ _rosterPeer(runtimeId) {
40462
+ let found = null;
40463
+ for (const roster of this.rosters.values()) {
40464
+ for (const peer of roster) {
40465
+ if (peer.runtimeId !== runtimeId) continue;
40466
+ if (found && found.publicKey !== peer.publicKey) return null;
40467
+ found = peer;
40468
+ }
40469
+ }
40470
+ return found;
40471
+ }
40140
40472
  _handleRelayEvent(event) {
40141
40473
  var _a, _b, _c;
40474
+ if ((event == null ? void 0 : event.kind) === "peer.online" && ID_PATTERN.test(event.targetRuntimeId || "")) {
40475
+ const client = this.clients.get(event.targetRuntimeId);
40476
+ if (client && client.getState().phase !== "online") {
40477
+ this._diagnostic("peer.online", { peer: peerRef(event.targetRuntimeId) });
40478
+ client.reconnectNow();
40479
+ }
40480
+ return;
40481
+ }
40142
40482
  if ((event == null ? void 0 : event.kind) === "peer.offline" && ID_PATTERN.test(event.targetRuntimeId || "")) {
40143
40483
  this._diagnostic("peer.offline", { peer: peerRef(event.targetRuntimeId) });
40144
40484
  (_a = this.clientSockets.get(event.targetRuntimeId)) == null ? void 0 : _a.offline();
@@ -40149,6 +40489,20 @@ var require_peer_runtime_network = __commonJS({
40149
40489
  if ((event == null ? void 0 : event.kind) !== "peer.message" || !ID_PATTERN.test(event.sourceRuntimeId || "") || !KEY_PATTERN.test(event.sourcePublicKey || "")) return;
40150
40490
  const peer = this.peers.get(event.sourceRuntimeId);
40151
40491
  if (!peer || peer.publicKey !== event.sourcePublicKey) {
40492
+ const revokedPeer = this.revokedPeers.has(event.sourceRuntimeId) ? this._rosterPeer(event.sourceRuntimeId) : null;
40493
+ if ((revokedPeer == null ? void 0 : revokedPeer.publicKey) === event.sourcePublicKey && event.stream === "to-runtime") {
40494
+ let payload2;
40495
+ try {
40496
+ payload2 = decryptJson(event.box, this.keyPair.secretKey, revokedPeer.publicKey);
40497
+ } catch {
40498
+ }
40499
+ if ((payload2 == null ? void 0 : payload2.kind) === "hello" && Array.isArray(payload2.features) && payload2.features.includes(PEER_DENIAL_FEATURE)) {
40500
+ const box = encryptJson({ kind: "peer.denied" }, this.keyPair.secretKey, revokedPeer.publicKey);
40501
+ if (this.relay.sendPeerMessage(revokedPeer.runtimeId, box, "to-client")) {
40502
+ this._diagnostic("peer.denial_sent", { peer: peerRef(revokedPeer.runtimeId) });
40503
+ }
40504
+ }
40505
+ }
40152
40506
  this._diagnostic("peer.message_rejected", {
40153
40507
  reason: peer ? "key_mismatch" : "not_introduced",
40154
40508
  peer: peerRef(event.sourceRuntimeId)
@@ -40341,7 +40695,7 @@ var require_headless_session_bridge = __commonJS({
40341
40695
  };
40342
40696
  }
40343
40697
  var HeadlessSessionBridge = class {
40344
- constructor({ runtime, remoteClient, peerRuntimeNetwork = null, dataPath, deliverMessage, randomBytes = crypto.randomBytes } = {}) {
40698
+ constructor({ runtime, remoteClient, peerRuntimeNetwork = null, dataPath, deliverMessage, createPairingLink = null, randomBytes = crypto.randomBytes } = {}) {
40345
40699
  if (!runtime || !remoteClient || typeof deliverMessage !== "function" || !path.isAbsolute(dataPath || "")) {
40346
40700
  throw new Error("CAS Cloud session bridge configuration is invalid");
40347
40701
  }
@@ -40350,6 +40704,7 @@ var require_headless_session_bridge = __commonJS({
40350
40704
  this.peerRuntimeNetwork = peerRuntimeNetwork;
40351
40705
  this.dataPath = dataPath;
40352
40706
  this.deliverMessage = deliverMessage;
40707
+ this.createPairingLink = createPairingLink;
40353
40708
  this.adminToken = randomBytes(32).toString("hex");
40354
40709
  this.sessionSecret = randomBytes(32);
40355
40710
  this.server = null;
@@ -40534,6 +40889,15 @@ var require_headless_session_bridge = __commonJS({
40534
40889
  if (request.method === "GET" && url.pathname === "/admin/remote-runtime") {
40535
40890
  return sendJson(response, 200, publicRemoteState(this.remoteClient));
40536
40891
  }
40892
+ if (request.method === "POST" && url.pathname === "/admin/pairing-link") {
40893
+ if (typeof this.createPairingLink !== "function") return sendJson(response, 503, { error: "Pairing is unavailable" });
40894
+ try {
40895
+ const result = await this.createPairingLink();
40896
+ return sendJson(response, 201, { url: result.url, expiresAt: result.expiresAt });
40897
+ } catch (_) {
40898
+ return sendJson(response, 503, { error: "Could not create a connection link" });
40899
+ }
40900
+ }
40537
40901
  if (request.method === "POST" && url.pathname === "/admin/remote-runtime/pair") {
40538
40902
  const body = await readJson(request);
40539
40903
  if (typeof body.pairing !== "string" || !body.pairing.trim()) {
@@ -41126,6 +41490,19 @@ var require_headless_project_registry = __commonJS({
41126
41490
  const result = this._registerPath(resolved, root ? this._rootByPath(root.rootPath).root_id : null, { legacy: true });
41127
41491
  if (result.changed) changed = true;
41128
41492
  }
41493
+ for (const row of this.db.prepare("SELECT project_id, path FROM runtime_projects WHERE registered = 1").all()) {
41494
+ let resolved;
41495
+ try {
41496
+ resolved = fs.realpathSync(row.path);
41497
+ } catch (_) {
41498
+ continue;
41499
+ }
41500
+ if (resolved === row.path) continue;
41501
+ const canonical = this.db.prepare("SELECT project_id FROM runtime_projects WHERE path = ? AND registered = 1").get(resolved);
41502
+ if (!canonical || canonical.project_id === row.project_id) continue;
41503
+ this.db.prepare("UPDATE runtime_projects SET registered = 0, updated_at = CURRENT_TIMESTAMP WHERE project_id = ?").run(row.project_id);
41504
+ changed = true;
41505
+ }
41129
41506
  return changed;
41130
41507
  }
41131
41508
  _resolveDirectory(candidate, label) {
@@ -41393,12 +41770,20 @@ var require_headless_project_registry = __commonJS({
41393
41770
  const revision = this._bumpRevision();
41394
41771
  return this._recordRequest(requestId, hash, { projectId: project.projectId, revision, updated: true });
41395
41772
  }
41396
- clone({ rootId, url, relativePath, requestId }) {
41773
+ clone({ rootId, url, relativePath, displayName, color, icon, requestId }) {
41397
41774
  const normalizedUrl = validateGitUrl(url);
41398
41775
  const root = this._root(rootId);
41399
41776
  this._assertSecureCloneRoot(root);
41400
41777
  const normalizedRelative = validateCloneChildName(relativePath || cloneDirectoryName(normalizedUrl));
41401
- const hash = requestHash("clone", { rootId, url: normalizedUrl, relativePath: normalizedRelative });
41778
+ const appearance = {
41779
+ ...displayName !== void 0 ? { displayName: String(displayName || "").trim().slice(0, 120) } : {},
41780
+ ...color !== void 0 ? { color: String(color || "").trim().slice(0, 32) } : {},
41781
+ ...icon !== void 0 ? { icon } : {}
41782
+ };
41783
+ if (appearance.displayName !== void 0 && !appearance.displayName || appearance.icon !== void 0 && appearance.icon !== null && (typeof appearance.icon !== "string" || !ICON_PATTERN.test(appearance.icon))) {
41784
+ throw runtimeError("invalid_project_update", "Project changes are invalid");
41785
+ }
41786
+ const hash = requestHash("clone", { rootId, url: normalizedUrl, relativePath: normalizedRelative, ...appearance });
41402
41787
  const duplicate = this._request(requestId, hash);
41403
41788
  if (duplicate) return duplicate;
41404
41789
  const destinationInfo = this._cloneDestination(root, normalizedRelative, normalizedUrl);
@@ -41415,6 +41800,7 @@ var require_headless_project_registry = __commonJS({
41415
41800
  requestId,
41416
41801
  rootId,
41417
41802
  url: normalizedUrl,
41803
+ appearance,
41418
41804
  destination: destinationInfo.destination,
41419
41805
  temporaryDestination: null,
41420
41806
  temporaryIdentity: null,
@@ -41552,7 +41938,17 @@ var require_headless_project_registry = __commonJS({
41552
41938
  return this._finish(operation, "failed", "path_symlink_escape");
41553
41939
  }
41554
41940
  const registered = this._registerPath(resolved, root.root_id);
41555
- const revision = registered.changed ? this._bumpRevision() : this.getRevision();
41941
+ if (Object.keys(operation.appearance).length) {
41942
+ this.db.prepare(`UPDATE runtime_projects
41943
+ SET display_name = COALESCE(?, display_name), color = COALESCE(?, color), icon = ?, updated_at = CURRENT_TIMESTAMP
41944
+ WHERE project_id = ?`).run(
41945
+ operation.appearance.displayName ?? null,
41946
+ operation.appearance.color ?? null,
41947
+ operation.appearance.icon === void 0 ? registered.row.icon || null : operation.appearance.icon,
41948
+ registered.row.project_id
41949
+ );
41950
+ }
41951
+ const revision = registered.changed || Object.keys(operation.appearance).length ? this._bumpRevision() : this.getRevision();
41556
41952
  this.db.prepare(`UPDATE runtime_project_operations SET state = 'succeeded', project_id = ?, error = NULL, updated_at = CURRENT_TIMESTAMP WHERE operation_id = ?`).run(registered.row.project_id, operation.operationId);
41557
41953
  this.running.delete(operation.operationId);
41558
41954
  this.destinations.delete(operation.destination);
@@ -48824,12 +49220,15 @@ var require_headless_runtime = __commonJS({
48824
49220
  var os = require("os");
48825
49221
  var path = require("path");
48826
49222
  var { DriverChatManager, SESSION_EVENT } = require_driver_chat_manager();
49223
+ var { CHAT_PERMISSION_MODES } = require_chat_permission_modes();
49224
+ var { classifyProviderStartupError } = require_provider_auth();
48827
49225
  var { boundedConversationMessages } = require_chat_history_pagination();
48828
49226
  var { MobileRuntime } = require_mobile_runtime();
48829
49227
  var { MobileRelayClient } = require_mobile_relay_client();
48830
49228
  var { createKeyPair } = require_mobile_crypto();
48831
49229
  var { RemoteRuntimeClient } = require_remote_runtime_client();
48832
49230
  var { RemoteRuntimeStore } = require_remote_runtime_store();
49231
+ var { desktopConnectionLink } = require_desktop_connection_link();
48833
49232
  var { PeerRuntimeNetwork } = require_peer_runtime_network();
48834
49233
  var { HeadlessSessionBridge } = require_headless_session_bridge();
48835
49234
  var { createHeadlessChatPreferences } = require_headless_chat_preferences();
@@ -48896,18 +49295,11 @@ var require_headless_runtime = __commonJS({
48896
49295
  "workspace.git.switch",
48897
49296
  "workspace.git.create"
48898
49297
  ]);
48899
- var FINAL_COORDINATION_STATUSES = /* @__PURE__ */ new Set(["done", "pushed", "completed", "finished"]);
48900
- var COORDINATION_IDLE_MS = 30 * 6e4;
48901
49298
  var COORDINATION_COMPLETION_GRACE_MS = 5e3;
48902
- function isCoordinatedSessionEligible(session, now = Date.now()) {
48903
- var _a, _b, _c;
48904
- if (!session || session.state === "stopped" || typeof session.terminalUuid !== "string" || !session.terminalUuid) return false;
48905
- const status = typeof session.workStatus === "string" ? session.workStatus.trim().toLowerCase() : "";
48906
- if (FINAL_COORDINATION_STATUSES.has(status)) return false;
48907
- if (((_a = session.currentTurn) == null ? void 0 : _a.state) === "running" || status === "needs_input" || status === "working") return true;
48908
- const lastActivity = Number(session.lastActivityAt) || Date.parse(session.lastActivityAt);
48909
- if (Number.isFinite(lastActivity) && now - lastActivity > COORDINATION_IDLE_MS) return false;
48910
- return Array.from(((_c = (_b = session.items) == null ? void 0 : _b.values) == null ? void 0 : _c.call(_b)) || []).some((item) => (item == null ? void 0 : item.itemType) === "user_message" || (item == null ? void 0 : item.itemType) === "assistant_message");
49299
+ function isCoordinatedSessionEligible(session) {
49300
+ return Boolean(
49301
+ session && session.state !== "stopped" && typeof session.terminalUuid === "string" && session.terminalUuid
49302
+ );
48911
49303
  }
48912
49304
  function appDataPath({ env = process.env, platform = process.platform, home = os.homedir() } = {}) {
48913
49305
  if (platform === "linux") {
@@ -49163,7 +49555,8 @@ var require_headless_runtime = __commonJS({
49163
49555
  quotaService: suppliedQuotaService = null,
49164
49556
  generateConversationTitle: generateConversationTitleFn = generateConversationTitle,
49165
49557
  forkConversation: forkConversationFn = forkConversation,
49166
- spawnImpl
49558
+ spawnImpl,
49559
+ getuid = typeof process.getuid === "function" ? () => process.getuid() : null
49167
49560
  } = {}) {
49168
49561
  if (typeof getToken !== "function") throw new Error("CAS CLI requires an access token provider");
49169
49562
  if (!validIdentity(identity)) throw new Error("CAS CLI identity is invalid");
@@ -49205,7 +49598,17 @@ var require_headless_runtime = __commonJS({
49205
49598
  const startSessionWithPreferences = async (options) => {
49206
49599
  const launchOptions = chatPreferences.apply(options.agent, options);
49207
49600
  if (launchOptions.permissionMode === void 0) launchOptions.permissionMode = "default";
49208
- const started = await manager.startSession(launchOptions);
49601
+ if (options.agent === "claude" && launchOptions.permissionMode === CHAT_PERMISSION_MODES.FULL_ACCESS && (getuid == null ? void 0 : getuid()) === 0) launchOptions.permissionMode = "default";
49602
+ let started;
49603
+ try {
49604
+ started = await manager.startSession(launchOptions);
49605
+ } catch (error) {
49606
+ const status = classifyProviderStartupError(options.agent, error);
49607
+ if (status) {
49608
+ error.code = status.state === "not_installed" ? "provider_not_installed" : "provider_unauthenticated";
49609
+ }
49610
+ throw error;
49611
+ }
49209
49612
  chatPreferences.write(started.agent || options.agent, {
49210
49613
  permissionMode: started.permissionMode,
49211
49614
  effort: started.effort
@@ -49652,10 +50055,8 @@ ${message}`;
49652
50055
  handoffSession,
49653
50056
  sessionAction,
49654
50057
  closeSession: async ({ sessionId }) => {
49655
- var _a;
49656
- return {
49657
- success: ((_a = await manager.stopSession(sessionId)) == null ? void 0 : _a.stopped) === true
49658
- };
50058
+ await manager.stopSession(sessionId);
50059
+ return { success: true };
49659
50060
  },
49660
50061
  minimizeSession: ({ sessionId }) => updateIdentity(sessionId, { minimized: true }),
49661
50062
  restoreSession: ({ sessionId }) => updateIdentity(sessionId, { minimized: false }),
@@ -49712,7 +50113,11 @@ ${message}`;
49712
50113
  remoteClient: remoteRuntimeClient,
49713
50114
  peerRuntimeNetwork,
49714
50115
  dataPath: resolvedDataPath,
49715
- deliverMessage: deliverCoordinatedMessage
50116
+ deliverMessage: deliverCoordinatedMessage,
50117
+ createPairingLink: async () => {
50118
+ const pairing = await relay.createPairing();
50119
+ return { url: desktopConnectionLink(pairing), expiresAt: pairing.expiresAt };
50120
+ }
49716
50121
  });
49717
50122
  return {
49718
50123
  identity,
@@ -49755,7 +50160,7 @@ ${message}`;
49755
50160
  }
49756
50161
  terminalOrder = Math.max(terminalOrder, saved.terminalOrder || 0);
49757
50162
  try {
49758
- const started = await manager.startSession({
50163
+ const started = await startSessionWithPreferences({
49759
50164
  agent: saved.agent,
49760
50165
  ...saved.accountId && saved.accountId !== "current" ? { accountId: saved.accountId } : {},
49761
50166
  cwd: saved.cwd,
@@ -50163,6 +50568,9 @@ var require_mobile_pairing_ipc = __commonJS({
50163
50568
  "src/infrastructure/mobile/mobile-pairing-ipc.js"(exports2, module2) {
50164
50569
  var MOBILE_PAIRING_EVENT = "mobile-pairing:event";
50165
50570
  var { PRODUCTION_MOBILE_WEB_ORIGIN } = require_mobile_build_channel();
50571
+ var WARM_PAIRING_MIN_REMAINING_MS = 2 * 6e4;
50572
+ var WARM_PAIRING_CHECK_INTERVAL_MS = 3e4;
50573
+ var WARM_PAIRING_SERVE_MIN_REMAINING_MS = 15e3;
50166
50574
  function pairingPayload(pairing, mobileWebOrigin = PRODUCTION_MOBILE_WEB_ORIGIN) {
50167
50575
  const credentials = new URL("codeagentswarm://pair");
50168
50576
  credentials.searchParams.set("relay", new URL(pairing.relayOrigin).origin);
@@ -50184,13 +50592,49 @@ var require_mobile_pairing_ipc = __commonJS({
50184
50592
  setKeepAvailable = () => false,
50185
50593
  createQrDataUrl = (payload, options) => require("qrcode").toDataURL(payload, options)
50186
50594
  }) {
50595
+ var _a;
50187
50596
  const broadcast = (message) => {
50188
50597
  for (const window2 of BrowserWindow.getAllWindows()) {
50189
50598
  if (!window2.isDestroyed()) window2.webContents.send(MOBILE_PAIRING_EVENT, message);
50190
50599
  }
50191
50600
  };
50192
- relayClient.on("status", (status) => broadcast({ type: "status", status }));
50193
- relayClient.on("event", (event) => broadcast({ type: "relay", event }));
50601
+ const buildPairing = async () => {
50602
+ const pairing = await relayClient.createPairing();
50603
+ const qrDataUrl = await createQrDataUrl(
50604
+ pairingPayload(pairing, mobileWebOrigin),
50605
+ {
50606
+ scale: 8,
50607
+ margin: 4,
50608
+ errorCorrectionLevel: "L",
50609
+ color: { dark: "#111827", light: "#ffffff" }
50610
+ }
50611
+ );
50612
+ return { expiresAt: pairing.expiresAt, pairingCode: pairing.pairingCode, qrDataUrl };
50613
+ };
50614
+ let warm = null;
50615
+ let warming = null;
50616
+ const warmPairing = () => {
50617
+ if (warming || relayClient.status !== "online") return warming;
50618
+ const remaining = warm ? warm.expiresAt - Date.now() : 0;
50619
+ if (warm && (warm.served ? remaining > 0 : remaining > WARM_PAIRING_MIN_REMAINING_MS)) return null;
50620
+ warming = buildPairing().then((result) => {
50621
+ warm = result;
50622
+ }, () => {
50623
+ }).finally(() => {
50624
+ warming = null;
50625
+ });
50626
+ return warming;
50627
+ };
50628
+ const warmTimer = setInterval(warmPairing, WARM_PAIRING_CHECK_INTERVAL_MS);
50629
+ (_a = warmTimer.unref) == null ? void 0 : _a.call(warmTimer);
50630
+ relayClient.on("status", (status) => {
50631
+ broadcast({ type: "status", status });
50632
+ if ((status == null ? void 0 : status.status) === "online") void warmPairing();
50633
+ });
50634
+ relayClient.on("event", (event) => {
50635
+ if ((event == null ? void 0 : event.kind) === "pair.scanned") warm = null;
50636
+ broadcast({ type: "relay", event });
50637
+ });
50194
50638
  ipcMain.handle("mobile-pairing:status", () => ({
50195
50639
  success: true,
50196
50640
  ...relayClient.getStatus(),
@@ -50222,23 +50666,16 @@ var require_mobile_pairing_ipc = __commonJS({
50222
50666
  ipcMain.handle("mobile-pairing:create", async () => {
50223
50667
  try {
50224
50668
  if (getKeepAvailable() == null) setKeepAvailable(true);
50225
- const pairing = await relayClient.createPairing();
50226
- const qrDataUrl = await createQrDataUrl(
50227
- pairingPayload(pairing, mobileWebOrigin),
50228
- {
50229
- scale: 8,
50230
- margin: 4,
50231
- errorCorrectionLevel: "L",
50232
- color: { dark: "#111827", light: "#ffffff" }
50233
- }
50234
- );
50669
+ if (warming) await warming;
50670
+ if (!warm || warm.expiresAt - Date.now() < WARM_PAIRING_SERVE_MIN_REMAINING_MS) warm = await buildPairing();
50671
+ warm.served = true;
50235
50672
  return {
50236
50673
  success: true,
50237
- expiresAt: pairing.expiresAt,
50238
- pairingCode: pairing.pairingCode,
50674
+ expiresAt: warm.expiresAt,
50675
+ pairingCode: warm.pairingCode,
50239
50676
  webUrl: mobileWebOrigin,
50240
50677
  keepAvailable: getKeepAvailable() === true,
50241
- qrDataUrl
50678
+ qrDataUrl: warm.qrDataUrl
50242
50679
  };
50243
50680
  } catch (error) {
50244
50681
  return { success: false, error: error.message };
@@ -50274,6 +50711,7 @@ var require_cas = __commonJS({
50274
50711
  var { pairingPayload } = require_mobile_pairing_ipc();
50275
50712
  var { mobileWebOrigin } = require_mobile_build_channel();
50276
50713
  var { requestHeadlessBridge } = require_headless_session_bridge();
50714
+ var { resolvePairingInput } = require_desktop_connection_link();
50277
50715
  var {
50278
50716
  DEFAULT_BACKEND_URL,
50279
50717
  appDataPath,
@@ -50282,8 +50720,7 @@ var require_cas = __commonJS({
50282
50720
  loadIdentity,
50283
50721
  resolveProject
50284
50722
  } = require_headless_runtime();
50285
- var version = true ? "0.0.6" : JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8")).version;
50286
- var DEFAULT_PAIRING_CODE_ORIGIN = "https://codeagentswarm-connect.elcaminodelprogramadorweb.workers.dev";
50723
+ var version = true ? "0.0.15" : JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf8")).version;
50287
50724
  function help() {
50288
50725
  return `CAS CLI ${version}
50289
50726
 
@@ -50293,6 +50730,7 @@ Usage:
50293
50730
  cas-cli setup
50294
50731
  cas-cli doctor
50295
50732
  cas-cli update
50733
+ cas-cli connect
50296
50734
  cas-cli link PAIRING_CODE
50297
50735
  cas-cli remote-status
50298
50736
  cas-cli unlink
@@ -50319,7 +50757,7 @@ host-local configuration; either repeatable flag may be omitted.
50319
50757
  });
50320
50758
  const requestedCommand = parsed.positionals.shift() || "serve";
50321
50759
  const command = requestedCommand === "cloud" ? "serve" : requestedCommand;
50322
- if (!["serve", "setup", "doctor", "update", "help", "link", "remote-status", "unlink"].includes(command)) {
50760
+ if (!["serve", "setup", "doctor", "update", "help", "connect", "link", "remote-status", "unlink"].includes(command)) {
50323
50761
  throw new Error(`Unknown command: ${command}`);
50324
50762
  }
50325
50763
  const pairingInput = command === "link" ? parsed.positionals.shift() : null;
@@ -50331,28 +50769,6 @@ host-local configuration; either repeatable flag may be omitted.
50331
50769
  const { ["projects-root"]: projectsRoot, ...values } = parsed.values;
50332
50770
  return { command, ...values, projectsRoot, ...pairingInput ? { pairingInput } : {} };
50333
50771
  }
50334
- async function resolvePairingInput(raw, fetchImpl = globalThis.fetch) {
50335
- const compact = String(raw || "").trim().toUpperCase().replace(/[\s-]/g, "");
50336
- if (!/^[A-HJ-NP-Z2-9]{8}$/.test(compact)) throw new Error("This pairing code is not valid");
50337
- const code = `${compact.slice(0, 4)}-${compact.slice(4)}`;
50338
- const origin = new URL(process.env.CAS_PAIRING_CODE_ORIGIN || DEFAULT_PAIRING_CODE_ORIGIN);
50339
- const local = ["localhost", "127.0.0.1", "[::1]"].includes(origin.hostname);
50340
- if (origin.protocol !== "https:" && !(origin.protocol === "http:" && local) || origin.username || origin.password) {
50341
- throw new Error("The pairing service is not secure");
50342
- }
50343
- try {
50344
- const response = await fetchImpl(`${origin.origin}/api/mobile/pairing-code/${encodeURIComponent(code)}`, {
50345
- headers: { Accept: "application/json" },
50346
- signal: AbortSignal.timeout(1e4)
50347
- });
50348
- if (!response.ok) throw new Error();
50349
- const body = await response.json();
50350
- if (typeof body.pairingUri !== "string") throw new Error();
50351
- return body.pairingUri;
50352
- } catch (_) {
50353
- throw new Error("This pairing code is invalid or has expired");
50354
- }
50355
- }
50356
50772
  async function linkRemoteRuntime(pairingInput, { output = console.log, request = requestHeadlessBridge } = {}) {
50357
50773
  var _a, _b;
50358
50774
  const pairing = await resolvePairingInput(pairingInput);
@@ -50374,6 +50790,13 @@ host-local configuration; either repeatable flag may be omitted.
50374
50790
  }
50375
50791
  throw new Error("Pairing timed out before it was confirmed on the Mac");
50376
50792
  }
50793
+ async function createDesktopConnectionLink({ output = console.log, request = requestHeadlessBridge } = {}) {
50794
+ const result = await request(appDataPath(), "POST", "/admin/pairing-link");
50795
+ output("Open this link on the Mac running CodeAgentSwarm Desktop:");
50796
+ output(result.url);
50797
+ output("The link is single-use and expires in 5 minutes.");
50798
+ return result;
50799
+ }
50377
50800
  async function printRemoteStatus({ output = console.log, request = requestHeadlessBridge } = {}) {
50378
50801
  var _a, _b;
50379
50802
  const state = await request(appDataPath(), "GET", "/admin/remote-runtime");
@@ -50538,6 +50961,7 @@ CAS Cloud is serving ${host.projects.length} registered project${host.projects.l
50538
50961
  if (options.command === "doctor") return doctor();
50539
50962
  if (options.command === "setup") return setupCloud();
50540
50963
  if (options.command === "update") return updateInstallation();
50964
+ if (options.command === "connect") return createDesktopConnectionLink();
50541
50965
  if (options.command === "link") return linkRemoteRuntime(options.pairingInput);
50542
50966
  if (options.command === "remote-status") return printRemoteStatus();
50543
50967
  if (options.command === "unlink") return unlinkRemoteRuntime();
@@ -50552,6 +50976,7 @@ CAS Cloud is serving ${host.projects.length} registered project${host.projects.l
50552
50976
  module2.exports = {
50553
50977
  AGENT_BINARIES,
50554
50978
  doctor,
50979
+ createDesktopConnectionLink,
50555
50980
  help,
50556
50981
  linkRemoteRuntime,
50557
50982
  main,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeagentswarm/cas-cloud",
3
- "version": "0.0.6",
3
+ "version": "0.0.15",
4
4
  "description": "CAS Cloud runtime for remote CodeAgentSwarm sessions",
5
5
  "author": "Arturo Garcia <hello@codeagentswarm.com>",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",