@sideboard-ai/core 0.1.189 → 0.1.191

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.cjs CHANGED
@@ -8687,13 +8687,21 @@ function looksLikeHugeToolResultDump(text7) {
8687
8687
  if (t.length < 4e3) return false;
8688
8688
  return looksLikeMinifiedJsDump(t);
8689
8689
  }
8690
+ function looksLikeJobWaitResult(rec) {
8691
+ if (typeof rec.delta === "string") {
8692
+ return typeof rec.status === "string" || typeof rec.id === "string" || typeof rec.stillRunning === "boolean";
8693
+ }
8694
+ if (typeof rec.stopped === "boolean") {
8695
+ return typeof rec.id === "string" || typeof rec.status === "string";
8696
+ }
8697
+ return rec.started === true || rec.reason === "already-running";
8698
+ }
8690
8699
  function clipJobWaitResultJson(content) {
8691
8700
  const trimmed = content.trim();
8692
8701
  if (!trimmed.startsWith("{")) return void 0;
8693
8702
  try {
8694
8703
  const rec = JSON.parse(trimmed);
8695
- const isJob = typeof rec.stillRunning === "boolean" || typeof rec.stopped === "boolean" || typeof rec.delta === "string" && typeof rec.status === "string";
8696
- if (!isJob) return void 0;
8704
+ if (!looksLikeJobWaitResult(rec)) return void 0;
8697
8705
  if (typeof rec.delta === "string" && rec.delta.length > 3500) {
8698
8706
  rec.delta = `\u2026(truncated ${rec.delta.length} chars)
8699
8707
  ${rec.delta.slice(-3500)}`;
@@ -8859,7 +8867,7 @@ function humanizeAgentFailDetail(detail) {
8859
8867
  return /ran out of memory/i.test(raw) ? raw : V8_OOM_SUMMARY;
8860
8868
  }
8861
8869
  if (/homebrew node \+ shared libuv|Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(raw)) {
8862
- return /brew install node@22/i.test(raw) ? raw : `${raw} \u2014 install Node 22 LTS (\`brew install node@22\`) and retry.`;
8870
+ return /brew install node@(?:22|24)/i.test(raw) ? raw : `${raw} \u2014 install Node 24 LTS (\`brew install node@24\`) and retry.`;
8863
8871
  }
8864
8872
  if (/uv_run|spineventloopinternal|uv__io_poll|cursor runner crashed in node/i.test(lower)) {
8865
8873
  return /retry the turn/i.test(raw) ? raw : BUNDLED_NODE_CRASH_SUMMARY;
@@ -8937,7 +8945,7 @@ var init_error_detail = __esm({
8937
8945
  "use strict";
8938
8946
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
8939
8947
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
8940
- HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 22 LTS (`brew install node@22`) and retry.";
8948
+ HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 24 LTS (`brew install node@24`) and retry.";
8941
8949
  BUNDLED_NODE_CRASH_SUMMARY = "Cursor runner crashed in Node. Retry the turn.";
8942
8950
  V8_OOM_SUMMARY = "Agent ran out of memory (JavaScript heap). If it keeps happening, exclude large files from the project folder.";
8943
8951
  MINIFIED_DUMP_SUMMARY = "Cursor local agent crashed during startup (truncated crash dump)";
@@ -10807,7 +10815,7 @@ function parseNodeMajor(version) {
10807
10815
  function scoreNodeForAgentRuntime(candidate) {
10808
10816
  const major = parseNodeMajor(candidate.version);
10809
10817
  if (major == null) return Number.NEGATIVE_INFINITY;
10810
- if (major < 20) return major - 100;
10818
+ if (major < 22) return major - 100;
10811
10819
  const posix = candidate.path.replace(/\\/g, "/");
10812
10820
  let score = 0;
10813
10821
  if (major % 2 === 0) {
@@ -10968,7 +10976,7 @@ var init_node_launch = __esm({
10968
10976
  init_packaged_runtime();
10969
10977
  AGENT_RUNNER_MAX_OLD_SPACE_MB = 8192;
10970
10978
  MAX_OLD_SPACE_FLAG = /(?:^|\s)(--max[-_]old[-_]space[-_]size)(?:[= ](\d+))?(?=\s|$)/;
10971
- PREFERRED_LTS_MAJORS = [24, 22, 20];
10979
+ PREFERRED_LTS_MAJORS = [24, 22];
10972
10980
  }
10973
10981
  });
10974
10982
 
@@ -16880,13 +16888,17 @@ var init_home_board = __esm({
16880
16888
  });
16881
16889
 
16882
16890
  // src/integrations/abletime-mcp.ts
16891
+ function abletimeToolUsesPm(name) {
16892
+ return ABLETIME_PM_TOOL_NAMES.includes(name);
16893
+ }
16883
16894
  function normalizeAbleTimeHost(raw) {
16884
16895
  const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
16885
16896
  const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
16886
16897
  return withScheme.replace(/\/+$/, "");
16887
16898
  }
16888
- function abletimeMcpUrl(host) {
16889
- return `${normalizeAbleTimeHost(host)}${ABLETIME_MCP_PATH}`;
16899
+ function abletimeMcpUrl(host, opts) {
16900
+ const path2 = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
16901
+ return `${normalizeAbleTimeHost(host)}${path2}`;
16890
16902
  }
16891
16903
  function rewriteAbleTimeError(message) {
16892
16904
  if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
@@ -16964,7 +16976,8 @@ async function abletimeMcpRequest(method, params, opts) {
16964
16976
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
16965
16977
  }
16966
16978
  const url = abletimeMcpUrl(
16967
- opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
16979
+ opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
16980
+ { pm: opts?.pm }
16968
16981
  );
16969
16982
  const res = await httpFetch(url, {
16970
16983
  method: "POST",
@@ -17028,7 +17041,8 @@ async function callAbleTimeTool(name, args = {}, opts) {
17028
17041
  if (!opts?.token && !isAbleTimeConnected()) {
17029
17042
  throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
17030
17043
  }
17031
- const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, opts).then((result) => {
17044
+ const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
17045
+ const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
17032
17046
  return unwrapToolResult(result);
17033
17047
  });
17034
17048
  try {
@@ -17036,13 +17050,13 @@ async function callAbleTimeTool(name, args = {}, opts) {
17036
17050
  } catch (err) {
17037
17051
  const message = err instanceof Error ? err.message : String(err);
17038
17052
  if (/initialize|session|not initialized|-32000/i.test(message)) {
17039
- await initializeIfNeeded(opts);
17053
+ await initializeIfNeeded(requestOpts);
17040
17054
  return await call();
17041
17055
  }
17042
17056
  throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
17043
17057
  }
17044
17058
  }
17045
- var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, MCP_PROTOCOL_VERSION;
17059
+ var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
17046
17060
  var init_abletime_mcp = __esm({
17047
17061
  "src/integrations/abletime-mcp.ts"() {
17048
17062
  "use strict";
@@ -17050,13 +17064,27 @@ var init_abletime_mcp = __esm({
17050
17064
  init_app_settings();
17051
17065
  DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
17052
17066
  ABLETIME_MCP_PATH = "/api/public/v2/mcp";
17067
+ ABLETIME_MCP_PM_PATH = "/api/public/v2/mcp/pm";
17053
17068
  MCP_PROTOCOL_VERSION = "2025-03-26";
17069
+ ABLETIME_PM_TOOL_NAMES = [
17070
+ "set_task_assignee",
17071
+ "set_task_priority",
17072
+ "set_task_stage",
17073
+ "set_task_dependency",
17074
+ "set_task_blocked",
17075
+ "set_task_locked",
17076
+ "set_task_schedule",
17077
+ "clear_task_schedule",
17078
+ "archive_task",
17079
+ "delete_task"
17080
+ ];
17054
17081
  }
17055
17082
  });
17056
17083
 
17057
17084
  // src/integrations/abletime.ts
17058
17085
  var abletime_exports = {};
17059
17086
  __export(abletime_exports, {
17087
+ ableTimeDependencyTarget: () => ableTimeDependencyTarget,
17060
17088
  commentAbleTimeTask: () => commentAbleTimeTask,
17061
17089
  createAbleTimeTask: () => createAbleTimeTask,
17062
17090
  ensureAbleTimeTask: () => ensureAbleTimeTask,
@@ -17313,39 +17341,122 @@ async function commentAbleTimeTask(input, opts) {
17313
17341
  body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
17314
17342
  };
17315
17343
  }
17344
+ function isAbleTimeNoneToken(value) {
17345
+ if (value == null) return true;
17346
+ const s = value.trim();
17347
+ return !s || /^(none|clear|unschedule|null)$/i.test(s);
17348
+ }
17349
+ function taskIdArgs(id) {
17350
+ return { id, task_id: id, task: id };
17351
+ }
17352
+ function ableTimeDependencyTarget(thisId, type, relatedId) {
17353
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
17354
+ if (s === "none" || s === "clear" || s === "null") {
17355
+ return { taskId: thisId, dependsOn: null };
17356
+ }
17357
+ if (s === "blockedby" || s === "blocked" || s === "dependson" || s === "waiton") {
17358
+ return { taskId: thisId, dependsOn: relatedId };
17359
+ }
17360
+ if (s === "blocks" || s === "blocking") {
17361
+ return { taskId: relatedId, dependsOn: thisId };
17362
+ }
17363
+ throw new Error(
17364
+ `AbleTime relation type must be blocks or blockedBy (got ${type}). AbleTime models one wait-on dependency.`
17365
+ );
17366
+ }
17367
+ async function setAbleTimeTaskDependency(taskId, dependsOn, opts) {
17368
+ const args = {
17369
+ ...taskIdArgs(taskId)
17370
+ };
17371
+ if (dependsOn) {
17372
+ args.depends_on = dependsOn;
17373
+ args.depends_on_id = dependsOn;
17374
+ args.dependency = dependsOn;
17375
+ args.dependency_id = dependsOn;
17376
+ args.blocked_by = dependsOn;
17377
+ args.wait_on = dependsOn;
17378
+ args.related_task_id = dependsOn;
17379
+ } else {
17380
+ args.depends_on = null;
17381
+ args.dependency = null;
17382
+ args.blocked_by = null;
17383
+ args.clear = true;
17384
+ }
17385
+ await callAbleTimeTool("set_task_dependency", args, opts);
17386
+ }
17316
17387
  async function updateAbleTimeTask(input, opts) {
17317
17388
  const id = input.id.trim();
17318
17389
  if (!id) throw new Error("AbleTime task id is required");
17319
17390
  const title = input.title?.trim();
17320
17391
  const description = input.description;
17321
17392
  const state = input.state?.trim();
17322
- if (!title && description === void 0 && !state) {
17323
- throw new Error("abletime_update_task needs at least one of title, description, state");
17393
+ const addRelations = input.relations?.length ? input.relations : void 0;
17394
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
17395
+ const updateArgs = { ...taskIdArgs(id) };
17396
+ if (title) updateArgs.title = title;
17397
+ if (description !== void 0) updateArgs.description = description;
17398
+ if (input.labels !== void 0) {
17399
+ updateArgs.tags = input.labels;
17400
+ updateArgs.labels = input.labels;
17401
+ updateArgs.tag_names = input.labels;
17402
+ }
17403
+ if (input.project !== void 0) {
17404
+ const projectName = (input.project ?? "").trim();
17405
+ if (isAbleTimeNoneToken(projectName)) {
17406
+ throw new Error("AbleTime tasks stay in a project \u2014 pass a project name or id");
17407
+ }
17408
+ const project = await resolveAbleTimeProject(projectName, opts);
17409
+ updateArgs.project = project.id;
17410
+ updateArgs.project_id = project.id;
17411
+ }
17412
+ if (input.parent !== void 0) {
17413
+ if (isAbleTimeNoneToken(input.parent)) {
17414
+ updateArgs.parent = null;
17415
+ updateArgs.parent_id = null;
17416
+ updateArgs.related_task_id = null;
17417
+ } else {
17418
+ const parent = (input.parent ?? "").trim();
17419
+ updateArgs.parent = parent;
17420
+ updateArgs.parent_id = parent;
17421
+ updateArgs.related_task_id = parent;
17422
+ }
17423
+ }
17424
+ const hasUpdateFields = Boolean(title) || description !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0;
17425
+ if (!hasUpdateFields && !state && !addRelations && !removeRelations) {
17426
+ throw new Error(
17427
+ "abletime_update_task needs at least one of title, description, state, labels, project, parent, relations, removeRelations"
17428
+ );
17324
17429
  }
17325
17430
  if (state) {
17326
17431
  await callAbleTimeTool(
17327
17432
  "set_task_state",
17328
- { id, task_id: id, task: id, state },
17433
+ { ...taskIdArgs(id), state },
17329
17434
  opts
17330
17435
  ).catch(async () => {
17331
17436
  await callAbleTimeTool(
17332
17437
  "update_task",
17333
- { id, task_id: id, title, description, state },
17438
+ { ...updateArgs, state },
17334
17439
  opts
17335
17440
  );
17336
17441
  });
17337
17442
  }
17338
- if (title || description !== void 0) {
17339
- await callAbleTimeTool(
17340
- "update_task",
17341
- {
17342
- id,
17343
- task_id: id,
17344
- ...title ? { title } : {},
17345
- ...description !== void 0 ? { description } : {}
17346
- },
17347
- opts
17348
- );
17443
+ if (hasUpdateFields) {
17444
+ await callAbleTimeTool("update_task", updateArgs, opts);
17445
+ }
17446
+ for (const rel of removeRelations ?? []) {
17447
+ const related = rel.issue.trim() || id;
17448
+ const target = ableTimeDependencyTarget(id, rel.type, related);
17449
+ await setAbleTimeTaskDependency(target.taskId, null, opts);
17450
+ }
17451
+ for (const rel of addRelations ?? []) {
17452
+ const related = rel.issue.trim();
17453
+ if (!related) throw new Error("AbleTime relations.issue is required (id or CRM-232)");
17454
+ const target = ableTimeDependencyTarget(id, rel.type, related);
17455
+ if (!target.dependsOn) {
17456
+ await setAbleTimeTaskDependency(target.taskId, null, opts);
17457
+ continue;
17458
+ }
17459
+ await setAbleTimeTaskDependency(target.taskId, target.dependsOn, opts);
17349
17460
  }
17350
17461
  return getAbleTimeTask(id, opts);
17351
17462
  }
@@ -20023,17 +20134,17 @@ function formatIssueToolsDirective(opts) {
20023
20134
  ];
20024
20135
  if (opts.linear) {
20025
20136
  lines.push(
20026
- "- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state, cycle), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
20137
+ "- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state, cycle, labels, project, parent, relations), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
20027
20138
  );
20028
20139
  }
20029
20140
  if (github) {
20030
20141
  lines.push(
20031
- "- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
20142
+ "- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
20032
20143
  );
20033
20144
  }
20034
20145
  if (opts.abletime) {
20035
20146
  lines.push(
20036
- "- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
20147
+ "- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
20037
20148
  );
20038
20149
  }
20039
20150
  lines.push(
@@ -23123,6 +23234,8 @@ var init_schedule_runner = __esm({
23123
23234
  var index_exports = {};
23124
23235
  __export(index_exports, {
23125
23236
  ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
23237
+ ABLETIME_MCP_PM_PATH: () => ABLETIME_MCP_PM_PATH,
23238
+ ABLETIME_PM_TOOL_NAMES: () => ABLETIME_PM_TOOL_NAMES,
23126
23239
  AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
23127
23240
  AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
23128
23241
  ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
@@ -23212,8 +23325,10 @@ __export(index_exports, {
23212
23325
  WORKTREE_GITHUB_MCP_TOOLS: () => WORKTREE_GITHUB_MCP_TOOLS,
23213
23326
  WORKTREE_LINEAR_MCP_TOOLS: () => WORKTREE_LINEAR_MCP_TOOLS,
23214
23327
  WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
23328
+ ableTimeDependencyTarget: () => ableTimeDependencyTarget,
23215
23329
  abletimeMcpRequest: () => abletimeMcpRequest,
23216
23330
  abletimeMcpUrl: () => abletimeMcpUrl,
23331
+ abletimeToolUsesPm: () => abletimeToolUsesPm,
23217
23332
  ackSlackInboundSeen: () => ackSlackInboundSeen,
23218
23333
  addBoardPin: () => addBoardPin,
23219
23334
  addPrStackLayer: () => addPrStackLayer,
@@ -23486,6 +23601,7 @@ __export(index_exports, {
23486
23601
  git: () => git,
23487
23602
  githubAgentGitEnv: () => githubAgentGitEnv,
23488
23603
  githubHttpsInsteadOfEntries: () => githubHttpsInsteadOfEntries,
23604
+ githubRelationEditFlag: () => githubRelationEditFlag,
23489
23605
  globalAgentCwd: () => globalAgentCwd,
23490
23606
  groupHomeBoardWorktrees: () => groupHomeBoardWorktrees,
23491
23607
  handleSlackInbound: () => handleSlackInbound,
@@ -23535,6 +23651,7 @@ __export(index_exports, {
23535
23651
  isInternalAgentStatusText: () => isInternalAgentStatusText,
23536
23652
  isIssueSourceConnected: () => isIssueSourceConnected,
23537
23653
  isLinearConnected: () => isLinearConnected,
23654
+ isLinearNoneToken: () => isLinearNoneToken,
23538
23655
  isLinearOAuthCancelled: () => isLinearOAuthCancelled,
23539
23656
  isOptionalServiceId: () => isOptionalServiceId,
23540
23657
  isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
@@ -23572,6 +23689,7 @@ __export(index_exports, {
23572
23689
  linearGraphql: () => linearGraphql,
23573
23690
  linearOAuthAuthorizeUrl: () => linearOAuthAuthorizeUrl,
23574
23691
  linearOAuthCredentials: () => linearOAuthCredentials,
23692
+ linearRelationCreateInput: () => linearRelationCreateInput,
23575
23693
  linearTicketIdFromThread: () => linearTicketIdFromThread,
23576
23694
  listAbleTimeAssignedIssues: () => listAbleTimeAssignedIssues,
23577
23695
  listAbleTimeProjects: () => listAbleTimeProjects,
@@ -23640,6 +23758,7 @@ __export(index_exports, {
23640
23758
  nonInteractiveGitProcessEnv: () => nonInteractiveGitProcessEnv,
23641
23759
  normalizeAbleTimeHost: () => normalizeAbleTimeHost,
23642
23760
  normalizeCodeSelection: () => normalizeCodeSelection,
23761
+ normalizeLinearRelationType: () => normalizeLinearRelationType,
23643
23762
  normalizeParseResult: () => normalizeParseResult,
23644
23763
  normalizeServiceOrigin: () => normalizeServiceOrigin,
23645
23764
  normalizeThinkingEffort: () => normalizeThinkingEffort,
@@ -23733,6 +23852,8 @@ __export(index_exports, {
23733
23852
  resolveGithubAgentToken: () => resolveGithubAgentToken,
23734
23853
  resolveGithubRepoSlug: () => resolveGithubRepoSlug,
23735
23854
  resolveLinearCycle: () => resolveLinearCycle,
23855
+ resolveLinearLabel: () => resolveLinearLabel,
23856
+ resolveLinearProject: () => resolveLinearProject,
23736
23857
  resolveLinearState: () => resolveLinearState,
23737
23858
  resolveLinearTeam: () => resolveLinearTeam,
23738
23859
  resolveLoginCommand: () => resolveLoginCommand,
@@ -24350,12 +24471,14 @@ var ISSUE_FIELDS = `
24350
24471
  children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
24351
24472
  relations(first: 25) {
24352
24473
  nodes {
24474
+ id
24353
24475
  type
24354
24476
  relatedIssue { ${ISSUE_REF_FIELDS} }
24355
24477
  }
24356
24478
  }
24357
24479
  inverseRelations(first: 25) {
24358
24480
  nodes {
24481
+ id
24359
24482
  type
24360
24483
  issue { ${ISSUE_REF_FIELDS} }
24361
24484
  }
@@ -24475,6 +24598,35 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
24475
24598
  }
24476
24599
  }
24477
24600
  `;
24601
+ var LABELS_QUERY = `
24602
+ query SideboardLabels($first: Int!) {
24603
+ issueLabels(first: $first) {
24604
+ nodes { id name }
24605
+ }
24606
+ }
24607
+ `;
24608
+ var PROJECTS_QUERY = `
24609
+ query SideboardProjects($first: Int!, $filter: ProjectFilter) {
24610
+ projects(first: $first, filter: $filter) {
24611
+ nodes { id name slugId }
24612
+ }
24613
+ }
24614
+ `;
24615
+ var ISSUE_RELATION_CREATE = `
24616
+ mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
24617
+ issueRelationCreate(input: $input) {
24618
+ success
24619
+ issueRelation { id type }
24620
+ }
24621
+ }
24622
+ `;
24623
+ var ISSUE_RELATION_DELETE = `
24624
+ mutation SideboardIssueRelationDelete($id: String!) {
24625
+ issueRelationDelete(id: $id) {
24626
+ success
24627
+ }
24628
+ }
24629
+ `;
24478
24630
  function rewriteLinearError(message) {
24479
24631
  if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
24480
24632
  return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
@@ -24594,12 +24746,18 @@ function mapRelations(node) {
24594
24746
  for (const rel of node.relations?.nodes ?? []) {
24595
24747
  const issue = mapIssueRef(rel.relatedIssue);
24596
24748
  if (!issue) continue;
24597
- out.push({ type: String(rel.type ?? "related"), issue });
24749
+ const id = rel.id?.trim();
24750
+ out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
24598
24751
  }
24599
24752
  for (const rel of node.inverseRelations?.nodes ?? []) {
24600
24753
  const issue = mapIssueRef(rel.issue);
24601
24754
  if (!issue) continue;
24602
- out.push({ type: flipLinearRelationType(String(rel.type ?? "related")), issue });
24755
+ const id = rel.id?.trim();
24756
+ out.push({
24757
+ type: flipLinearRelationType(String(rel.type ?? "related")),
24758
+ issue,
24759
+ ...id ? { id } : {}
24760
+ });
24603
24761
  }
24604
24762
  return out;
24605
24763
  }
@@ -24745,7 +24903,14 @@ function resolveLinearCycle(team, cycle) {
24745
24903
  }
24746
24904
  const lower = s.toLowerCase();
24747
24905
  const asNumber = Number.parseInt(s, 10);
24748
- const cycles = team.cycles ?? [];
24906
+ const seen = /* @__PURE__ */ new Set();
24907
+ const cycles = [team.activeCycle, ...team.cycles ?? []].filter(
24908
+ (c) => {
24909
+ if (!c?.id || seen.has(c.id)) return false;
24910
+ seen.add(c.id);
24911
+ return true;
24912
+ }
24913
+ );
24749
24914
  const found = cycles.find((c) => c.id === s) || cycles.find((c) => c.name.toLowerCase() === lower) || (Number.isInteger(asNumber) ? cycles.find((c) => c.number === asNumber) : void 0);
24750
24915
  if (!found) {
24751
24916
  const available = cycles.map((c) => c.number != null ? `${c.name} (${c.number})` : c.name).join(", ") || "(none)";
@@ -24753,6 +24918,148 @@ function resolveLinearCycle(team, cycle) {
24753
24918
  }
24754
24919
  return found.id;
24755
24920
  }
24921
+ var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
24922
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24923
+ function isLinearNoneToken(value) {
24924
+ if (value == null) return true;
24925
+ const s = value.trim();
24926
+ return !s || LINEAR_NONE_RE.test(s);
24927
+ }
24928
+ function resolveLinearLabel(labels, name) {
24929
+ const s = name.trim();
24930
+ if (!s) throw new Error("Linear label is empty");
24931
+ const lower = s.toLowerCase();
24932
+ const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
24933
+ if (!found) {
24934
+ const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
24935
+ throw new Error(`Linear label not found: ${name}. Available: ${available}`);
24936
+ }
24937
+ return found;
24938
+ }
24939
+ function resolveLinearProject(projects, project) {
24940
+ if (isLinearNoneToken(project)) return null;
24941
+ const s = (project ?? "").trim();
24942
+ const lower = s.toLowerCase();
24943
+ const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
24944
+ if (!found) {
24945
+ const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
24946
+ throw new Error(`Linear project not found: ${project}. Available: ${available}`);
24947
+ }
24948
+ return found.id;
24949
+ }
24950
+ function normalizeLinearRelationType(type) {
24951
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
24952
+ if (s === "blocks" || s === "block") return "blocks";
24953
+ if (s === "blockedby" || s === "blocked") return "blockedBy";
24954
+ if (s === "related" || s === "relates" || s === "relatesto") return "related";
24955
+ if (s === "duplicate" || s === "dup") return "duplicate";
24956
+ if (s === "duplicateof" || s === "original") return "duplicateOf";
24957
+ if (s === "similar") return "similar";
24958
+ throw new Error(
24959
+ `Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
24960
+ );
24961
+ }
24962
+ function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
24963
+ const view = normalizeLinearRelationType(type);
24964
+ if (view === "blockedBy") {
24965
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
24966
+ }
24967
+ if (view === "duplicateOf") {
24968
+ return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
24969
+ }
24970
+ return { issueId: thisIssueId, relatedIssueId, type: view };
24971
+ }
24972
+ function relationMatches(rel, type, issueRef) {
24973
+ if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
24974
+ const ref = issueRef.trim().toLowerCase();
24975
+ return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
24976
+ }
24977
+ function formatRelationList(relations) {
24978
+ if (!relations.length) return "(none)";
24979
+ return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
24980
+ }
24981
+ async function listLinearLabels(opts) {
24982
+ const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
24983
+ return (json.issueLabels?.nodes ?? []).flatMap((node) => {
24984
+ const id = String(node.id ?? "").trim();
24985
+ const name = String(node.name ?? "").trim();
24986
+ if (!id) return [];
24987
+ return [{ id, name }];
24988
+ });
24989
+ }
24990
+ async function listLinearProjects(filter, opts) {
24991
+ const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
24992
+ return (json.projects?.nodes ?? []).flatMap((node) => {
24993
+ const id = String(node.id ?? "").trim();
24994
+ if (!id) return [];
24995
+ return [
24996
+ {
24997
+ id,
24998
+ name: String(node.name ?? ""),
24999
+ ...node.slugId ? { slugId: String(node.slugId) } : {}
25000
+ }
25001
+ ];
25002
+ });
25003
+ }
25004
+ async function resolveLinearProjectId(project, opts) {
25005
+ if (isLinearNoneToken(project)) return null;
25006
+ const s = (project ?? "").trim();
25007
+ const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
25008
+ const matched = await listLinearProjects(filter, opts);
25009
+ try {
25010
+ return resolveLinearProject(matched, s);
25011
+ } catch {
25012
+ if (UUID_RE.test(s)) return s;
25013
+ const listed = await listLinearProjects(void 0, opts);
25014
+ return resolveLinearProject(listed, s);
25015
+ }
25016
+ }
25017
+ async function resolveLinearLabelIds(names, opts) {
25018
+ if (names.length === 0) return [];
25019
+ const labels = await listLinearLabels(opts);
25020
+ return names.map((name) => {
25021
+ const s = name.trim();
25022
+ try {
25023
+ return resolveLinearLabel(labels, s).id;
25024
+ } catch (error) {
25025
+ if (UUID_RE.test(s)) return s;
25026
+ throw error;
25027
+ }
25028
+ });
25029
+ }
25030
+ async function applyLinearIssueRelations(issue, add, remove, opts) {
25031
+ const current = [...issue.relations];
25032
+ for (const rel of remove ?? []) {
25033
+ const type = normalizeLinearRelationType(rel.type);
25034
+ const issueRef = rel.issue.trim();
25035
+ if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
25036
+ const match = current.find((r) => relationMatches(r, type, issueRef));
25037
+ if (!match?.id) {
25038
+ throw new Error(
25039
+ `Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
25040
+ );
25041
+ }
25042
+ const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
25043
+ if (!json.issueRelationDelete?.success) {
25044
+ throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
25045
+ }
25046
+ const idx = current.indexOf(match);
25047
+ if (idx >= 0) current.splice(idx, 1);
25048
+ }
25049
+ for (const rel of add ?? []) {
25050
+ const type = normalizeLinearRelationType(rel.type);
25051
+ const issueRef = rel.issue.trim();
25052
+ if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
25053
+ if (current.some((r) => relationMatches(r, type, issueRef))) continue;
25054
+ const related = await getLinearIssue(issueRef, opts);
25055
+ const input = linearRelationCreateInput(issue.id, type, related.id);
25056
+ const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
25057
+ if (!json.issueRelationCreate?.success) {
25058
+ throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
25059
+ }
25060
+ current.push({ type, issue: related, id: void 0 });
25061
+ }
25062
+ }
24756
25063
  function normalizePriority(priority) {
24757
25064
  if (priority == null) return void 0;
24758
25065
  if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
@@ -24760,7 +25067,6 @@ function normalizePriority(priority) {
24760
25067
  }
24761
25068
  return priority;
24762
25069
  }
24763
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24764
25070
  function assigneeFilterKey(assignee) {
24765
25071
  return (assignee ?? "").trim();
24766
25072
  }
@@ -24862,7 +25168,12 @@ async function listLinearCommentsSince(opts) {
24862
25168
  first,
24863
25169
  filter: {
24864
25170
  createdAt: { gte: opts.since },
24865
- issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
25171
+ issue: buildLinearIssueFilter({
25172
+ assignee,
25173
+ // Search hits may match identifier/comments — do not also require
25174
+ // title/description contains when an allowlist is already applied.
25175
+ query: restrictToIssues ? void 0 : query || void 0
25176
+ })
24866
25177
  }
24867
25178
  },
24868
25179
  opts
@@ -24968,8 +25279,10 @@ async function updateLinearIssue(input, opts) {
24968
25279
  if (input.description !== void 0) {
24969
25280
  mutationInput.description = input.description;
24970
25281
  }
24971
- const needsTeam = Boolean(input.state?.trim()) || input.cycle !== void 0;
24972
- const existing = needsTeam ? await getLinearIssue(issueId, opts) : void 0;
25282
+ const addRelations = input.relations?.length ? input.relations : void 0;
25283
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
25284
+ const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
25285
+ const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
24973
25286
  if (input.state?.trim()) {
24974
25287
  const { teams } = await listLinearTeams(opts);
24975
25288
  const teamRef = existing?.team?.key || existing?.team?.id;
@@ -25002,14 +25315,44 @@ async function updateLinearIssue(input, opts) {
25002
25315
  if (input.priority !== void 0) {
25003
25316
  mutationInput.priority = normalizePriority(input.priority);
25004
25317
  }
25005
- if (Object.keys(mutationInput).length === 0) {
25006
- throw new Error("linear_update_issue needs at least one of title, description, state, assignee, priority, cycle");
25318
+ if (input.labels !== void 0) {
25319
+ mutationInput.labelIds = await resolveLinearLabelIds(input.labels, opts);
25320
+ }
25321
+ if (input.project !== void 0) {
25322
+ mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
25323
+ }
25324
+ if (input.parent !== void 0) {
25325
+ if (isLinearNoneToken(input.parent)) {
25326
+ mutationInput.parentId = null;
25327
+ } else {
25328
+ const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
25329
+ mutationInput.parentId = parent.id;
25330
+ }
25007
25331
  }
25008
- const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
25009
- if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
25010
- throw new Error("Linear issueUpdate failed");
25332
+ const hasIssueFields = Object.keys(mutationInput).length > 0;
25333
+ const hasRelationWrites = Boolean(addRelations || removeRelations);
25334
+ if (!hasIssueFields && !hasRelationWrites) {
25335
+ throw new Error(
25336
+ "linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
25337
+ );
25011
25338
  }
25012
- return mapIssue(json.issueUpdate.issue);
25339
+ let updated;
25340
+ if (hasIssueFields) {
25341
+ const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
25342
+ if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
25343
+ throw new Error("Linear issueUpdate failed");
25344
+ }
25345
+ updated = mapIssue(json.issueUpdate.issue);
25346
+ }
25347
+ if (hasRelationWrites) {
25348
+ const base = updated ?? existing;
25349
+ if (!base) {
25350
+ throw new Error(`Linear issue not found: ${issueId}`);
25351
+ }
25352
+ await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
25353
+ return getLinearIssue(base.id || issueId, opts);
25354
+ }
25355
+ return updated;
25013
25356
  }
25014
25357
  async function commentLinearIssue(input, opts) {
25015
25358
  const issueId = input.id.trim();
@@ -25163,7 +25506,7 @@ async function listGitHubIssueCommentsSince(opts) {
25163
25506
  }).filter((n) => n != null)
25164
25507
  );
25165
25508
  if (restrictToIssues && allowed.size === 0) return [];
25166
- const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
25509
+ const pageSize = GITHUB_ISSUE_COMMENTS_PAGE_SIZE;
25167
25510
  const out = [];
25168
25511
  for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
25169
25512
  const path2 = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
@@ -25231,13 +25574,78 @@ async function commentGitHubIssue(input, opts) {
25231
25574
  const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
25232
25575
  return { body, url };
25233
25576
  }
25577
+ function isGitHubNoneToken(value) {
25578
+ if (value == null) return true;
25579
+ const s = value.trim();
25580
+ return !s || /^(none|clear|unschedule|null)$/i.test(s);
25581
+ }
25582
+ function githubRelationEditFlag(type, remove = false) {
25583
+ const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
25584
+ if (s === "blockedby" || s === "blocked" || s === "dependson") {
25585
+ return remove ? "--remove-blocked-by" : "--add-blocked-by";
25586
+ }
25587
+ if (s === "blocks" || s === "blocking") {
25588
+ return remove ? "--remove-blocking" : "--add-blocking";
25589
+ }
25590
+ throw new Error(
25591
+ `GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
25592
+ );
25593
+ }
25594
+ function labelsEqual(a, b) {
25595
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
25596
+ }
25597
+ function parseProjectTitles(raw) {
25598
+ const out = [];
25599
+ const seen = /* @__PURE__ */ new Set();
25600
+ const push = (name) => {
25601
+ const title = name.trim();
25602
+ const key = title.toLowerCase();
25603
+ if (!title || seen.has(key)) return;
25604
+ seen.add(key);
25605
+ out.push(title);
25606
+ };
25607
+ const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
25608
+ if (Array.isArray(fromItems)) {
25609
+ for (const item of fromItems) {
25610
+ if (typeof item === "string") {
25611
+ push(item);
25612
+ continue;
25613
+ }
25614
+ if (!item || typeof item !== "object") continue;
25615
+ const rec = item;
25616
+ const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
25617
+ const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
25618
+ if (title) push(title);
25619
+ }
25620
+ }
25621
+ return out;
25622
+ }
25623
+ async function listGitHubIssueProjects(number, cwd, repoArgs) {
25624
+ const result = await gh(
25625
+ ["issue", "view", String(number), ...repoArgs, "--json", "projectItems,projects"],
25626
+ cwd,
25627
+ { reject: false }
25628
+ );
25629
+ if (result.exitCode !== 0 || !result.stdout.trim()) return [];
25630
+ try {
25631
+ const parsed = JSON.parse(result.stdout);
25632
+ return parseProjectTitles(parsed);
25633
+ } catch {
25634
+ return [];
25635
+ }
25636
+ }
25234
25637
  async function updateGitHubIssue(input, opts) {
25235
25638
  const number = parseGitHubIssueNumber(input.id);
25236
25639
  const title = input.title?.trim();
25237
25640
  const body = input.body;
25238
25641
  const state = input.state?.trim().toLowerCase();
25239
- if (!title && body === void 0 && !state) {
25240
- throw new Error("github_update_issue needs at least one of title, body, state");
25642
+ const addRelations = input.relations?.length ? input.relations : void 0;
25643
+ const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
25644
+ const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
25645
+ if (!hasEditFields && !state) {
25646
+ throw new Error(
25647
+ "github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
25648
+ );
25241
25649
  }
25242
25650
  const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
25243
25651
  if (state === "closed" || state === "close") {
@@ -25253,10 +25661,46 @@ async function updateGitHubIssue(input, opts) {
25253
25661
  } else if (state) {
25254
25662
  throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
25255
25663
  }
25256
- if (title || body !== void 0) {
25257
- const args = ["issue", "edit", String(number), ...repoArgs];
25258
- if (title) args.push("--title", title);
25259
- if (body !== void 0) args.push("--body", body);
25664
+ const args = ["issue", "edit", String(number), ...repoArgs];
25665
+ if (title) args.push("--title", title);
25666
+ if (body !== void 0) args.push("--body", body);
25667
+ if (input.labels !== void 0) {
25668
+ const existing = await getGitHubIssue(String(number), opts);
25669
+ const desired = input.labels.map((name) => name.trim()).filter(Boolean);
25670
+ const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
25671
+ const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
25672
+ if (add.length) args.push("--add-label", add.join(","));
25673
+ if (remove.length) args.push("--remove-label", remove.join(","));
25674
+ }
25675
+ if (input.project !== void 0) {
25676
+ const current = await listGitHubIssueProjects(number, cwd, repoArgs);
25677
+ if (isGitHubNoneToken(input.project)) {
25678
+ for (const name of current) args.push("--remove-project", name);
25679
+ } else {
25680
+ const wanted = (input.project ?? "").trim();
25681
+ if (!current.some((name) => labelsEqual(name, wanted))) {
25682
+ args.push("--add-project", wanted);
25683
+ }
25684
+ for (const name of current) {
25685
+ if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
25686
+ }
25687
+ }
25688
+ }
25689
+ if (input.parent !== void 0) {
25690
+ if (isGitHubNoneToken(input.parent)) {
25691
+ args.push("--remove-parent");
25692
+ } else {
25693
+ args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
25694
+ }
25695
+ }
25696
+ for (const rel of removeRelations ?? []) {
25697
+ args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
25698
+ }
25699
+ for (const rel of addRelations ?? []) {
25700
+ args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
25701
+ }
25702
+ const editChanged = args.length > 3 + repoArgs.length;
25703
+ if (editChanged) {
25260
25704
  requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
25261
25705
  }
25262
25706
  return getGitHubIssue(String(number), opts);
@@ -26938,14 +27382,23 @@ function registerAbleTimeTools(server) {
26938
27382
  }
26939
27383
  }
26940
27384
  );
27385
+ const relationInputSchema = import_zod4.z.object({
27386
+ type: import_zod4.z.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
27387
+ issue: import_zod4.z.string().describe("Related task id or reference (CRM-232).")
27388
+ });
26941
27389
  server.tool(
26942
27390
  "abletime_update_task",
26943
- "Update an AbleTime task (id or CRM-232). Pass title, description, and/or state. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.",
27391
+ 'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.',
26944
27392
  {
26945
27393
  id: import_zod4.z.string(),
26946
27394
  title: import_zod4.z.string().optional(),
26947
27395
  description: import_zod4.z.string().optional(),
26948
- state: import_zod4.z.string().optional()
27396
+ state: import_zod4.z.string().optional(),
27397
+ labels: import_zod4.z.array(import_zod4.z.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
27398
+ project: import_zod4.z.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
27399
+ parent: import_zod4.z.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
27400
+ relations: import_zod4.z.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
27401
+ removeRelations: import_zod4.z.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
26949
27402
  },
26950
27403
  async (args) => {
26951
27404
  try {
@@ -27080,14 +27533,23 @@ function registerGithubIssueTools(server) {
27080
27533
  }
27081
27534
  }
27082
27535
  );
27536
+ const relationInputSchema = import_zod5.z.object({
27537
+ type: import_zod5.z.string().describe("blocks or blockedBy (this issue's view)."),
27538
+ issue: import_zod5.z.string().describe("Related issue #123 or URL.")
27539
+ });
27083
27540
  server.tool(
27084
27541
  "github_update_issue",
27085
- "Update a GitHub issue (#123). Pass title, body, and/or state (open|closed). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.",
27542
+ 'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.',
27086
27543
  {
27087
27544
  id: import_zod5.z.string(),
27088
27545
  title: import_zod5.z.string().optional(),
27089
27546
  body: import_zod5.z.string().optional(),
27090
27547
  state: import_zod5.z.string().optional().describe("open or closed"),
27548
+ labels: import_zod5.z.array(import_zod5.z.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
27549
+ project: import_zod5.z.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
27550
+ parent: import_zod5.z.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
27551
+ relations: import_zod5.z.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
27552
+ removeRelations: import_zod5.z.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
27091
27553
  repoPath: repoPathSchema
27092
27554
  },
27093
27555
  async (args) => {
@@ -27217,9 +27679,13 @@ function registerLinearTools(server) {
27217
27679
  }
27218
27680
  }
27219
27681
  );
27682
+ const relationInputSchema = import_zod6.z.object({
27683
+ type: import_zod6.z.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
27684
+ issue: import_zod6.z.string().describe("Related issue uuid or identifier (ENG-123).")
27685
+ });
27220
27686
  server.tool(
27221
27687
  "linear_update_issue",
27222
- 'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or cycle. Cycle is name, number, "current"/"active", or "none" to unschedule. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
27688
+ 'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
27223
27689
  {
27224
27690
  id: import_zod6.z.string(),
27225
27691
  title: import_zod6.z.string().optional(),
@@ -27227,7 +27693,12 @@ function registerLinearTools(server) {
27227
27693
  state: import_zod6.z.string().optional(),
27228
27694
  assignee: import_zod6.z.string().nullable().optional(),
27229
27695
  priority: prioritySchema,
27230
- cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
27696
+ cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
27697
+ labels: import_zod6.z.array(import_zod6.z.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
27698
+ project: import_zod6.z.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
27699
+ parent: import_zod6.z.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
27700
+ relations: import_zod6.z.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
27701
+ removeRelations: import_zod6.z.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
27231
27702
  },
27232
27703
  async (args) => {
27233
27704
  try {
@@ -31334,6 +31805,8 @@ init_outbound_watch();
31334
31805
  // Annotate the CommonJS export names for ESM import in node:
31335
31806
  0 && (module.exports = {
31336
31807
  ABLETIME_MCP_PATH,
31808
+ ABLETIME_MCP_PM_PATH,
31809
+ ABLETIME_PM_TOOL_NAMES,
31337
31810
  AGENT_GIT_ACTIONS,
31338
31811
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
31339
31812
  ATTACHMENTS_DIR,
@@ -31423,8 +31896,10 @@ init_outbound_watch();
31423
31896
  WORKTREE_GITHUB_MCP_TOOLS,
31424
31897
  WORKTREE_LINEAR_MCP_TOOLS,
31425
31898
  WORKTREE_MCP_TOOLS,
31899
+ ableTimeDependencyTarget,
31426
31900
  abletimeMcpRequest,
31427
31901
  abletimeMcpUrl,
31902
+ abletimeToolUsesPm,
31428
31903
  ackSlackInboundSeen,
31429
31904
  addBoardPin,
31430
31905
  addPrStackLayer,
@@ -31697,6 +32172,7 @@ init_outbound_watch();
31697
32172
  git,
31698
32173
  githubAgentGitEnv,
31699
32174
  githubHttpsInsteadOfEntries,
32175
+ githubRelationEditFlag,
31700
32176
  globalAgentCwd,
31701
32177
  groupHomeBoardWorktrees,
31702
32178
  handleSlackInbound,
@@ -31746,6 +32222,7 @@ init_outbound_watch();
31746
32222
  isInternalAgentStatusText,
31747
32223
  isIssueSourceConnected,
31748
32224
  isLinearConnected,
32225
+ isLinearNoneToken,
31749
32226
  isLinearOAuthCancelled,
31750
32227
  isOptionalServiceId,
31751
32228
  isOrchestratorCapableAgent,
@@ -31783,6 +32260,7 @@ init_outbound_watch();
31783
32260
  linearGraphql,
31784
32261
  linearOAuthAuthorizeUrl,
31785
32262
  linearOAuthCredentials,
32263
+ linearRelationCreateInput,
31786
32264
  linearTicketIdFromThread,
31787
32265
  listAbleTimeAssignedIssues,
31788
32266
  listAbleTimeProjects,
@@ -31851,6 +32329,7 @@ init_outbound_watch();
31851
32329
  nonInteractiveGitProcessEnv,
31852
32330
  normalizeAbleTimeHost,
31853
32331
  normalizeCodeSelection,
32332
+ normalizeLinearRelationType,
31854
32333
  normalizeParseResult,
31855
32334
  normalizeServiceOrigin,
31856
32335
  normalizeThinkingEffort,
@@ -31944,6 +32423,8 @@ init_outbound_watch();
31944
32423
  resolveGithubAgentToken,
31945
32424
  resolveGithubRepoSlug,
31946
32425
  resolveLinearCycle,
32426
+ resolveLinearLabel,
32427
+ resolveLinearProject,
31947
32428
  resolveLinearState,
31948
32429
  resolveLinearTeam,
31949
32430
  resolveLoginCommand,