@phenx-inc/ctlsurf 0.3.13 → 0.3.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 (35) hide show
  1. package/bin/ctlsurf-worker.js +38 -22
  2. package/out/headless/index.mjs +295 -3
  3. package/out/headless/index.mjs.map +4 -4
  4. package/out/main/index.js +351 -48
  5. package/out/preload/index.js +11 -0
  6. package/out/renderer/assets/{cssMode-CYoo4t9f.js → cssMode-D5dPwEy5.js} +3 -3
  7. package/out/renderer/assets/{freemarker2--UQnPZsn.js → freemarker2-c5jJjQ9s.js} +1 -1
  8. package/out/renderer/assets/{handlebars-DVDrmX0C.js → handlebars-BTbmOxx9.js} +1 -1
  9. package/out/renderer/assets/{html-D1-cXoLy.js → html-3cIIQcxO.js} +1 -1
  10. package/out/renderer/assets/{htmlMode-f5nBuprq.js → htmlMode-DYbpW1yY.js} +3 -3
  11. package/out/renderer/assets/{index-65hyKM_8.css → index-6KvOnYL1.css} +404 -0
  12. package/out/renderer/assets/{index-D23nru43.js → index-D2MUZin7.js} +332 -23
  13. package/out/renderer/assets/{javascript-CcarFzBL.js → javascript-CDuCMm-6.js} +2 -2
  14. package/out/renderer/assets/{jsonMode-BvF-xK9U.js → jsonMode-COLqbq0s.js} +3 -3
  15. package/out/renderer/assets/{liquid-CHLtUKl2.js → liquid-BFcqZizB.js} +1 -1
  16. package/out/renderer/assets/{lspLanguageFeatures-B9aNeatS.js → lspLanguageFeatures-CbkEcL-z.js} +1 -1
  17. package/out/renderer/assets/{mdx-HGDrkifZ.js → mdx-DyK93oEE.js} +1 -1
  18. package/out/renderer/assets/{python-B_dPzjJ6.js → python-D4lCwSVr.js} +1 -1
  19. package/out/renderer/assets/{razor-CHheM4ot.js → razor-DdkE9XVt.js} +1 -1
  20. package/out/renderer/assets/{tsMode-CdC3i1gG.js → tsMode-BrQ4Fsc-.js} +1 -1
  21. package/out/renderer/assets/{typescript-BX6guVRK.js → typescript-BakbYMnC.js} +1 -1
  22. package/out/renderer/assets/{xml-CpS-pOPE.js → xml-DHDW9Xhp.js} +1 -1
  23. package/out/renderer/assets/{yaml-Du0AjOHW.js → yaml-1Ayv_J3q.js} +1 -1
  24. package/out/renderer/index.html +2 -2
  25. package/package.json +1 -1
  26. package/src/main/agents.ts +36 -1
  27. package/src/main/ctlsurfApi.ts +11 -0
  28. package/src/main/headless.ts +5 -3
  29. package/src/main/index.ts +24 -2
  30. package/src/main/orchestrator.ts +66 -0
  31. package/src/main/ticketStore.ts +252 -0
  32. package/src/preload/index.ts +17 -0
  33. package/src/renderer/App.tsx +40 -1
  34. package/src/renderer/components/TicketPanel.tsx +308 -0
  35. package/src/renderer/styles.css +404 -0
@@ -460,35 +460,51 @@ function ask(question) {
460
460
  })
461
461
  }
462
462
 
463
- // Persist a key to every worker settings.json profile + the shell rc.
464
- function persistKey(key) {
465
- for (const p of allSettingsPaths()) {
466
- const s = normalizeSettings(readSettings(p))
467
- activeProfile(s).apiKey = key
468
- writeSettings(p, s)
463
+ // Export CTLSURF_API_KEY to the OS so MCP clients pick it up. On Windows
464
+ // this is a persistent user env var (setx); elsewhere it's an export line
465
+ // in the shell rc. Either way it applies to new shells, not the current one.
466
+ function exportKeyToEnv(key) {
467
+ if (process.platform === 'win32') {
468
+ try {
469
+ execFileSync('setx', ['CTLSURF_API_KEY', key], { stdio: 'ignore' })
470
+ console.log(` ${G}✓${R} Set CTLSURF_API_KEY (user environment)`)
471
+ console.log(` ${D}Open a new terminal for the env var to apply.${R}`)
472
+ } catch {
473
+ console.log(` ${Y}!${R} Could not run setx — set CTLSURF_API_KEY manually.`)
474
+ }
475
+ return
469
476
  }
470
- console.log(` ${G}✓${R} Saved to worker settings.`)
471
477
 
472
478
  const home = os.homedir()
473
479
  const rc = ['.zshrc', '.bashrc', '.bash_profile']
474
480
  .map((f) => path.join(home, f))
475
481
  .find((f) => fs.existsSync(f))
476
- if (rc) {
477
- try {
478
- let content = fs.readFileSync(rc, 'utf-8')
479
- content = content
480
- .split('\n')
481
- .filter((l) => !/^\s*export\s+CTLSURF_API_KEY=/.test(l))
482
- .join('\n')
483
- if (content && !content.endsWith('\n')) content += '\n'
484
- content += `\n# ctlsurf worker API key\nexport CTLSURF_API_KEY="${key}"\n`
485
- fs.writeFileSync(rc, content)
486
- console.log(` ${G}✓${R} Exported CTLSURF_API_KEY in ${rc}`)
487
- console.log(` ${D}Restart your shell or run: source ${rc}${R}`)
488
- } catch {
489
- console.log(` ${Y}!${R} Could not update ${rc} — set CTLSURF_API_KEY manually.`)
490
- }
482
+ if (!rc) return
483
+ try {
484
+ let content = fs.readFileSync(rc, 'utf-8')
485
+ content = content
486
+ .split('\n')
487
+ .filter((l) => !/^\s*export\s+CTLSURF_API_KEY=/.test(l))
488
+ .join('\n')
489
+ if (content && !content.endsWith('\n')) content += '\n'
490
+ content += `\n# ctlsurf worker API key\nexport CTLSURF_API_KEY="${key}"\n`
491
+ fs.writeFileSync(rc, content)
492
+ console.log(` ${G}✓${R} Exported CTLSURF_API_KEY in ${rc}`)
493
+ console.log(` ${D}Restart your shell or run: source ${rc}${R}`)
494
+ } catch {
495
+ console.log(` ${Y}!${R} Could not update ${rc} — set CTLSURF_API_KEY manually.`)
491
496
  }
497
+ }
498
+
499
+ // Persist a key to every worker settings.json profile + the OS environment.
500
+ function persistKey(key) {
501
+ for (const p of allSettingsPaths()) {
502
+ const s = normalizeSettings(readSettings(p))
503
+ activeProfile(s).apiKey = key
504
+ writeSettings(p, s)
505
+ }
506
+ console.log(` ${G}✓${R} Saved to worker settings.`)
507
+ exportKeyToEnv(key)
492
508
  console.log('')
493
509
  }
494
510
 
@@ -5471,7 +5471,7 @@ var require_package = __commonJS({
5471
5471
  "package.json"(exports, module) {
5472
5472
  module.exports = {
5473
5473
  name: "@phenx-inc/ctlsurf",
5474
- version: "0.3.13",
5474
+ version: "0.3.15",
5475
5475
  description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
5476
5476
  main: "out/main/index.js",
5477
5477
  bin: {
@@ -5619,10 +5619,27 @@ var PtyManager = class {
5619
5619
  };
5620
5620
 
5621
5621
  // src/main/agents.ts
5622
+ import { accessSync, constants } from "fs";
5623
+ import { join, delimiter } from "path";
5622
5624
  function getShellCommand() {
5623
5625
  if (process.platform === "win32") return "powershell.exe";
5624
5626
  return process.env.SHELL || "/bin/zsh";
5625
5627
  }
5628
+ function isCommandAvailable(command) {
5629
+ const dirs = (process.env.PATH || "").split(delimiter).filter(Boolean);
5630
+ const isWin = process.platform === "win32";
5631
+ const exts = isWin ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
5632
+ for (const dir of dirs) {
5633
+ for (const ext of exts) {
5634
+ try {
5635
+ accessSync(join(dir, command + ext), isWin ? constants.F_OK : constants.X_OK);
5636
+ return true;
5637
+ } catch {
5638
+ }
5639
+ }
5640
+ }
5641
+ return false;
5642
+ }
5626
5643
  function getBuiltinAgents() {
5627
5644
  return [
5628
5645
  {
@@ -5649,6 +5666,12 @@ function getBuiltinAgents() {
5649
5666
  }
5650
5667
  ];
5651
5668
  }
5669
+ function getAvailableAgents() {
5670
+ const all = getBuiltinAgents();
5671
+ const coding = all.filter((a) => isCodingAgent(a) && isCommandAvailable(a.command));
5672
+ const shell = all.filter((a) => !isCodingAgent(a));
5673
+ return [...coding, ...shell];
5674
+ }
5652
5675
  function isCodingAgent(agent) {
5653
5676
  return agent.id !== "shell";
5654
5677
  }
@@ -5738,6 +5761,14 @@ var CtlsurfApi = class {
5738
5761
  async updateRow(blockId, rowId, data) {
5739
5762
  return this.request("PUT", `/datastore/${blockId}/rows/${rowId}`, { data });
5740
5763
  }
5764
+ async queryRows(blockId, opts) {
5765
+ const params = new URLSearchParams();
5766
+ if (opts?.orderBy) params.set("order_by", opts.orderBy);
5767
+ if (opts?.order) params.set("order", opts.order);
5768
+ params.set("limit", String(opts?.limit ?? 200));
5769
+ const qs = params.toString();
5770
+ return this.request("GET", `/datastore/${blockId}/rows${qs ? `?${qs}` : ""}`);
5771
+ }
5741
5772
  async getDatastoreSchema(blockId) {
5742
5773
  return this.request("GET", `/datastore/${blockId}/schema`);
5743
5774
  }
@@ -6607,6 +6638,213 @@ var TimeTracker = class {
6607
6638
  }
6608
6639
  };
6609
6640
 
6641
+ // src/main/ticketStore.ts
6642
+ var DATASTORE_TITLE2 = "Tickets";
6643
+ var AGENT_DATASTORE_PAGE_TITLE2 = "Agent Datastore";
6644
+ var SYSTEM_KEY2 = "tickets";
6645
+ var STATUS_OPTIONS = [
6646
+ { value: "Open", color: "blue" },
6647
+ { value: "In Progress", color: "yellow" },
6648
+ { value: "Blocked", color: "red" },
6649
+ { value: "Done", color: "green" }
6650
+ ];
6651
+ var PRIORITY_OPTIONS = [
6652
+ { value: "Low", color: "gray" },
6653
+ { value: "Med", color: "yellow" },
6654
+ { value: "High", color: "red" }
6655
+ ];
6656
+ var COLUMNS2 = [
6657
+ { name: "Title", type: "text" },
6658
+ { name: "Description", type: "text" },
6659
+ { name: "Status", type: "select", options: STATUS_OPTIONS },
6660
+ { name: "Priority", type: "select", options: PRIORITY_OPTIONS },
6661
+ { name: "Created", type: "date" }
6662
+ ];
6663
+ function log3(...args) {
6664
+ try {
6665
+ console.log("[ticket-store]", ...args);
6666
+ } catch {
6667
+ }
6668
+ }
6669
+ function findPageByTitle2(pages, title) {
6670
+ for (const p of pages) {
6671
+ if (p?.title === title) return p;
6672
+ if (p?.children?.length) {
6673
+ const c = findPageByTitle2(p.children, title);
6674
+ if (c) return c;
6675
+ }
6676
+ }
6677
+ return null;
6678
+ }
6679
+ var TicketStore = class {
6680
+ api;
6681
+ blockCache = /* @__PURE__ */ new Map();
6682
+ constructor(api) {
6683
+ this.api = api;
6684
+ }
6685
+ /** Resolves (or creates) the project's Tickets datastore and appends a row. */
6686
+ async addTicket(cwd, input) {
6687
+ const title = input.title?.trim();
6688
+ if (!title) return { ok: false, error: "Title is required" };
6689
+ try {
6690
+ const blockId = await this.ensureDatastore(cwd, true);
6691
+ if (!blockId) {
6692
+ return { ok: false, error: "No ctlsurf project found for this folder" };
6693
+ }
6694
+ await this.api.addRow(blockId, {
6695
+ Title: title,
6696
+ Description: input.description?.trim() || "",
6697
+ Status: input.status || "Open",
6698
+ Priority: input.priority || "Med",
6699
+ Created: (/* @__PURE__ */ new Date()).toISOString()
6700
+ });
6701
+ log3(`Added ticket "${title}" for ${cwd}`);
6702
+ return { ok: true };
6703
+ } catch (err) {
6704
+ log3(`addTicket failed for ${cwd}: ${err?.message || err}`);
6705
+ return { ok: false, error: err?.message || String(err) };
6706
+ }
6707
+ }
6708
+ /** Updates an existing ticket row in the project's Tickets datastore. */
6709
+ async updateTicket(cwd, rowId, input) {
6710
+ const title = input.title?.trim();
6711
+ if (!title) return { ok: false, error: "Title is required" };
6712
+ try {
6713
+ const blockId = await this.ensureDatastore(cwd, false);
6714
+ if (!blockId) return { ok: false, error: "No Tickets datastore for this project" };
6715
+ await this.api.updateRow(blockId, rowId, {
6716
+ Title: title,
6717
+ Description: input.description?.trim() || "",
6718
+ Status: input.status || "Open",
6719
+ Priority: input.priority || "Med"
6720
+ });
6721
+ log3(`Updated ticket ${rowId} for ${cwd}`);
6722
+ return { ok: true };
6723
+ } catch (err) {
6724
+ log3(`updateTicket failed for ${cwd}: ${err?.message || err}`);
6725
+ return { ok: false, error: err?.message || String(err) };
6726
+ }
6727
+ }
6728
+ /** Lists existing tickets for the project, newest first. Does not create the
6729
+ * datastore — an unconfigured project simply has no tickets yet. */
6730
+ async listTickets(cwd) {
6731
+ try {
6732
+ const blockId = await this.ensureDatastore(cwd, false);
6733
+ if (!blockId) return { ok: true, tickets: [] };
6734
+ const res = await this.api.queryRows(blockId, { orderBy: "Created", order: "desc", limit: 200 });
6735
+ const tickets = (res?.rows || []).map((r) => ({
6736
+ id: r.id,
6737
+ title: String(r.data?.Title ?? ""),
6738
+ description: String(r.data?.Description ?? ""),
6739
+ status: String(r.data?.Status ?? "Open"),
6740
+ priority: String(r.data?.Priority ?? "Med"),
6741
+ created: r.data?.Created ?? r.created_at ?? null
6742
+ }));
6743
+ return { ok: true, tickets };
6744
+ } catch (err) {
6745
+ log3(`listTickets failed for ${cwd}: ${err?.message || err}`);
6746
+ return { ok: false, tickets: [], error: err?.message || String(err) };
6747
+ }
6748
+ }
6749
+ async ensureDatastore(cwd, create) {
6750
+ const cached = this.blockCache.get(cwd);
6751
+ if (cached) return cached;
6752
+ let folder = null;
6753
+ try {
6754
+ folder = await this.api.findFolderByPath(cwd);
6755
+ } catch {
6756
+ return null;
6757
+ }
6758
+ if (!folder?.id) return null;
6759
+ const folderDetail = await this.api.getFolder(folder.id);
6760
+ const agentPage = findPageByTitle2(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE2);
6761
+ if (!agentPage?.id) return null;
6762
+ const blockId = await this.findOrAdoptBlock(agentPage.id);
6763
+ if (blockId) {
6764
+ await this.ensureColumns(blockId);
6765
+ this.blockCache.set(cwd, blockId);
6766
+ return blockId;
6767
+ }
6768
+ if (!create) return null;
6769
+ const columns = COLUMNS2.map((c, i) => ({
6770
+ id: `col_${i}`,
6771
+ name: c.name,
6772
+ type: c.type,
6773
+ ...c.options ? { options: c.options } : {}
6774
+ }));
6775
+ const created = await this.api.createBlock(agentPage.id, {
6776
+ type: "datastore",
6777
+ title: DATASTORE_TITLE2,
6778
+ props: { columns, system_key: SYSTEM_KEY2 }
6779
+ });
6780
+ if (created?.id) {
6781
+ log3(`Created "${DATASTORE_TITLE2}" datastore on Agent Datastore page for ${cwd}`);
6782
+ this.blockCache.set(cwd, created.id);
6783
+ return created.id;
6784
+ }
6785
+ return null;
6786
+ }
6787
+ /** Finds an existing Tickets block on the page. Prefers a system_key match
6788
+ * (which survives title renames). Falls back to title match for legacy blocks
6789
+ * and backfills system_key on the way out. */
6790
+ async findOrAdoptBlock(pageId) {
6791
+ const summaries = await this.api.getPageBlockSummaries(pageId) || [];
6792
+ const datastoreSummaries = summaries.filter((b) => b?.type === "datastore" && b?.id);
6793
+ if (datastoreSummaries.length === 0) return null;
6794
+ let titleFallbackId = null;
6795
+ let titleFallbackProps = null;
6796
+ for (const s of datastoreSummaries) {
6797
+ try {
6798
+ const block = await this.api.getBlock(s.id);
6799
+ const props = block?.props || {};
6800
+ if (props.system_key === SYSTEM_KEY2) {
6801
+ return s.id;
6802
+ }
6803
+ if (s.title === DATASTORE_TITLE2 && titleFallbackId === null) {
6804
+ titleFallbackId = s.id;
6805
+ titleFallbackProps = props;
6806
+ }
6807
+ } catch (err) {
6808
+ log3(`getBlock(${s.id}) failed during lookup: ${err?.message || err}`);
6809
+ }
6810
+ }
6811
+ if (titleFallbackId) {
6812
+ try {
6813
+ await this.api.updateBlock(titleFallbackId, {
6814
+ props: { ...titleFallbackProps || {}, system_key: SYSTEM_KEY2 }
6815
+ });
6816
+ log3(`Backfilled system_key on legacy Tickets block ${titleFallbackId}`);
6817
+ } catch (err) {
6818
+ log3(`backfill system_key failed on ${titleFallbackId}: ${err?.message || err}`);
6819
+ }
6820
+ return titleFallbackId;
6821
+ }
6822
+ return null;
6823
+ }
6824
+ async ensureColumns(blockId) {
6825
+ try {
6826
+ const schema = await this.api.getDatastoreSchema(blockId);
6827
+ const existingCols = schema.columns || [];
6828
+ const existingNames = new Set(existingCols.map((c) => c.name));
6829
+ const missing = COLUMNS2.filter((c) => !existingNames.has(c.name));
6830
+ if (missing.length === 0) return;
6831
+ const usedIds = new Set(existingCols.map((c) => c.id));
6832
+ let nextIdx = existingCols.length;
6833
+ const appended = missing.map((c) => {
6834
+ let id = `col_${nextIdx++}`;
6835
+ while (usedIds.has(id)) id = `col_${nextIdx++}`;
6836
+ usedIds.add(id);
6837
+ return { id, name: c.name, type: c.type, ...c.options ? { options: c.options } : {} };
6838
+ });
6839
+ const merged = [...existingCols, ...appended];
6840
+ await this.api.updateDatastoreSchema(blockId, merged);
6841
+ log3(`Added ${missing.length} missing column(s) to existing Tickets datastore: ${missing.map((c) => c.name).join(", ")}`);
6842
+ } catch (err) {
6843
+ log3(`ensureColumns failed: ${err?.message || err}`);
6844
+ }
6845
+ }
6846
+ };
6847
+
6610
6848
  // src/main/orchestrator.ts
6611
6849
  var DEFAULT_IDLE_TIMEOUT_MIN = 15;
6612
6850
  var DEFAULT_PROFILES = {
@@ -6629,6 +6867,7 @@ var Orchestrator = class {
6629
6867
  bridge = new ConversationBridge();
6630
6868
  workerWs;
6631
6869
  timeTracker = new TimeTracker(this.ctlsurfApi);
6870
+ ticketStore = new TicketStore(this.ctlsurfApi);
6632
6871
  // State
6633
6872
  tabs = /* @__PURE__ */ new Map();
6634
6873
  activeTabId = null;
@@ -6641,6 +6880,7 @@ var Orchestrator = class {
6641
6880
  };
6642
6881
  noProjectPollTimer = null;
6643
6882
  noProjectPollCwd = null;
6883
+ currentProjectName = null;
6644
6884
  constructor(settingsDir, events) {
6645
6885
  this.settingsDir = settingsDir;
6646
6886
  this.events = events;
@@ -6665,12 +6905,14 @@ var Orchestrator = class {
6665
6905
  log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`);
6666
6906
  events.onWorkerRegistered(data);
6667
6907
  if (!data.folder_id) {
6908
+ this.setProjectName(null);
6668
6909
  events.onWorkerStatus("no_project");
6669
6910
  if (this.currentCwd && data.status !== "pending_approval") {
6670
6911
  this.startNoProjectPolling(this.currentCwd);
6671
6912
  }
6672
6913
  } else {
6673
6914
  this.stopNoProjectPolling();
6915
+ this.resolveProjectName(data.folder_id);
6674
6916
  }
6675
6917
  },
6676
6918
  onTerminalInput: (data) => {
@@ -6705,6 +6947,26 @@ var Orchestrator = class {
6705
6947
  get cwd() {
6706
6948
  return this.currentCwd;
6707
6949
  }
6950
+ // Name of the connected ctlsurf project (folder) for the desktop header.
6951
+ get projectName() {
6952
+ return this.currentProjectName;
6953
+ }
6954
+ setProjectName(name) {
6955
+ if (this.currentProjectName === name) return;
6956
+ this.currentProjectName = name;
6957
+ this.events.onProjectChanged?.(name);
6958
+ }
6959
+ // Resolve the connected folder's human-readable name. Best-effort: a failed
6960
+ // lookup just leaves the project name unset rather than blocking anything.
6961
+ async resolveProjectName(folderId) {
6962
+ try {
6963
+ const folder = await this.ctlsurfApi.getFolder(folderId);
6964
+ const name = folder?.name ?? folder?.title;
6965
+ this.setProjectName(typeof name === "string" && name ? name : null);
6966
+ } catch (err) {
6967
+ log(`[worker-ws] Failed to resolve project name for folder ${folderId}: ${err}`);
6968
+ }
6969
+ }
6708
6970
  get agent() {
6709
6971
  return this.currentAgent;
6710
6972
  }
@@ -6977,6 +7239,36 @@ var Orchestrator = class {
6977
7239
  await this.timeTracker.endSession(this.activeTabId);
6978
7240
  }
6979
7241
  }
7242
+ // ─── Tickets (active tab) ───────────────────────
7243
+ /** cwd of the focused terminal tab, or null if no tab is active. */
7244
+ getActiveTabCwd() {
7245
+ if (!this.activeTabId) return null;
7246
+ return this.tabs.get(this.activeTabId)?.cwd ?? null;
7247
+ }
7248
+ async addTicketForActiveTab(input) {
7249
+ const cwd = this.getActiveTabCwd();
7250
+ if (!cwd) return { ok: false, error: "No active terminal tab" };
7251
+ if (!this.ctlsurfApi.getApiKey()) {
7252
+ return { ok: false, error: "ctlsurf API key not configured" };
7253
+ }
7254
+ return this.ticketStore.addTicket(cwd, input);
7255
+ }
7256
+ async updateTicketForActiveTab(rowId, input) {
7257
+ const cwd = this.getActiveTabCwd();
7258
+ if (!cwd) return { ok: false, error: "No active terminal tab" };
7259
+ if (!this.ctlsurfApi.getApiKey()) {
7260
+ return { ok: false, error: "ctlsurf API key not configured" };
7261
+ }
7262
+ return this.ticketStore.updateTicket(cwd, rowId, input);
7263
+ }
7264
+ async listTicketsForActiveTab() {
7265
+ const cwd = this.getActiveTabCwd();
7266
+ if (!cwd) return { ok: false, tickets: [], error: "No active terminal tab" };
7267
+ if (!this.ctlsurfApi.getApiKey()) {
7268
+ return { ok: false, tickets: [], error: "ctlsurf API key not configured" };
7269
+ }
7270
+ return this.ticketStore.listTickets(cwd);
7271
+ }
6980
7272
  // ─── Worker WebSocket ───────────────────────────
6981
7273
  connectWorkerWs(agent, cwd) {
6982
7274
  const profile = this.getActiveProfile();
@@ -7465,7 +7757,7 @@ async function main() {
7465
7757
  const settingsDir = getSettingsDir(false);
7466
7758
  await checkVersionAndNotify();
7467
7759
  const tui = new Tui();
7468
- const agents = getBuiltinAgents();
7760
+ const agents = getAvailableAgents();
7469
7761
  const orchestrator = new Orchestrator(settingsDir, {
7470
7762
  onPtyData: (_tabId, data) => {
7471
7763
  tui.writePtyData(data);
@@ -7494,7 +7786,7 @@ async function main() {
7494
7786
  let agent;
7495
7787
  let trackTimeOverride;
7496
7788
  if (args.agent) {
7497
- const found = agents.find((a) => a.id === args.agent);
7789
+ const found = getBuiltinAgents().find((a) => a.id === args.agent);
7498
7790
  agent = found || {
7499
7791
  id: args.agent,
7500
7792
  name: args.agent,