@sideboard-ai/core 0.1.184 → 0.1.186

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2213,13 +2213,10 @@ type LinearAssignedIssuesResult = {
2213
2213
  };
2214
2214
  /** `me`, `unassigned`, `all`, a Linear user id, or a display name. */
2215
2215
  type LinearAssigneeFilter = string;
2216
- /**
2217
- * Linear `IssueFilter` for open issues. Default assignee is `me` so existing
2218
- * Home / Create-from callers stay assigned-to-you unless they ask otherwise.
2219
- */
2220
2216
  declare function buildLinearIssueFilter(input?: {
2221
2217
  assignee?: string | null;
2222
2218
  updatedSince?: string | null;
2219
+ query?: string | null;
2223
2220
  }): Record<string, unknown>;
2224
2221
  /**
2225
2222
  * List or search open Linear issues. `assignee` is `me` (default when there is
@@ -2234,13 +2231,16 @@ declare function listLinearIssuesFiltered(opts?: {
2234
2231
  updatedSince?: string | null;
2235
2232
  }): Promise<LinearAssignedIssuesResult>;
2236
2233
  /**
2237
- * Comments created at/after `since` on issues matching the same assignee filter.
2238
- * One GraphQL query — do not get_issue each ticket to check for new comments.
2234
+ * Comments created at/after `since` on issues matching the same assignee / search
2235
+ * filter. One GraphQL query — do not get_issue each ticket to check for new comments.
2236
+ * Pass `issueIdentifiers` (search hits) to keep only those tickets; an empty
2237
+ * allowlist means no comments.
2239
2238
  */
2240
2239
  declare function listLinearCommentsSince(opts: {
2241
2240
  since: string;
2242
2241
  assignee?: string | null;
2243
2242
  query?: string;
2243
+ issueIdentifiers?: string[];
2244
2244
  limit?: number;
2245
2245
  apiKey?: string | null;
2246
2246
  }): Promise<IssueActivityComment[]>;
@@ -2364,7 +2364,8 @@ declare function resolveGitHubIssueRepo(repoPath?: string | null): Promise<{
2364
2364
  declare function toGitHubIssueInfo(issue: GitHubIssue): IssueInfo;
2365
2365
  /**
2366
2366
  * Repo issue comments created at/after `since`. Pass `issueIdentifiers` (#12)
2367
- * to keep only comments on that inbox page — one REST call, not N issue views.
2367
+ * to keep only comments on that inbox page — paginated REST, not N issue views.
2368
+ * An empty allowlist means no comments (do not fall through to the whole repo).
2368
2369
  */
2369
2370
  declare function listGitHubIssueCommentsSince(opts: {
2370
2371
  since: string;
package/dist/index.d.ts CHANGED
@@ -2213,13 +2213,10 @@ type LinearAssignedIssuesResult = {
2213
2213
  };
2214
2214
  /** `me`, `unassigned`, `all`, a Linear user id, or a display name. */
2215
2215
  type LinearAssigneeFilter = string;
2216
- /**
2217
- * Linear `IssueFilter` for open issues. Default assignee is `me` so existing
2218
- * Home / Create-from callers stay assigned-to-you unless they ask otherwise.
2219
- */
2220
2216
  declare function buildLinearIssueFilter(input?: {
2221
2217
  assignee?: string | null;
2222
2218
  updatedSince?: string | null;
2219
+ query?: string | null;
2223
2220
  }): Record<string, unknown>;
2224
2221
  /**
2225
2222
  * List or search open Linear issues. `assignee` is `me` (default when there is
@@ -2234,13 +2231,16 @@ declare function listLinearIssuesFiltered(opts?: {
2234
2231
  updatedSince?: string | null;
2235
2232
  }): Promise<LinearAssignedIssuesResult>;
2236
2233
  /**
2237
- * Comments created at/after `since` on issues matching the same assignee filter.
2238
- * One GraphQL query — do not get_issue each ticket to check for new comments.
2234
+ * Comments created at/after `since` on issues matching the same assignee / search
2235
+ * filter. One GraphQL query — do not get_issue each ticket to check for new comments.
2236
+ * Pass `issueIdentifiers` (search hits) to keep only those tickets; an empty
2237
+ * allowlist means no comments.
2239
2238
  */
2240
2239
  declare function listLinearCommentsSince(opts: {
2241
2240
  since: string;
2242
2241
  assignee?: string | null;
2243
2242
  query?: string;
2243
+ issueIdentifiers?: string[];
2244
2244
  limit?: number;
2245
2245
  apiKey?: string | null;
2246
2246
  }): Promise<IssueActivityComment[]>;
@@ -2364,7 +2364,8 @@ declare function resolveGitHubIssueRepo(repoPath?: string | null): Promise<{
2364
2364
  declare function toGitHubIssueInfo(issue: GitHubIssue): IssueInfo;
2365
2365
  /**
2366
2366
  * Repo issue comments created at/after `since`. Pass `issueIdentifiers` (#12)
2367
- * to keep only comments on that inbox page — one REST call, not N issue views.
2367
+ * to keep only comments on that inbox page — paginated REST, not N issue views.
2368
+ * An empty allowlist means no comments (do not fall through to the whole repo).
2368
2369
  */
2369
2370
  declare function listGitHubIssueCommentsSince(opts: {
2370
2371
  since: string;
package/dist/index.js CHANGED
@@ -238,7 +238,7 @@ import {
238
238
  worktreeCleanupSettings,
239
239
  wrapReviewSkillMarkdown,
240
240
  writeWorktreeFile
241
- } from "./chunk-4IQQEJ2C.js";
241
+ } from "./chunk-VTIVVVEC.js";
242
242
  import {
243
243
  LEGACY_PLAN_FILE_REL,
244
244
  PLAN_FILE_NAME,
@@ -1518,12 +1518,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1518
1518
  function assigneeFilterKey(assignee) {
1519
1519
  return (assignee ?? "").trim();
1520
1520
  }
1521
+ function applyLinearIssueQueryFilter(filter, query) {
1522
+ const term = query?.trim();
1523
+ if (!term) return;
1524
+ const or = [
1525
+ { title: { containsIgnoreCase: term } },
1526
+ { description: { containsIgnoreCase: term } }
1527
+ ];
1528
+ const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
1529
+ const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
1530
+ if (Number.isFinite(asNumber) && asNumber > 0) {
1531
+ or.push({ number: { eq: asNumber } });
1532
+ }
1533
+ filter.or = or;
1534
+ }
1521
1535
  function buildLinearIssueFilter(input) {
1522
1536
  const filter = {
1523
1537
  state: { type: { nin: ["completed", "canceled"] } }
1524
1538
  };
1525
1539
  const since = input?.updatedSince?.trim();
1526
1540
  if (since) filter.updatedAt = { gte: since };
1541
+ applyLinearIssueQueryFilter(filter, input?.query);
1527
1542
  const raw = assigneeFilterKey(input?.assignee) || "me";
1528
1543
  const key = raw.toLowerCase();
1529
1544
  if (key === "all" || key === "*") return filter;
@@ -1589,6 +1604,11 @@ async function listLinearIssuesFiltered(opts) {
1589
1604
  async function listLinearCommentsSince(opts) {
1590
1605
  const first = Math.max(1, Math.min(250, opts.limit ?? 200));
1591
1606
  const query = opts.query?.trim() ?? "";
1607
+ const restrictToIssues = opts.issueIdentifiers != null;
1608
+ const allowed = new Set(
1609
+ (opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
1610
+ );
1611
+ if (restrictToIssues && allowed.size === 0) return [];
1592
1612
  const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
1593
1613
  const json = await linearGraphql(
1594
1614
  COMMENTS_SINCE_QUERY,
@@ -1596,7 +1616,7 @@ async function listLinearCommentsSince(opts) {
1596
1616
  first,
1597
1617
  filter: {
1598
1618
  createdAt: { gte: opts.since },
1599
- issue: buildLinearIssueFilter({ assignee })
1619
+ issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
1600
1620
  }
1601
1621
  },
1602
1622
  opts
@@ -1604,6 +1624,7 @@ async function listLinearCommentsSince(opts) {
1604
1624
  const out = [];
1605
1625
  for (const node of json.comments?.nodes ?? []) {
1606
1626
  const identifier = String(node.issue?.identifier ?? "").trim();
1627
+ if (restrictToIssues && !allowed.has(identifier)) continue;
1607
1628
  const body = previewIssueCommentBody(String(node.body ?? ""));
1608
1629
  if (!identifier && !body) continue;
1609
1630
  out.push({
@@ -1833,10 +1854,32 @@ function issueNumberFromApiUrl(url) {
1833
1854
  const number = Number(match[1]);
1834
1855
  return Number.isFinite(number) && number > 0 ? number : null;
1835
1856
  }
1857
+ var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
1858
+ var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
1859
+ function parseGitHubIssueCommentNode(item, allowed) {
1860
+ const rec = item && typeof item === "object" ? item : null;
1861
+ if (!rec) return null;
1862
+ const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
1863
+ const number = issueNumberFromApiUrl(issueUrl);
1864
+ if (number == null) return null;
1865
+ if (allowed && !allowed.has(number)) return null;
1866
+ const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
1867
+ const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
1868
+ const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
1869
+ const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
1870
+ return {
1871
+ identifier: `#${number}`,
1872
+ ...user ? { author: user } : {},
1873
+ ...createdAt ? { createdAt } : {},
1874
+ body,
1875
+ ...url ? { url } : {}
1876
+ };
1877
+ }
1836
1878
  async function listGitHubIssueCommentsSince(opts) {
1837
1879
  const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
1838
1880
  const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
1839
1881
  if (!slug) return [];
1882
+ const restrictToIssues = opts.issueIdentifiers != null;
1840
1883
  const allowed = new Set(
1841
1884
  (opts.issueIdentifiers ?? []).map((id) => {
1842
1885
  try {
@@ -1846,36 +1889,27 @@ async function listGitHubIssueCommentsSince(opts) {
1846
1889
  }
1847
1890
  }).filter((n) => n != null)
1848
1891
  );
1849
- const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&per_page=${limit + 1}`;
1850
- const result = await gh(["api", path2], cwd, { reject: false });
1851
- if (result.exitCode !== 0 || !result.stdout.trim()) return [];
1852
- let parsed;
1853
- try {
1854
- parsed = JSON.parse(result.stdout);
1855
- } catch {
1856
- return [];
1857
- }
1858
- if (!Array.isArray(parsed)) return [];
1892
+ if (restrictToIssues && allowed.size === 0) return [];
1893
+ const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
1859
1894
  const out = [];
1860
- for (const item of parsed) {
1861
- const rec = item && typeof item === "object" ? item : null;
1862
- if (!rec) continue;
1863
- const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
1864
- const number = issueNumberFromApiUrl(issueUrl);
1865
- if (number == null) continue;
1866
- if (allowed.size > 0 && !allowed.has(number)) continue;
1867
- const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
1868
- const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
1869
- const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
1870
- const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
1871
- out.push({
1872
- identifier: `#${number}`,
1873
- ...user ? { author: user } : {},
1874
- ...createdAt ? { createdAt } : {},
1875
- body,
1876
- ...url ? { url } : {}
1877
- });
1878
- if (out.length >= limit) break;
1895
+ for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
1896
+ const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
1897
+ const result = await gh(["api", path2], cwd, { reject: false });
1898
+ if (result.exitCode !== 0 || !result.stdout.trim()) break;
1899
+ let parsed;
1900
+ try {
1901
+ parsed = JSON.parse(result.stdout);
1902
+ } catch {
1903
+ break;
1904
+ }
1905
+ if (!Array.isArray(parsed) || parsed.length === 0) break;
1906
+ for (const item of parsed) {
1907
+ const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
1908
+ if (!comment) continue;
1909
+ out.push(comment);
1910
+ if (out.length >= limit) return out;
1911
+ }
1912
+ if (parsed.length < pageSize) break;
1879
1913
  }
1880
1914
  return out;
1881
1915
  }
@@ -2081,20 +2115,19 @@ async function listIssues(repoPath, opts) {
2081
2115
  const assignee = opts?.assignee?.trim() || void 0;
2082
2116
  const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
2083
2117
  if (source === "linear") {
2084
- const [listed, comments2] = await Promise.all([
2085
- listLinearIssuesFiltered({
2086
- assignee,
2087
- query,
2088
- limit: opts?.limit,
2089
- updatedSince: since
2090
- }),
2091
- since ? listLinearCommentsSince({
2092
- since,
2093
- assignee,
2094
- query,
2095
- limit: opts?.limit
2096
- }) : Promise.resolve(void 0)
2097
- ]);
2118
+ const listed = await listLinearIssuesFiltered({
2119
+ assignee,
2120
+ query,
2121
+ limit: opts?.limit,
2122
+ updatedSince: since
2123
+ });
2124
+ const comments2 = since ? await listLinearCommentsSince({
2125
+ since,
2126
+ assignee,
2127
+ query,
2128
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
2129
+ limit: opts?.limit
2130
+ }) : void 0;
2098
2131
  return {
2099
2132
  source,
2100
2133
  preferredSource,
@@ -3762,20 +3795,19 @@ function registerLinearTools(server) {
3762
3795
  try {
3763
3796
  const page = clampMcpIssueLimit(limit);
3764
3797
  const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
3765
- const [listed, comments] = await Promise.all([
3766
- listLinearIssuesFiltered({
3767
- query,
3768
- assignee,
3769
- limit: page + 1,
3770
- updatedSince: since
3771
- }),
3772
- since ? listLinearCommentsSince({
3773
- since,
3774
- assignee,
3775
- query,
3776
- limit: page + 1
3777
- }) : Promise.resolve(void 0)
3778
- ]);
3798
+ const listed = await listLinearIssuesFiltered({
3799
+ query,
3800
+ assignee,
3801
+ limit: page + 1,
3802
+ updatedSince: since
3803
+ });
3804
+ const comments = since ? await listLinearCommentsSince({
3805
+ since,
3806
+ assignee,
3807
+ query,
3808
+ issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
3809
+ limit: page + 1
3810
+ }) : void 0;
3779
3811
  return mcpJson(
3780
3812
  formatListedIssuesForMcp(
3781
3813
  {
@@ -4880,7 +4912,7 @@ async function startMcpServer() {
4880
4912
  );
4881
4913
  server.tool(
4882
4914
  "wait_for_job",
4883
- "Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
4915
+ "Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
4884
4916
  {
4885
4917
  id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
4886
4918
  timeoutMs: z9.number().optional()
@@ -4894,7 +4926,7 @@ async function startMcpServer() {
4894
4926
  );
4895
4927
  server.tool(
4896
4928
  "stop_job",
4897
- "Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. After stop, present_artifact type=log with status=failed and the last delta, then decide the next step.",
4929
+ "Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
4898
4930
  {
4899
4931
  id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
4900
4932
  reason: z9.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")