@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.
- package/bin/ctlsurf-worker.js +38 -22
- package/out/headless/index.mjs +295 -3
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +351 -48
- package/out/preload/index.js +11 -0
- package/out/renderer/assets/{cssMode-CYoo4t9f.js → cssMode-D5dPwEy5.js} +3 -3
- package/out/renderer/assets/{freemarker2--UQnPZsn.js → freemarker2-c5jJjQ9s.js} +1 -1
- package/out/renderer/assets/{handlebars-DVDrmX0C.js → handlebars-BTbmOxx9.js} +1 -1
- package/out/renderer/assets/{html-D1-cXoLy.js → html-3cIIQcxO.js} +1 -1
- package/out/renderer/assets/{htmlMode-f5nBuprq.js → htmlMode-DYbpW1yY.js} +3 -3
- package/out/renderer/assets/{index-65hyKM_8.css → index-6KvOnYL1.css} +404 -0
- package/out/renderer/assets/{index-D23nru43.js → index-D2MUZin7.js} +332 -23
- package/out/renderer/assets/{javascript-CcarFzBL.js → javascript-CDuCMm-6.js} +2 -2
- package/out/renderer/assets/{jsonMode-BvF-xK9U.js → jsonMode-COLqbq0s.js} +3 -3
- package/out/renderer/assets/{liquid-CHLtUKl2.js → liquid-BFcqZizB.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-B9aNeatS.js → lspLanguageFeatures-CbkEcL-z.js} +1 -1
- package/out/renderer/assets/{mdx-HGDrkifZ.js → mdx-DyK93oEE.js} +1 -1
- package/out/renderer/assets/{python-B_dPzjJ6.js → python-D4lCwSVr.js} +1 -1
- package/out/renderer/assets/{razor-CHheM4ot.js → razor-DdkE9XVt.js} +1 -1
- package/out/renderer/assets/{tsMode-CdC3i1gG.js → tsMode-BrQ4Fsc-.js} +1 -1
- package/out/renderer/assets/{typescript-BX6guVRK.js → typescript-BakbYMnC.js} +1 -1
- package/out/renderer/assets/{xml-CpS-pOPE.js → xml-DHDW9Xhp.js} +1 -1
- package/out/renderer/assets/{yaml-Du0AjOHW.js → yaml-1Ayv_J3q.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/agents.ts +36 -1
- package/src/main/ctlsurfApi.ts +11 -0
- package/src/main/headless.ts +5 -3
- package/src/main/index.ts +24 -2
- package/src/main/orchestrator.ts +66 -0
- package/src/main/ticketStore.ts +252 -0
- package/src/preload/index.ts +17 -0
- package/src/renderer/App.tsx +40 -1
- package/src/renderer/components/TicketPanel.tsx +308 -0
- package/src/renderer/styles.css +404 -0
package/bin/ctlsurf-worker.js
CHANGED
|
@@ -460,35 +460,51 @@ function ask(question) {
|
|
|
460
460
|
})
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
package/out/headless/index.mjs
CHANGED
|
@@ -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.
|
|
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 =
|
|
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 =
|
|
7789
|
+
const found = getBuiltinAgents().find((a) => a.id === args.agent);
|
|
7498
7790
|
agent = found || {
|
|
7499
7791
|
id: args.agent,
|
|
7500
7792
|
name: args.agent,
|