@phenx-inc/ctlsurf 0.15.0 → 0.15.1

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 (29) hide show
  1. package/out/headless/index.mjs +89 -71
  2. package/out/headless/index.mjs.map +3 -3
  3. package/out/main/index.js +90 -72
  4. package/out/preload/index.js +1 -1
  5. package/out/renderer/assets/{cssMode-CJR4iD-G.js → cssMode-B77M9HlN.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-BRI9ISq9.js → freemarker2-oUT2zvyQ.js} +1 -1
  7. package/out/renderer/assets/{handlebars-3hL6XIZI.js → handlebars-jCckoi5j.js} +1 -1
  8. package/out/renderer/assets/{html-Dd5_vBoP.js → html-kAgrAaKW.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-DgNtlKcG.js → htmlMode-DIWkIawy.js} +3 -3
  10. package/out/renderer/assets/{index-5azC4yNn.js → index-CN3UN7Tm.js} +32 -27
  11. package/out/renderer/assets/{javascript-D0vPLEJg.js → javascript-B9GYUE-I.js} +2 -2
  12. package/out/renderer/assets/{jsonMode-DBvR0zMu.js → jsonMode-BzFKTZjJ.js} +3 -3
  13. package/out/renderer/assets/{liquid-CLoc6Tx7.js → liquid-Ccw-S_Y6.js} +1 -1
  14. package/out/renderer/assets/{lspLanguageFeatures-CiaMaQoY.js → lspLanguageFeatures-BE0g7ubH.js} +1 -1
  15. package/out/renderer/assets/{mdx-DTsq6tlv.js → mdx-oXeAA0qU.js} +1 -1
  16. package/out/renderer/assets/{python-D04DpZDr.js → python-DlSX3TvA.js} +1 -1
  17. package/out/renderer/assets/{razor-Di6CJ6s8.js → razor-ButO9S7r.js} +1 -1
  18. package/out/renderer/assets/{tsMode-mfv6mHOv.js → tsMode-CTCpE-67.js} +1 -1
  19. package/out/renderer/assets/{typescript-CDA0Hhef.js → typescript-9o62WOnS.js} +1 -1
  20. package/out/renderer/assets/{xml-BU0voRuq.js → xml-C4nOH2_D.js} +1 -1
  21. package/out/renderer/assets/{yaml-qzFWnaZJ.js → yaml-mXYgKoWW.js} +1 -1
  22. package/out/renderer/index.html +1 -1
  23. package/package.json +1 -1
  24. package/src/main/index.ts +2 -2
  25. package/src/main/orchestrator.ts +2 -2
  26. package/src/main/ticketStore.ts +100 -66
  27. package/src/preload/index.ts +3 -3
  28. package/src/renderer/App.tsx +2 -2
  29. package/src/renderer/components/TicketPanel.tsx +16 -5
@@ -17314,7 +17314,7 @@ var require_package = __commonJS({
17314
17314
  "package.json"(exports, module) {
17315
17315
  module.exports = {
17316
17316
  name: "@phenx-inc/ctlsurf",
17317
- version: "0.15.0",
17317
+ version: "0.15.1",
17318
17318
  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",
17319
17319
  main: "out/main/index.js",
17320
17320
  bin: {
@@ -19105,21 +19105,19 @@ var COLUMNS2 = [
19105
19105
  function log3(...args) {
19106
19106
  log("[ticket-store]", ...args);
19107
19107
  }
19108
- function findPageByTitle2(pages, title) {
19108
+ function findPagesByTitle(pages, title) {
19109
+ const found = [];
19109
19110
  for (const p of pages) {
19110
- if (p?.title === title) return p;
19111
- if (p?.children?.length) {
19112
- const c = findPageByTitle2(p.children, title);
19113
- if (c) return c;
19114
- }
19111
+ if (p?.title === title) found.push(p);
19112
+ if (p?.children?.length) found.push(...findPagesByTitle(p.children, title));
19115
19113
  }
19116
- return null;
19114
+ return found;
19117
19115
  }
19118
19116
  var TicketStore = class {
19119
19117
  api;
19120
- // Per-cwd cache of the resolved Tickets datastore: the block (the datastore
19121
- // element) and the Agent Datastore page it lives on. Both ids are handed to
19122
- // the coding agent so it can read/update tickets via the ctlsurf MCP tools.
19118
+ // Per-cwd cache of every resolved Tickets datastore. `primary` is where new
19119
+ // tickets are written and what gets handed to the coding agent; `all` covers
19120
+ // duplicate Agent Datastore pages so listing misses nothing.
19123
19121
  dsCache = /* @__PURE__ */ new Map();
19124
19122
  constructor(api) {
19125
19123
  this.api = api;
@@ -19129,11 +19127,11 @@ var TicketStore = class {
19129
19127
  const title = input.title?.trim();
19130
19128
  if (!title) return { ok: false, error: "Title is required" };
19131
19129
  try {
19132
- const ds = await this.ensureDatastore(cwd, true);
19133
- if (!ds) {
19130
+ const stores = await this.resolveStores(cwd, true);
19131
+ if (!stores) {
19134
19132
  return { ok: false, error: "No ctlsurf project found for this folder" };
19135
19133
  }
19136
- await this.api.addRow(ds.blockId, {
19134
+ await this.api.addRow(stores.primary.blockId, {
19137
19135
  Title: title,
19138
19136
  Description: input.description?.trim() || "",
19139
19137
  Status: input.status || "Open",
@@ -19147,14 +19145,15 @@ var TicketStore = class {
19147
19145
  return { ok: false, error: err?.message || String(err) };
19148
19146
  }
19149
19147
  }
19150
- /** Updates an existing ticket row in the project's Tickets datastore. */
19151
- async updateTicket(cwd, rowId, input) {
19148
+ /** Updates an existing ticket row. `blockId` names the store the row lives
19149
+ * in (from listTickets); without it the primary store is assumed. */
19150
+ async updateTicket(cwd, rowId, input, blockId) {
19152
19151
  const title = input.title?.trim();
19153
19152
  if (!title) return { ok: false, error: "Title is required" };
19154
19153
  try {
19155
- const ds = await this.ensureDatastore(cwd, false);
19156
- if (!ds) return { ok: false, error: "No Tickets datastore for this project" };
19157
- await this.api.updateRow(ds.blockId, rowId, {
19154
+ const stores = await this.resolveStores(cwd, false);
19155
+ if (!stores) return { ok: false, error: "No Tickets datastore for this project" };
19156
+ await this.api.updateRow(blockId || stores.primary.blockId, rowId, {
19158
19157
  Title: title,
19159
19158
  Description: input.description?.trim() || "",
19160
19159
  Status: input.status || "Open",
@@ -19167,28 +19166,39 @@ var TicketStore = class {
19167
19166
  return { ok: false, error: err?.message || String(err) };
19168
19167
  }
19169
19168
  }
19170
- /** Lists existing tickets for the project, newest first. Does not create the
19171
- * datastore — an unconfigured project simply has no tickets yet. */
19169
+ /** Lists existing tickets across every Tickets store in the project's
19170
+ * folder, newest first. Does not create the datastore — an unconfigured
19171
+ * project simply has no tickets yet. */
19172
19172
  async listTickets(cwd) {
19173
19173
  try {
19174
- const ds = await this.ensureDatastore(cwd, false);
19175
- if (!ds) return { ok: true, tickets: [] };
19176
- const res = await this.api.queryRows(ds.blockId, { orderBy: "Created", order: "desc", limit: 200 });
19177
- const tickets = (res?.rows || []).map((r) => ({
19178
- id: r.id,
19179
- title: String(r.data?.Title ?? ""),
19180
- description: String(r.data?.Description ?? ""),
19181
- status: String(r.data?.Status ?? "Open"),
19182
- priority: String(r.data?.Priority ?? "Med"),
19183
- created: r.data?.Created ?? r.created_at ?? null
19184
- }));
19185
- return { ok: true, tickets, blockId: ds.blockId, pageId: ds.pageId };
19174
+ const stores = await this.resolveStores(cwd, false);
19175
+ if (!stores) return { ok: true, tickets: [] };
19176
+ const tickets = [];
19177
+ for (const ds of stores.all) {
19178
+ const res = await this.api.queryRows(ds.blockId, { orderBy: "Created", order: "desc", limit: 200 });
19179
+ for (const r of res?.rows || []) {
19180
+ tickets.push({
19181
+ id: r.id,
19182
+ title: String(r.data?.Title ?? ""),
19183
+ description: String(r.data?.Description ?? ""),
19184
+ status: String(r.data?.Status ?? "Open"),
19185
+ priority: String(r.data?.Priority ?? "Med"),
19186
+ created: r.data?.Created ?? r.created_at ?? null,
19187
+ blockId: ds.blockId,
19188
+ pageId: ds.pageId
19189
+ });
19190
+ }
19191
+ }
19192
+ if (stores.all.length > 1) {
19193
+ tickets.sort((a, b) => (b.created || "").localeCompare(a.created || ""));
19194
+ }
19195
+ return { ok: true, tickets, blockId: stores.primary.blockId, pageId: stores.primary.pageId };
19186
19196
  } catch (err) {
19187
19197
  log3(`listTickets failed for ${cwd}: ${err?.message || err}`);
19188
19198
  return { ok: false, tickets: [], error: err?.message || String(err) };
19189
19199
  }
19190
19200
  }
19191
- async ensureDatastore(cwd, create) {
19201
+ async resolveStores(cwd, create) {
19192
19202
  const cached = this.dsCache.get(cwd);
19193
19203
  if (cached) return cached;
19194
19204
  let folder = null;
@@ -19199,17 +19209,27 @@ var TicketStore = class {
19199
19209
  }
19200
19210
  if (!folder?.id) return null;
19201
19211
  const folderDetail = await this.api.getFolder(folder.id);
19202
- const agentPage = findPageByTitle2(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE2);
19203
- if (!agentPage?.id) return null;
19204
- const pageId = agentPage.id;
19205
- const blockId = await this.findOrAdoptBlock(pageId);
19206
- if (blockId) {
19207
- await this.ensureColumns(blockId);
19208
- const ds = { blockId, pageId };
19209
- this.dsCache.set(cwd, ds);
19210
- return ds;
19212
+ const agentPages = findPagesByTitle(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE2);
19213
+ if (agentPages.length === 0) return null;
19214
+ const rootPath = folder.root_path || cwd;
19215
+ agentPages.sort((a, b) => Number(b?.root_path === rootPath) - Number(a?.root_path === rootPath));
19216
+ if (agentPages.length > 1) {
19217
+ log3(`Found ${agentPages.length} "Agent Datastore" pages for ${cwd}; aggregating tickets across all of them`);
19218
+ }
19219
+ const all = [];
19220
+ for (const page of agentPages) {
19221
+ for (const blockId of await this.findTicketBlocks(page.id)) {
19222
+ all.push({ blockId, pageId: page.id });
19223
+ }
19224
+ }
19225
+ if (all.length > 0) {
19226
+ for (const ds of all) await this.ensureColumns(ds.blockId);
19227
+ const stores = { primary: all[0], all };
19228
+ this.dsCache.set(cwd, stores);
19229
+ return stores;
19211
19230
  }
19212
19231
  if (!create) return null;
19232
+ const pageId = agentPages[0].id;
19213
19233
  const columns = COLUMNS2.map((c, i) => ({
19214
19234
  id: `col_${i}`,
19215
19235
  name: c.name,
@@ -19224,47 +19244,45 @@ var TicketStore = class {
19224
19244
  if (created?.id) {
19225
19245
  log3(`Created "${DATASTORE_TITLE2}" datastore on Agent Datastore page for ${cwd}`);
19226
19246
  const ds = { blockId: created.id, pageId };
19227
- this.dsCache.set(cwd, ds);
19228
- return ds;
19247
+ const stores = { primary: ds, all: [ds] };
19248
+ this.dsCache.set(cwd, stores);
19249
+ return stores;
19229
19250
  }
19230
19251
  return null;
19231
19252
  }
19232
- /** Finds an existing Tickets block on the page. Prefers a system_key match
19233
- * (which survives title renames). Falls back to title match for legacy blocks
19234
- * and backfills system_key on the way out. */
19235
- async findOrAdoptBlock(pageId) {
19253
+ /** Finds every Tickets block on the page: system_key matches first, then
19254
+ * title matches for legacy blocks. When no system_key block exists, the
19255
+ * first legacy block is adopted (system_key backfilled so it survives a
19256
+ * later rename); extra title matches are still listed but left unstamped so
19257
+ * the primary can't flip between sessions. */
19258
+ async findTicketBlocks(pageId) {
19236
19259
  const summaries = await this.api.getPageBlockSummaries(pageId) || [];
19237
19260
  const datastoreSummaries = summaries.filter((b) => b?.type === "datastore" && b?.id);
19238
- if (datastoreSummaries.length === 0) return null;
19239
- let titleFallbackId = null;
19240
- let titleFallbackProps = null;
19261
+ if (datastoreSummaries.length === 0) return [];
19262
+ const keyed = [];
19263
+ const legacy = [];
19241
19264
  for (const s of datastoreSummaries) {
19242
19265
  try {
19243
19266
  const block = await this.api.getBlock(s.id);
19244
19267
  const props = block?.props || {};
19245
- if (props.system_key === SYSTEM_KEY2) {
19246
- return s.id;
19247
- }
19248
- if (s.title === DATASTORE_TITLE2 && titleFallbackId === null) {
19249
- titleFallbackId = s.id;
19250
- titleFallbackProps = props;
19251
- }
19268
+ if (props.system_key === SYSTEM_KEY2) keyed.push(s.id);
19269
+ else if (s.title === DATASTORE_TITLE2) legacy.push({ id: s.id, props });
19252
19270
  } catch (err) {
19253
19271
  log3(`getBlock(${s.id}) failed during lookup: ${err?.message || err}`);
19254
19272
  }
19255
19273
  }
19256
- if (titleFallbackId) {
19274
+ if (keyed.length === 0 && legacy.length > 0) {
19275
+ const adopt = legacy[0];
19257
19276
  try {
19258
- await this.api.updateBlock(titleFallbackId, {
19259
- props: { ...titleFallbackProps || {}, system_key: SYSTEM_KEY2 }
19277
+ await this.api.updateBlock(adopt.id, {
19278
+ props: { ...adopt.props, system_key: SYSTEM_KEY2 }
19260
19279
  });
19261
- log3(`Backfilled system_key on legacy Tickets block ${titleFallbackId}`);
19280
+ log3(`Backfilled system_key on legacy Tickets block ${adopt.id}`);
19262
19281
  } catch (err) {
19263
- log3(`backfill system_key failed on ${titleFallbackId}: ${err?.message || err}`);
19282
+ log3(`backfill system_key failed on ${adopt.id}: ${err?.message || err}`);
19264
19283
  }
19265
- return titleFallbackId;
19266
19284
  }
19267
- return null;
19285
+ return [...keyed, ...legacy.map((l) => l.id)];
19268
19286
  }
19269
19287
  async ensureColumns(blockId) {
19270
19288
  try {
@@ -20085,11 +20103,11 @@ var COLUMNS3 = [
20085
20103
  function log4(...args) {
20086
20104
  log("[observer-store]", ...args);
20087
20105
  }
20088
- function findPageByTitle3(pages, title) {
20106
+ function findPageByTitle2(pages, title) {
20089
20107
  for (const p of pages) {
20090
20108
  if (p?.title === title) return p;
20091
20109
  if (p?.children?.length) {
20092
- const c = findPageByTitle3(p.children, title);
20110
+ const c = findPageByTitle2(p.children, title);
20093
20111
  if (c) return c;
20094
20112
  }
20095
20113
  }
@@ -20158,7 +20176,7 @@ var ObserverStore = class {
20158
20176
  }
20159
20177
  if (!folder?.id) return null;
20160
20178
  const folderDetail = await this.api.getFolder(folder.id);
20161
- const agentPage = findPageByTitle3(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE3);
20179
+ const agentPage = findPageByTitle2(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE3);
20162
20180
  if (!agentPage?.id) return null;
20163
20181
  const summaries = await this.api.getPageBlockSummaries(agentPage.id) || [];
20164
20182
  for (const s of summaries.filter((b) => b?.type === spec.type && b?.id)) {
@@ -21255,13 +21273,13 @@ var Orchestrator = class {
21255
21273
  }
21256
21274
  return this.ticketStore.addTicket(cwd, input);
21257
21275
  }
21258
- async updateTicketForActiveTab(rowId, input) {
21276
+ async updateTicketForActiveTab(rowId, input, blockId) {
21259
21277
  const cwd = this.getActiveTabProjectPath();
21260
21278
  if (!cwd) return { ok: false, error: "No active terminal tab" };
21261
21279
  if (!this.ctlsurfApi.getApiKey()) {
21262
21280
  return { ok: false, error: "ctlsurf API key not configured" };
21263
21281
  }
21264
- return this.ticketStore.updateTicket(cwd, rowId, input);
21282
+ return this.ticketStore.updateTicket(cwd, rowId, input, blockId);
21265
21283
  }
21266
21284
  async listTicketsForActiveTab() {
21267
21285
  const cwd = this.getActiveTabProjectPath();